Project

General

Profile

Download (11.5 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / common / jsonschema / validator.js @ 74bbdd9a

1
/* SPDX-License-Identifier: MIT
2
 *
3
 * jsonschema is licensed under MIT license.
4
 *
5
 * Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl>
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
8
 * this software and associated documentation files (the "Software"), to deal in
9
 * the Software without restriction, including without limitation the rights to
10
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11
 * of the Software, and to permit persons to whom the Software is furnished to do
12
 * so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

    
26
#IMPORT common/jsonschema/urllib_mock.js AS urilib
27
#IMPORT common/jsonschema/attribute.js
28
#IMPORT common/jsonschema/helpers.js
29

    
30
#FROM common/jsonschema/scan.js IMPORT scan AS scanSchema
31

    
32
var ValidatorResult = helpers.ValidatorResult;
33
var ValidatorResultError = helpers.ValidatorResultError;
34
var SchemaError = helpers.SchemaError;
35
var SchemaContext = helpers.SchemaContext;
36
//var anonymousBase = 'vnd.jsonschema:///';
37
var anonymousBase = '/';
38

    
39
/**
40
 * Creates a new Validator object
41
 * @name Validator
42
 * @constructor
43
 */
44
var Validator = function Validator () {
45
  // Allow a validator instance to override global custom formats or to have their
46
  // own custom formats.
47
  this.customFormats = Object.create(Validator.prototype.customFormats);
48
  this.schemas = {};
49
  this.unresolvedRefs = [];
50

    
51
  // Use Object.create to make this extensible without Validator instances stepping on each other's toes.
52
  this.types = Object.create(types);
53
  this.attributes = Object.create(attribute.validators);
54
};
55

    
56
// Allow formats to be registered globally.
57
Validator.prototype.customFormats = {};
58

    
59
// Hint at the presence of a property
60
Validator.prototype.schemas = null;
61
Validator.prototype.types = null;
62
Validator.prototype.attributes = null;
63
Validator.prototype.unresolvedRefs = null;
64

    
65
/**
66
 * Adds a schema with a certain urn to the Validator instance.
67
 * @param schema
68
 * @param urn
69
 * @return {Object}
70
 */
71
Validator.prototype.addSchema = function addSchema (schema, base) {
72
  var self = this;
73
  if (!schema) {
74
    return null;
75
  }
76
  var scan = scanSchema(base||anonymousBase, schema);
77
  var ourUri = base || schema.$id || schema.id;
78
  for(var uri in scan.id){
79
    this.schemas[uri] = scan.id[uri];
80
  }
81
  for(var uri in scan.ref){
82
    // If this schema is already defined, it will be filtered out by the next step
83
    this.unresolvedRefs.push(uri);
84
  }
85
  // Remove newly defined schemas from unresolvedRefs
86
  this.unresolvedRefs = this.unresolvedRefs.filter(function(uri){
87
    return typeof self.schemas[uri]==='undefined';
88
  });
89
  return this.schemas[ourUri];
90
};
91

    
92
Validator.prototype.addSubSchemaArray = function addSubSchemaArray(baseuri, schemas) {
93
  if(!Array.isArray(schemas)) return;
94
  for(var i=0; i<schemas.length; i++){
95
    this.addSubSchema(baseuri, schemas[i]);
96
  }
97
};
98

    
99
Validator.prototype.addSubSchemaObject = function addSubSchemaArray(baseuri, schemas) {
100
  if(!schemas || typeof schemas!='object') return;
101
  for(var p in schemas){
102
    this.addSubSchema(baseuri, schemas[p]);
103
  }
104
};
105

    
106

    
107

    
108
/**
109
 * Sets all the schemas of the Validator instance.
110
 * @param schemas
111
 */
112
Validator.prototype.setSchemas = function setSchemas (schemas) {
113
  this.schemas = schemas;
114
};
115

    
116
/**
117
 * Returns the schema of a certain urn
118
 * @param urn
119
 */
120
Validator.prototype.getSchema = function getSchema (urn) {
121
  return this.schemas[urn];
122
};
123

    
124
/**
125
 * Validates instance against the provided schema
126
 * @param instance
127
 * @param schema
128
 * @param [options]
129
 * @param [ctx]
130
 * @return {Array}
131
 */
132
Validator.prototype.validate = function validate (instance, schema, options, ctx) {
133
  if((typeof schema !== 'boolean' && typeof schema !== 'object') || schema === null){
134
    throw new SchemaError('Expected `schema` to be an object or boolean');
135
  }
136
  if (!options) {
137
    options = {};
138
  }
139
  // This section indexes subschemas in the provided schema, so they don't need to be added with Validator#addSchema
140
  // This will work so long as the function at uri.resolve() will resolve a relative URI to a relative URI
141
  var id = schema.$id || schema.id;
142
  var base = urilib.resolve(options.base||anonymousBase, id||'');
143
  if(!ctx){
144
    ctx = new SchemaContext(schema, options, [], base, Object.create(this.schemas));
145
    if (!ctx.schemas[base]) {
146
      ctx.schemas[base] = schema;
147
    }
148
    var found = scanSchema(base, schema);
149
    for(var n in found.id){
150
      var sch = found.id[n];
151
      ctx.schemas[n] = sch;
152
    }
153
  }
154
  if(options.required && instance===undefined){
155
    var result = new ValidatorResult(instance, schema, options, ctx);
156
    result.addError('is required, but is undefined');
157
    return result;
158
  }
159
  var result = this.validateSchema(instance, schema, options, ctx);
160
  if (!result) {
161
    throw new Error('Result undefined');
162
  }else if(options.throwAll && result.errors.length){
163
    throw new ValidatorResultError(result);
164
  }
165
  return result;
166
};
167

    
168
/**
169
* @param Object schema
170
* @return mixed schema uri or false
171
*/
172
function shouldResolve(schema) {
173
  var ref = (typeof schema === 'string') ? schema : schema.$ref;
174
  if (typeof ref=='string') return ref;
175
  return false;
176
}
177

    
178
/**
179
 * Validates an instance against the schema (the actual work horse)
180
 * @param instance
181
 * @param schema
182
 * @param options
183
 * @param ctx
184
 * @private
185
 * @return {ValidatorResult}
186
 */
187
Validator.prototype.validateSchema = function validateSchema (instance, schema, options, ctx) {
188
  var result = new ValidatorResult(instance, schema, options, ctx);
189

    
190
  // Support for the true/false schemas
191
  if(typeof schema==='boolean') {
192
    if(schema===true){
193
      // `true` is always valid
194
      schema = {};
195
    }else if(schema===false){
196
      // `false` is always invalid
197
      schema = {type: []};
198
    }
199
  }else if(!schema){
200
    // This might be a string
201
    throw new Error("schema is undefined");
202
  }
203

    
204
  if (schema['extends']) {
205
    if (Array.isArray(schema['extends'])) {
206
      var schemaobj = {schema: schema, ctx: ctx};
207
      schema['extends'].forEach(this.schemaTraverser.bind(this, schemaobj));
208
      schema = schemaobj.schema;
209
      schemaobj.schema = null;
210
      schemaobj.ctx = null;
211
      schemaobj = null;
212
    } else {
213
      schema = helpers.deepMerge(schema, this.superResolve(schema['extends'], ctx));
214
    }
215
  }
216

    
217
  // If passed a string argument, load that schema URI
218
  var switchSchema = shouldResolve(schema);
219
  if (switchSchema) {
220
    var resolved = this.resolve(schema, switchSchema, ctx);
221
    var subctx = new SchemaContext(resolved.subschema, options, ctx.path, resolved.switchSchema, ctx.schemas);
222
    return this.validateSchema(instance, resolved.subschema, options, subctx);
223
  }
224

    
225
  var skipAttributes = options && options.skipAttributes || [];
226
  // Validate each schema attribute against the instance
227
  for (var key in schema) {
228
    if (!attribute.ignoreProperties[key] && skipAttributes.indexOf(key) < 0) {
229
      var validatorErr = null;
230
      var validator = this.attributes[key];
231
      if (validator) {
232
        validatorErr = validator.call(this, instance, schema, options, ctx);
233
      } else if (options.allowUnknownAttributes === false) {
234
        // This represents an error with the schema itself, not an invalid instance
235
        throw new SchemaError("Unsupported attribute: " + key, schema);
236
      }
237
      if (validatorErr) {
238
        result.importErrors(validatorErr);
239
      }
240
    }
241
  }
242

    
243
  if (typeof options.rewrite == 'function') {
244
    var value = options.rewrite.call(this, instance, schema, options, ctx);
245
    result.instance = value;
246
  }
247
  return result;
248
};
249

    
250
/**
251
* @private
252
* @param Object schema
253
* @param SchemaContext ctx
254
* @returns Object schema or resolved schema
255
*/
256
Validator.prototype.schemaTraverser = function schemaTraverser (schemaobj, s) {
257
  schemaobj.schema = helpers.deepMerge(schemaobj.schema, this.superResolve(s, schemaobj.ctx));
258
};
259

    
260
/**
261
* @private
262
* @param Object schema
263
* @param SchemaContext ctx
264
* @returns Object schema or resolved schema
265
*/
266
Validator.prototype.superResolve = function superResolve (schema, ctx) {
267
  var ref = shouldResolve(schema);
268
  if(ref) {
269
    return this.resolve(schema, ref, ctx).subschema;
270
  }
271
  return schema;
272
};
273

    
274
/**
275
* @private
276
* @param Object schema
277
* @param Object switchSchema
278
* @param SchemaContext ctx
279
* @return Object resolved schemas {subschema:String, switchSchema: String}
280
* @throws SchemaError
281
*/
282
Validator.prototype.resolve = function resolve (schema, switchSchema, ctx) {
283
  switchSchema = ctx.resolve(switchSchema);
284
  // First see if the schema exists under the provided URI
285
  if (ctx.schemas[switchSchema]) {
286
    return {subschema: ctx.schemas[switchSchema], switchSchema: switchSchema};
287
  }
288
  // Else try walking the property pointer
289
  var parsed = urilib.parse(switchSchema);
290
  var fragment = parsed && parsed.hash;
291
  var document = fragment && fragment.length && switchSchema.substr(0, switchSchema.length - fragment.length);
292
  if (!document || !ctx.schemas[document]) {
293
    throw new SchemaError("no such schema <" + switchSchema + ">", schema);
294
  }
295
  var subschema = helpers.objectGetPath(ctx.schemas[document], fragment.substr(1));
296
  if(subschema===undefined){
297
      throw new SchemaError("no such schema " + fragment + " located in <" + document + " delme " + ">", [ctx.schemas[document], schema]);
298
  }
299
  return {subschema: subschema, switchSchema: switchSchema};
300
};
301

    
302
/**
303
 * Tests whether the instance if of a certain type.
304
 * @private
305
 * @param instance
306
 * @param schema
307
 * @param options
308
 * @param ctx
309
 * @param type
310
 * @return {boolean}
311
 */
312
Validator.prototype.testType = function validateType (instance, schema, options, ctx, type) {
313
  if(type===undefined){
314
    return;
315
  }else if(type===null){
316
    throw new SchemaError('Unexpected null in "type" keyword');
317
  }
318
  if (typeof this.types[type] == 'function') {
319
    return this.types[type].call(this, instance);
320
  }
321
  if (type && typeof type == 'object') {
322
    var res = this.validateSchema(instance, type, options, ctx);
323
    return res === undefined || !(res && res.errors.length);
324
  }
325
  // Undefined or properties not on the list are acceptable, same as not being defined
326
  return true;
327
};
328

    
329
var types = Validator.prototype.types = {};
330
types.string = function testString (instance) {
331
  return typeof instance == 'string';
332
};
333
types.number = function testNumber (instance) {
334
  // isFinite returns false for NaN, Infinity, and -Infinity
335
  return typeof instance == 'number' && isFinite(instance);
336
};
337
types.integer = function testInteger (instance) {
338
  return (typeof instance == 'number') && instance % 1 === 0;
339
};
340
types.boolean = function testBoolean (instance) {
341
  return typeof instance == 'boolean';
342
};
343
types.array = function testArray (instance) {
344
  return Array.isArray(instance);
345
};
346
types['null'] = function testNull (instance) {
347
  return instance === null;
348
};
349
types.date = function testDate (instance) {
350
  return instance instanceof Date;
351
};
352
types.any = function testAny (instance) {
353
  return true;
354
};
355
types.object = function testObject (instance) {
356
  // TODO: fix this - see #15
357
  return instance && (typeof instance === 'object') && !(Array.isArray(instance)) && !(instance instanceof Date);
358
};
359

    
360
#EXPORT Validator
(5-5/5)