summaryrefslogtreecommitdiff
path: root/app/node_modules/okschema/index.js
blob: 1528eab38a62d93c513cdfc348a2630a1d2239f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
var cloneDeep = require('lodash.clonedeep');
var mschema = require('mschema');
var v = require('validator');

/**
 * Custom types.
 * To add a custom type, add a key with the type name and a value
 * in the form {parent: {...}, assertValid: function(d) {...}}
 * where parent is the base mschema spec and validate is a function
 * accepting one data point and throwing an array of errors if invalid.
 *
 * Error array format is derived from mschema and is in the form
 * [{property: x, constraint: x, actual: x, expected: x, message: x} ... ]
 */
var types = {
  /**
   * Larger text inputs. Currently just proxies to string type.
   */
  'text': {
    parent: {type: 'string'},
    // Let parent handle validation
    assertValid: function(spec, value) {}
  },
  'video': {
    parent: {type: 'string'},
    // Let parent handle validation
    assertValid: function(spec, value) {}
  },
  'enum': {
    parent: {type: 'string'},
    assertValid: function(spec, value) {
      value = value || '';
      if (spec.options.indexOf(value.trim()) === -1) {
        throw [{
          constraint: 'enum',
          actual: value,
          expected: JSON.stringify(spec.options),
          message: 'Invalid value'
        }];
      }
    }
  },
  'captioned-image-list': {
    parent: [{
      uri: { type: 'string' }, // TODO Implement URI type
      caption: { type: 'string' }
    }],
    assertValid: function(spec, value) {
      var message;
      var actual;
      if (!value || !value.length) {
        throw [{
          message: 'Not an array',
          expected: JSON.stringify(this.parent),
          actual: value
        }];
      }
    }
  }
}

/**
 * OKSchema!
 * Meant as a thin wrapper around some existing schema validation
 * module, mostly to allow for the extension of types.
 */
function OKSchema(spec) {
  if (!(this instanceof OKSchema)) return new OKSchema(spec);
  if (!spec)
    throw new Error('No spec provided to OKSchema');
  spec = cloneDeep(spec);
  var specKeys = Object.keys(spec);
  // Cache the mschema version of our spec
  this._mschemaSpec = specKeys.reduce(function(cache, prop) {
    // If custom type, return its parent spec
    var type = spec[prop].type;
    if (types[type]) {
      cache[prop] = types[type].parent;
    // Otherwise, it's already in mschema format
    } else {
      cache[prop] = spec[prop];
    }
    return cache;
  }, {});

  // Find ID field
  var idField;
  specKeys.every(function(prop) {
    if (prop === 'id' || spec[prop].id) {
      idField = prop;
      return false;
    } else {
      return true;
    }
  });

  // Register autoincrement fields
  // NOTE Does not work for nested fields
  var autoIncrementFields = specKeys.reduce(function(arr, prop) {
    if (spec[prop] === 'autoincrement') {
      arr.push(prop);
    }
    return arr;
  }, []);

  Object.defineProperty(this, 'spec', {
    get: function() {
      return cloneDeep(spec);
    },
    enumerable: true
  });

  Object.defineProperty(this, 'idField', {
    value: idField,
    writable: true,
    enumerable: true
  });

  Object.defineProperty(this, 'autoIncrementFields',{
    get: function() {
      return cloneDeep(autoIncrementFields);
    },
    enumerable: true
  });
}

OKSchema.prototype.assertValid = function(data) {
  data = data || {};
  var spec = this.spec;
  // Run through custom validators, they'll throw if invalid
  Object.keys(data).forEach(function(prop) {
    var type = spec[prop].type;
    if (types[type]) {
      types[type].assertValid(spec[prop], data[prop]);
    }
  });
  var result = mschema.validate(data, this.toMschema());
  if (!result.valid)
    throw result.errors;
};

/**
 * Return our custom spec as an mschema spec
 */
OKSchema.prototype.toMschema = function() {
  return this._mschemaSpec;
};

module.exports = OKSchema;