source: Dev/trunk/src/node_modules/tv4/tv4.js @ 513

Last change on this file since 513 was 487, checked in by hendrikvanantwerpen, 11 years ago

Completed migration to API, without CouchDB proxy.

Move to API is now completed. The full API is password protected, a very
limited API is exposed for respondents, which works with secrets that
are passed in URLs.

Serverside the HTTPResult class was introduced, which is similar to
Promises, but specifically for HTTP. It carries a status code and
response and makes it easier to extract parts of async handling in
separate functions.

Fixed a bug in our schema (it seems optional attributes don't exist but
a required list does). Verification of our schema by grunt-tv4 didn't
work yet. Our schema is organized the wrong way (this is fixable),
but the json-schema schema has problems with simple types and $refs.

File size: 41.5 KB
Line 
1/*
2Author: Geraint Luff and others
3Year: 2013
4
5This code is released into the "public domain" by its author(s).  Anybody may use, alter and distribute the code without restriction.  The author makes no guarantees, and takes no liability of any kind for use of this code.
6
7If you find a bug or make an improvement, it would be courteous to let the author know, but it is not compulsory.
8*/
9(function (global) {
10'use strict';
11
12// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2Fkeys
13if (!Object.keys) {
14        Object.keys = (function () {
15                var hasOwnProperty = Object.prototype.hasOwnProperty,
16                        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
17                        dontEnums = [
18                                'toString',
19                                'toLocaleString',
20                                'valueOf',
21                                'hasOwnProperty',
22                                'isPrototypeOf',
23                                'propertyIsEnumerable',
24                                'constructor'
25                        ],
26                        dontEnumsLength = dontEnums.length;
27
28                return function (obj) {
29                        if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
30                                throw new TypeError('Object.keys called on non-object');
31                        }
32
33                        var result = [];
34
35                        for (var prop in obj) {
36                                if (hasOwnProperty.call(obj, prop)) {
37                                        result.push(prop);
38                                }
39                        }
40
41                        if (hasDontEnumBug) {
42                                for (var i=0; i < dontEnumsLength; i++) {
43                                        if (hasOwnProperty.call(obj, dontEnums[i])) {
44                                                result.push(dontEnums[i]);
45                                        }
46                                }
47                        }
48                        return result;
49                };
50        })();
51}
52// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
53if (!Object.create) {
54        Object.create = (function(){
55                function F(){}
56
57                return function(o){
58                        if (arguments.length !== 1) {
59                                throw new Error('Object.create implementation only accepts one parameter.');
60                        }
61                        F.prototype = o;
62                        return new F();
63                };
64        })();
65}
66// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FisArray
67if(!Array.isArray) {
68        Array.isArray = function (vArg) {
69                return Object.prototype.toString.call(vArg) === "[object Array]";
70        };
71}
72// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FindexOf
73if (!Array.prototype.indexOf) {
74        Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
75                if (this === null) {
76                        throw new TypeError();
77                }
78                var t = Object(this);
79                var len = t.length >>> 0;
80
81                if (len === 0) {
82                        return -1;
83                }
84                var n = 0;
85                if (arguments.length > 1) {
86                        n = Number(arguments[1]);
87                        if (n !== n) { // shortcut for verifying if it's NaN
88                                n = 0;
89                        } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
90                                n = (n > 0 || -1) * Math.floor(Math.abs(n));
91                        }
92                }
93                if (n >= len) {
94                        return -1;
95                }
96                var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
97                for (; k < len; k++) {
98                        if (k in t && t[k] === searchElement) {
99                                return k;
100                        }
101                }
102                return -1;
103        };
104}
105
106// Grungey Object.isFrozen hack
107if (!Object.isFrozen) {
108        Object.isFrozen = function (obj) {
109                var key = "tv4_test_frozen_key";
110                while (obj.hasOwnProperty(key)) {
111                        key += Math.random();
112                }
113                try {
114                        obj[key] = true;
115                        delete obj[key];
116                        return false;
117                } catch (e) {
118                        return true;
119                }
120        };
121}
122var ValidatorContext = function ValidatorContext(parent, collectMultiple, errorMessages, checkRecursive, trackUnknownProperties) {
123        this.missing = [];
124        this.missingMap = {};
125        this.formatValidators = parent ? Object.create(parent.formatValidators) : {};
126        this.schemas = parent ? Object.create(parent.schemas) : {};
127        this.collectMultiple = collectMultiple;
128        this.errors = [];
129        this.handleError = collectMultiple ? this.collectError : this.returnError;
130        if (checkRecursive) {
131                this.checkRecursive = true;
132                this.scanned = [];
133                this.scannedFrozen = [];
134                this.scannedFrozenSchemas = [];
135                this.scannedFrozenValidationErrors = [];
136                this.validatedSchemasKey = 'tv4_validation_id';
137                this.validationErrorsKey = 'tv4_validation_errors_id';
138        }
139        if (trackUnknownProperties) {
140                this.trackUnknownProperties = true;
141                this.knownPropertyPaths = {};
142                this.unknownPropertyPaths = {};
143        }
144        this.errorMessages = errorMessages;
145};
146ValidatorContext.prototype.createError = function (code, messageParams, dataPath, schemaPath, subErrors) {
147        var messageTemplate = this.errorMessages[code] || ErrorMessagesDefault[code];
148        if (typeof messageTemplate !== 'string') {
149                return new ValidationError(code, "Unknown error code " + code + ": " + JSON.stringify(messageParams), dataPath, schemaPath, subErrors);
150        }
151        // Adapted from Crockford's supplant()
152        var message = messageTemplate.replace(/\{([^{}]*)\}/g, function (whole, varName) {
153                var subValue = messageParams[varName];
154                return typeof subValue === 'string' || typeof subValue === 'number' ? subValue : whole;
155        });
156        return new ValidationError(code, message, dataPath, schemaPath, subErrors);
157};
158ValidatorContext.prototype.returnError = function (error) {
159        return error;
160};
161ValidatorContext.prototype.collectError = function (error) {
162        if (error) {
163                this.errors.push(error);
164        }
165        return null;
166};
167ValidatorContext.prototype.prefixErrors = function (startIndex, dataPath, schemaPath) {
168        for (var i = startIndex; i < this.errors.length; i++) {
169                this.errors[i] = this.errors[i].prefixWith(dataPath, schemaPath);
170        }
171        return this;
172};
173ValidatorContext.prototype.banUnknownProperties = function () {
174        for (var unknownPath in this.unknownPropertyPaths) {
175                var error = this.createError(ErrorCodes.UNKNOWN_PROPERTY, {path: unknownPath}, unknownPath, "");
176                var result = this.handleError(error);
177                if (result) {
178                        return result;
179                }
180        }
181        return null;
182};
183
184ValidatorContext.prototype.addFormat = function (format, validator) {
185        if (typeof format === 'object') {
186                for (var key in format) {
187                        this.addFormat(key, format[key]);
188                }
189                return this;
190        }
191        this.formatValidators[format] = validator;
192};
193ValidatorContext.prototype.resolveRefs = function (schema, urlHistory) {
194        if (schema['$ref'] !== undefined) {
195                urlHistory = urlHistory || {};
196                if (urlHistory[schema['$ref']]) {
197                        return this.createError(ErrorCodes.CIRCULAR_REFERENCE, {urls: Object.keys(urlHistory).join(', ')}, '', '');
198                }
199                urlHistory[schema['$ref']] = true;
200                schema = this.getSchema(schema['$ref'], urlHistory);
201        }
202        return schema;
203};
204ValidatorContext.prototype.getSchema = function (url, urlHistory) {
205        var schema;
206        if (this.schemas[url] !== undefined) {
207                schema = this.schemas[url];
208                return this.resolveRefs(schema, urlHistory);
209        }
210        var baseUrl = url;
211        var fragment = "";
212        if (url.indexOf('#') !== -1) {
213                fragment = url.substring(url.indexOf("#") + 1);
214                baseUrl = url.substring(0, url.indexOf("#"));
215        }
216        if (typeof this.schemas[baseUrl] === 'object') {
217                schema = this.schemas[baseUrl];
218                var pointerPath = decodeURIComponent(fragment);
219                if (pointerPath === "") {
220                        return this.resolveRefs(schema, urlHistory);
221                } else if (pointerPath.charAt(0) !== "/") {
222                        return undefined;
223                }
224                var parts = pointerPath.split("/").slice(1);
225                for (var i = 0; i < parts.length; i++) {
226                        var component = parts[i].replace(/~1/g, "/").replace(/~0/g, "~");
227                        if (schema[component] === undefined) {
228                                schema = undefined;
229                                break;
230                        }
231                        schema = schema[component];
232                }
233                if (schema !== undefined) {
234                        return this.resolveRefs(schema, urlHistory);
235                }
236        }
237        if (this.missing[baseUrl] === undefined) {
238                this.missing.push(baseUrl);
239                this.missing[baseUrl] = baseUrl;
240                this.missingMap[baseUrl] = baseUrl;
241        }
242};
243ValidatorContext.prototype.searchSchemas = function (schema, url) {
244        if (schema && typeof schema === "object") {
245                if (typeof schema.id === "string") {
246                        if (isTrustedUrl(url, schema.id)) {
247                                if (this.schemas[schema.id] === undefined) {
248                                        this.schemas[schema.id] = schema;
249                                }
250                        }
251                }
252                for (var key in schema) {
253                        if (key !== "enum") {
254                                if (typeof schema[key] === "object") {
255                                        this.searchSchemas(schema[key], url);
256                                } else if (key === "$ref") {
257                                        var uri = getDocumentUri(schema[key]);
258                                        if (uri && this.schemas[uri] === undefined && this.missingMap[uri] === undefined) {
259                                                this.missingMap[uri] = uri;
260                                        }
261                                }
262                        }
263                }
264        }
265};
266ValidatorContext.prototype.addSchema = function (url, schema) {
267        //overload
268        if (typeof url !== 'string' || typeof schema === 'undefined') {
269                if (typeof url === 'object' && typeof url.id === 'string') {
270                        schema = url;
271                        url = schema.id;
272                }
273                else {
274                        return;
275                }
276        }
277        if (url = getDocumentUri(url) + "#") {
278                // Remove empty fragment
279                url = getDocumentUri(url);
280        }
281        this.schemas[url] = schema;
282        delete this.missingMap[url];
283        normSchema(schema, url);
284        this.searchSchemas(schema, url);
285};
286
287ValidatorContext.prototype.getSchemaMap = function () {
288        var map = {};
289        for (var key in this.schemas) {
290                map[key] = this.schemas[key];
291        }
292        return map;
293};
294
295ValidatorContext.prototype.getSchemaUris = function (filterRegExp) {
296        var list = [];
297        for (var key in this.schemas) {
298                if (!filterRegExp || filterRegExp.test(key)) {
299                        list.push(key);
300                }
301        }
302        return list;
303};
304
305ValidatorContext.prototype.getMissingUris = function (filterRegExp) {
306        var list = [];
307        for (var key in this.missingMap) {
308                if (!filterRegExp || filterRegExp.test(key)) {
309                        list.push(key);
310                }
311        }
312        return list;
313};
314
315ValidatorContext.prototype.dropSchemas = function () {
316        this.schemas = {};
317        this.reset();
318};
319ValidatorContext.prototype.reset = function () {
320        this.missing = [];
321        this.missingMap = {};
322        this.errors = [];
323};
324
325ValidatorContext.prototype.validateAll = function (data, schema, dataPathParts, schemaPathParts, dataPointerPath) {
326        var topLevel;
327        schema = this.resolveRefs(schema);
328        if (!schema) {
329                return null;
330        } else if (schema instanceof ValidationError) {
331                this.errors.push(schema);
332                return schema;
333        }
334
335        var startErrorCount = this.errors.length;
336        var frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;
337        if (this.checkRecursive && data && typeof data === 'object') {
338                topLevel = !this.scanned.length;
339                if (data[this.validatedSchemasKey]) {
340                        var schemaIndex = data[this.validatedSchemasKey].indexOf(schema);
341                        if (schemaIndex !== -1) {
342                                this.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);
343                                return null;
344                        }
345                }
346                if (Object.isFrozen(data)) {
347                        frozenIndex = this.scannedFrozen.indexOf(data);
348                        if (frozenIndex !== -1) {
349                                var frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);
350                                if (frozenSchemaIndex !== -1) {
351                                        this.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);
352                                        return null;
353                                }
354                        }
355                }
356                this.scanned.push(data);
357                if (Object.isFrozen(data)) {
358                        if (frozenIndex === -1) {
359                                frozenIndex = this.scannedFrozen.length;
360                                this.scannedFrozen.push(data);
361                                this.scannedFrozenSchemas.push([]);
362                        }
363                        scannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;
364                        this.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;
365                        this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];
366                } else {
367                        if (!data[this.validatedSchemasKey]) {
368                                try {
369                                        Object.defineProperty(data, this.validatedSchemasKey, {
370                                                value: [],
371                                                configurable: true
372                                        });
373                                        Object.defineProperty(data, this.validationErrorsKey, {
374                                                value: [],
375                                                configurable: true
376                                        });
377                                } catch (e) {
378                                        //IE 7/8 workaround
379                                        data[this.validatedSchemasKey] = [];
380                                        data[this.validationErrorsKey] = [];
381                                }
382                        }
383                        scannedSchemasIndex = data[this.validatedSchemasKey].length;
384                        data[this.validatedSchemasKey][scannedSchemasIndex] = schema;
385                        data[this.validationErrorsKey][scannedSchemasIndex] = [];
386                }
387        }
388
389        var errorCount = this.errors.length;
390        var error = this.validateBasic(data, schema, dataPointerPath)
391                || this.validateNumeric(data, schema, dataPointerPath)
392                || this.validateString(data, schema, dataPointerPath)
393                || this.validateArray(data, schema, dataPointerPath)
394                || this.validateObject(data, schema, dataPointerPath)
395                || this.validateCombinations(data, schema, dataPointerPath)
396                || this.validateFormat(data, schema, dataPointerPath)
397                || null;
398
399        if (topLevel) {
400                while (this.scanned.length) {
401                        var item = this.scanned.pop();
402                        delete item[this.validatedSchemasKey];
403                }
404                this.scannedFrozen = [];
405                this.scannedFrozenSchemas = [];
406        }
407
408        if (error || errorCount !== this.errors.length) {
409                while ((dataPathParts && dataPathParts.length) || (schemaPathParts && schemaPathParts.length)) {
410                        var dataPart = (dataPathParts && dataPathParts.length) ? "" + dataPathParts.pop() : null;
411                        var schemaPart = (schemaPathParts && schemaPathParts.length) ? "" + schemaPathParts.pop() : null;
412                        if (error) {
413                                error = error.prefixWith(dataPart, schemaPart);
414                        }
415                        this.prefixErrors(errorCount, dataPart, schemaPart);
416                }
417        }
418       
419        if (scannedFrozenSchemaIndex !== null) {
420                this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);
421        } else if (scannedSchemasIndex !== null) {
422                data[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);
423        }
424
425        return this.handleError(error);
426};
427ValidatorContext.prototype.validateFormat = function (data, schema) {
428        if (typeof schema.format !== 'string' || !this.formatValidators[schema.format]) {
429                return null;
430        }
431        var errorMessage = this.formatValidators[schema.format].call(null, data, schema);
432        if (typeof errorMessage === 'string' || typeof errorMessage === 'number') {
433                return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage}).prefixWith(null, "format");
434        } else if (errorMessage && typeof errorMessage === 'object') {
435                return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage.message || "?"}, errorMessage.dataPath || null, errorMessage.schemaPath || "/format");
436        }
437        return null;
438};
439
440function recursiveCompare(A, B) {
441        if (A === B) {
442                return true;
443        }
444        if (typeof A === "object" && typeof B === "object") {
445                if (Array.isArray(A) !== Array.isArray(B)) {
446                        return false;
447                } else if (Array.isArray(A)) {
448                        if (A.length !== B.length) {
449                                return false;
450                        }
451                        for (var i = 0; i < A.length; i++) {
452                                if (!recursiveCompare(A[i], B[i])) {
453                                        return false;
454                                }
455                        }
456                } else {
457                        var key;
458                        for (key in A) {
459                                if (B[key] === undefined && A[key] !== undefined) {
460                                        return false;
461                                }
462                        }
463                        for (key in B) {
464                                if (A[key] === undefined && B[key] !== undefined) {
465                                        return false;
466                                }
467                        }
468                        for (key in A) {
469                                if (!recursiveCompare(A[key], B[key])) {
470                                        return false;
471                                }
472                        }
473                }
474                return true;
475        }
476        return false;
477}
478
479ValidatorContext.prototype.validateBasic = function validateBasic(data, schema, dataPointerPath) {
480        var error;
481        if (error = this.validateType(data, schema, dataPointerPath)) {
482                return error.prefixWith(null, "type");
483        }
484        if (error = this.validateEnum(data, schema, dataPointerPath)) {
485                return error.prefixWith(null, "type");
486        }
487        return null;
488};
489
490ValidatorContext.prototype.validateType = function validateType(data, schema) {
491        if (schema.type === undefined) {
492                return null;
493        }
494        var dataType = typeof data;
495        if (data === null) {
496                dataType = "null";
497        } else if (Array.isArray(data)) {
498                dataType = "array";
499        }
500        var allowedTypes = schema.type;
501        if (typeof allowedTypes !== "object") {
502                allowedTypes = [allowedTypes];
503        }
504
505        for (var i = 0; i < allowedTypes.length; i++) {
506                var type = allowedTypes[i];
507                if (type === dataType || (type === "integer" && dataType === "number" && (data % 1 === 0))) {
508                        return null;
509                }
510        }
511        return this.createError(ErrorCodes.INVALID_TYPE, {type: dataType, expected: allowedTypes.join("/")});
512};
513
514ValidatorContext.prototype.validateEnum = function validateEnum(data, schema) {
515        if (schema["enum"] === undefined) {
516                return null;
517        }
518        for (var i = 0; i < schema["enum"].length; i++) {
519                var enumVal = schema["enum"][i];
520                if (recursiveCompare(data, enumVal)) {
521                        return null;
522                }
523        }
524        return this.createError(ErrorCodes.ENUM_MISMATCH, {value: (typeof JSON !== 'undefined') ? JSON.stringify(data) : data});
525};
526
527ValidatorContext.prototype.validateNumeric = function validateNumeric(data, schema, dataPointerPath) {
528        return this.validateMultipleOf(data, schema, dataPointerPath)
529                || this.validateMinMax(data, schema, dataPointerPath)
530                || null;
531};
532
533ValidatorContext.prototype.validateMultipleOf = function validateMultipleOf(data, schema) {
534        var multipleOf = schema.multipleOf || schema.divisibleBy;
535        if (multipleOf === undefined) {
536                return null;
537        }
538        if (typeof data === "number") {
539                if (data % multipleOf !== 0) {
540                        return this.createError(ErrorCodes.NUMBER_MULTIPLE_OF, {value: data, multipleOf: multipleOf});
541                }
542        }
543        return null;
544};
545
546ValidatorContext.prototype.validateMinMax = function validateMinMax(data, schema) {
547        if (typeof data !== "number") {
548                return null;
549        }
550        if (schema.minimum !== undefined) {
551                if (data < schema.minimum) {
552                        return this.createError(ErrorCodes.NUMBER_MINIMUM, {value: data, minimum: schema.minimum}).prefixWith(null, "minimum");
553                }
554                if (schema.exclusiveMinimum && data === schema.minimum) {
555                        return this.createError(ErrorCodes.NUMBER_MINIMUM_EXCLUSIVE, {value: data, minimum: schema.minimum}).prefixWith(null, "exclusiveMinimum");
556                }
557        }
558        if (schema.maximum !== undefined) {
559                if (data > schema.maximum) {
560                        return this.createError(ErrorCodes.NUMBER_MAXIMUM, {value: data, maximum: schema.maximum}).prefixWith(null, "maximum");
561                }
562                if (schema.exclusiveMaximum && data === schema.maximum) {
563                        return this.createError(ErrorCodes.NUMBER_MAXIMUM_EXCLUSIVE, {value: data, maximum: schema.maximum}).prefixWith(null, "exclusiveMaximum");
564                }
565        }
566        return null;
567};
568
569ValidatorContext.prototype.validateString = function validateString(data, schema, dataPointerPath) {
570        return this.validateStringLength(data, schema, dataPointerPath)
571                || this.validateStringPattern(data, schema, dataPointerPath)
572                || null;
573};
574
575ValidatorContext.prototype.validateStringLength = function validateStringLength(data, schema) {
576        if (typeof data !== "string") {
577                return null;
578        }
579        if (schema.minLength !== undefined) {
580                if (data.length < schema.minLength) {
581                        return this.createError(ErrorCodes.STRING_LENGTH_SHORT, {length: data.length, minimum: schema.minLength}).prefixWith(null, "minLength");
582                }
583        }
584        if (schema.maxLength !== undefined) {
585                if (data.length > schema.maxLength) {
586                        return this.createError(ErrorCodes.STRING_LENGTH_LONG, {length: data.length, maximum: schema.maxLength}).prefixWith(null, "maxLength");
587                }
588        }
589        return null;
590};
591
592ValidatorContext.prototype.validateStringPattern = function validateStringPattern(data, schema) {
593        if (typeof data !== "string" || schema.pattern === undefined) {
594                return null;
595        }
596        var regexp = new RegExp(schema.pattern);
597        if (!regexp.test(data)) {
598                return this.createError(ErrorCodes.STRING_PATTERN, {pattern: schema.pattern}).prefixWith(null, "pattern");
599        }
600        return null;
601};
602ValidatorContext.prototype.validateArray = function validateArray(data, schema, dataPointerPath) {
603        if (!Array.isArray(data)) {
604                return null;
605        }
606        return this.validateArrayLength(data, schema, dataPointerPath)
607                || this.validateArrayUniqueItems(data, schema, dataPointerPath)
608                || this.validateArrayItems(data, schema, dataPointerPath)
609                || null;
610};
611
612ValidatorContext.prototype.validateArrayLength = function validateArrayLength(data, schema) {
613        var error;
614        if (schema.minItems !== undefined) {
615                if (data.length < schema.minItems) {
616                        error = (this.createError(ErrorCodes.ARRAY_LENGTH_SHORT, {length: data.length, minimum: schema.minItems})).prefixWith(null, "minItems");
617                        if (this.handleError(error)) {
618                                return error;
619                        }
620                }
621        }
622        if (schema.maxItems !== undefined) {
623                if (data.length > schema.maxItems) {
624                        error = (this.createError(ErrorCodes.ARRAY_LENGTH_LONG, {length: data.length, maximum: schema.maxItems})).prefixWith(null, "maxItems");
625                        if (this.handleError(error)) {
626                                return error;
627                        }
628                }
629        }
630        return null;
631};
632
633ValidatorContext.prototype.validateArrayUniqueItems = function validateArrayUniqueItems(data, schema) {
634        if (schema.uniqueItems) {
635                for (var i = 0; i < data.length; i++) {
636                        for (var j = i + 1; j < data.length; j++) {
637                                if (recursiveCompare(data[i], data[j])) {
638                                        var error = (this.createError(ErrorCodes.ARRAY_UNIQUE, {match1: i, match2: j})).prefixWith(null, "uniqueItems");
639                                        if (this.handleError(error)) {
640                                                return error;
641                                        }
642                                }
643                        }
644                }
645        }
646        return null;
647};
648
649ValidatorContext.prototype.validateArrayItems = function validateArrayItems(data, schema, dataPointerPath) {
650        if (schema.items === undefined) {
651                return null;
652        }
653        var error, i;
654        if (Array.isArray(schema.items)) {
655                for (i = 0; i < data.length; i++) {
656                        if (i < schema.items.length) {
657                                if (error = this.validateAll(data[i], schema.items[i], [i], ["items", i], dataPointerPath + "/" + i)) {
658                                        return error;
659                                }
660                        } else if (schema.additionalItems !== undefined) {
661                                if (typeof schema.additionalItems === "boolean") {
662                                        if (!schema.additionalItems) {
663                                                error = (this.createError(ErrorCodes.ARRAY_ADDITIONAL_ITEMS, {})).prefixWith("" + i, "additionalItems");
664                                                if (this.handleError(error)) {
665                                                        return error;
666                                                }
667                                        }
668                                } else if (error = this.validateAll(data[i], schema.additionalItems, [i], ["additionalItems"], dataPointerPath + "/" + i)) {
669                                        return error;
670                                }
671                        }
672                }
673        } else {
674                for (i = 0; i < data.length; i++) {
675                        if (error = this.validateAll(data[i], schema.items, [i], ["items"], dataPointerPath + "/" + i)) {
676                                return error;
677                        }
678                }
679        }
680        return null;
681};
682
683ValidatorContext.prototype.validateObject = function validateObject(data, schema, dataPointerPath) {
684        if (typeof data !== "object" || data === null || Array.isArray(data)) {
685                return null;
686        }
687        return this.validateObjectMinMaxProperties(data, schema, dataPointerPath)
688                || this.validateObjectRequiredProperties(data, schema, dataPointerPath)
689                || this.validateObjectProperties(data, schema, dataPointerPath)
690                || this.validateObjectDependencies(data, schema, dataPointerPath)
691                || null;
692};
693
694ValidatorContext.prototype.validateObjectMinMaxProperties = function validateObjectMinMaxProperties(data, schema) {
695        var keys = Object.keys(data);
696        var error;
697        if (schema.minProperties !== undefined) {
698                if (keys.length < schema.minProperties) {
699                        error = this.createError(ErrorCodes.OBJECT_PROPERTIES_MINIMUM, {propertyCount: keys.length, minimum: schema.minProperties}).prefixWith(null, "minProperties");
700                        if (this.handleError(error)) {
701                                return error;
702                        }
703                }
704        }
705        if (schema.maxProperties !== undefined) {
706                if (keys.length > schema.maxProperties) {
707                        error = this.createError(ErrorCodes.OBJECT_PROPERTIES_MAXIMUM, {propertyCount: keys.length, maximum: schema.maxProperties}).prefixWith(null, "maxProperties");
708                        if (this.handleError(error)) {
709                                return error;
710                        }
711                }
712        }
713        return null;
714};
715
716ValidatorContext.prototype.validateObjectRequiredProperties = function validateObjectRequiredProperties(data, schema) {
717        if (schema.required !== undefined) {
718                for (var i = 0; i < schema.required.length; i++) {
719                        var key = schema.required[i];
720                        if (data[key] === undefined) {
721                                var error = this.createError(ErrorCodes.OBJECT_REQUIRED, {key: key}).prefixWith(null, "" + i).prefixWith(null, "required");
722                                if (this.handleError(error)) {
723                                        return error;
724                                }
725                        }
726                }
727        }
728        return null;
729};
730
731ValidatorContext.prototype.validateObjectProperties = function validateObjectProperties(data, schema, dataPointerPath) {
732        var error;
733        for (var key in data) {
734                var keyPointerPath = dataPointerPath + "/" + key.replace(/~/g, '~0').replace(/\//g, '~1');
735                var foundMatch = false;
736                if (schema.properties !== undefined && schema.properties[key] !== undefined) {
737                        foundMatch = true;
738                        if (error = this.validateAll(data[key], schema.properties[key], [key], ["properties", key], keyPointerPath)) {
739                                return error;
740                        }
741                }
742                if (schema.patternProperties !== undefined) {
743                        for (var patternKey in schema.patternProperties) {
744                                var regexp = new RegExp(patternKey);
745                                if (regexp.test(key)) {
746                                        foundMatch = true;
747                                        if (error = this.validateAll(data[key], schema.patternProperties[patternKey], [key], ["patternProperties", patternKey], keyPointerPath)) {
748                                                return error;
749                                        }
750                                }
751                        }
752                }
753                if (!foundMatch) {
754                        if (schema.additionalProperties !== undefined) {
755                                if (this.trackUnknownProperties) {
756                                        this.knownPropertyPaths[keyPointerPath] = true;
757                                        delete this.unknownPropertyPaths[keyPointerPath];
758                                }
759                                if (typeof schema.additionalProperties === "boolean") {
760                                        if (!schema.additionalProperties) {
761                                                error = this.createError(ErrorCodes.OBJECT_ADDITIONAL_PROPERTIES, {}).prefixWith(key, "additionalProperties");
762                                                if (this.handleError(error)) {
763                                                        return error;
764                                                }
765                                        }
766                                } else {
767                                        if (error = this.validateAll(data[key], schema.additionalProperties, [key], ["additionalProperties"], keyPointerPath)) {
768                                                return error;
769                                        }
770                                }
771                        } else if (this.trackUnknownProperties && !this.knownPropertyPaths[keyPointerPath]) {
772                                this.unknownPropertyPaths[keyPointerPath] = true;
773                        }
774                } else if (this.trackUnknownProperties) {
775                        this.knownPropertyPaths[keyPointerPath] = true;
776                        delete this.unknownPropertyPaths[keyPointerPath];
777                }
778        }
779        return null;
780};
781
782ValidatorContext.prototype.validateObjectDependencies = function validateObjectDependencies(data, schema, dataPointerPath) {
783        var error;
784        if (schema.dependencies !== undefined) {
785                for (var depKey in schema.dependencies) {
786                        if (data[depKey] !== undefined) {
787                                var dep = schema.dependencies[depKey];
788                                if (typeof dep === "string") {
789                                        if (data[dep] === undefined) {
790                                                error = this.createError(ErrorCodes.OBJECT_DEPENDENCY_KEY, {key: depKey, missing: dep}).prefixWith(null, depKey).prefixWith(null, "dependencies");
791                                                if (this.handleError(error)) {
792                                                        return error;
793                                                }
794                                        }
795                                } else if (Array.isArray(dep)) {
796                                        for (var i = 0; i < dep.length; i++) {
797                                                var requiredKey = dep[i];
798                                                if (data[requiredKey] === undefined) {
799                                                        error = this.createError(ErrorCodes.OBJECT_DEPENDENCY_KEY, {key: depKey, missing: requiredKey}).prefixWith(null, "" + i).prefixWith(null, depKey).prefixWith(null, "dependencies");
800                                                        if (this.handleError(error)) {
801                                                                return error;
802                                                        }
803                                                }
804                                        }
805                                } else {
806                                        if (error = this.validateAll(data, dep, [], ["dependencies", depKey], dataPointerPath)) {
807                                                return error;
808                                        }
809                                }
810                        }
811                }
812        }
813        return null;
814};
815
816ValidatorContext.prototype.validateCombinations = function validateCombinations(data, schema, dataPointerPath) {
817        return this.validateAllOf(data, schema, dataPointerPath)
818                || this.validateAnyOf(data, schema, dataPointerPath)
819                || this.validateOneOf(data, schema, dataPointerPath)
820                || this.validateNot(data, schema, dataPointerPath)
821                || null;
822};
823
824ValidatorContext.prototype.validateAllOf = function validateAllOf(data, schema, dataPointerPath) {
825        if (schema.allOf === undefined) {
826                return null;
827        }
828        var error;
829        for (var i = 0; i < schema.allOf.length; i++) {
830                var subSchema = schema.allOf[i];
831                if (error = this.validateAll(data, subSchema, [], ["allOf", i], dataPointerPath)) {
832                        return error;
833                }
834        }
835        return null;
836};
837
838ValidatorContext.prototype.validateAnyOf = function validateAnyOf(data, schema, dataPointerPath) {
839        if (schema.anyOf === undefined) {
840                return null;
841        }
842        var errors = [];
843        var startErrorCount = this.errors.length;
844        var oldUnknownPropertyPaths, oldKnownPropertyPaths;
845        if (this.trackUnknownProperties) {
846                oldUnknownPropertyPaths = this.unknownPropertyPaths;
847                oldKnownPropertyPaths = this.knownPropertyPaths;
848        }
849        var errorAtEnd = true;
850        for (var i = 0; i < schema.anyOf.length; i++) {
851                if (this.trackUnknownProperties) {
852                        this.unknownPropertyPaths = {};
853                        this.knownPropertyPaths = {};
854                }
855                var subSchema = schema.anyOf[i];
856
857                var errorCount = this.errors.length;
858                var error = this.validateAll(data, subSchema, [], ["anyOf", i], dataPointerPath);
859
860                if (error === null && errorCount === this.errors.length) {
861                        this.errors = this.errors.slice(0, startErrorCount);
862
863                        if (this.trackUnknownProperties) {
864                                for (var knownKey in this.knownPropertyPaths) {
865                                        oldKnownPropertyPaths[knownKey] = true;
866                                        delete oldUnknownPropertyPaths[knownKey];
867                                }
868                                for (var unknownKey in this.unknownPropertyPaths) {
869                                        if (!oldKnownPropertyPaths[unknownKey]) {
870                                                oldUnknownPropertyPaths[unknownKey] = true;
871                                        }
872                                }
873                                // We need to continue looping so we catch all the property definitions, but we don't want to return an error
874                                errorAtEnd = false;
875                                continue;
876                        }
877
878                        return null;
879                }
880                if (error) {
881                        errors.push(error.prefixWith(null, "" + i).prefixWith(null, "anyOf"));
882                }
883        }
884        if (this.trackUnknownProperties) {
885                this.unknownPropertyPaths = oldUnknownPropertyPaths;
886                this.knownPropertyPaths = oldKnownPropertyPaths;
887        }
888        if (errorAtEnd) {
889                errors = errors.concat(this.errors.slice(startErrorCount));
890                this.errors = this.errors.slice(0, startErrorCount);
891                return this.createError(ErrorCodes.ANY_OF_MISSING, {}, "", "/anyOf", errors);
892        }
893};
894
895ValidatorContext.prototype.validateOneOf = function validateOneOf(data, schema, dataPointerPath) {
896        if (schema.oneOf === undefined) {
897                return null;
898        }
899        var validIndex = null;
900        var errors = [];
901        var startErrorCount = this.errors.length;
902        var oldUnknownPropertyPaths, oldKnownPropertyPaths;
903        if (this.trackUnknownProperties) {
904                oldUnknownPropertyPaths = this.unknownPropertyPaths;
905                oldKnownPropertyPaths = this.knownPropertyPaths;
906        }
907        for (var i = 0; i < schema.oneOf.length; i++) {
908                if (this.trackUnknownProperties) {
909                        this.unknownPropertyPaths = {};
910                        this.knownPropertyPaths = {};
911                }
912                var subSchema = schema.oneOf[i];
913
914                var errorCount = this.errors.length;
915                var error = this.validateAll(data, subSchema, [], ["oneOf", i], dataPointerPath);
916
917                if (error === null && errorCount === this.errors.length) {
918                        if (validIndex === null) {
919                                validIndex = i;
920                        } else {
921                                this.errors = this.errors.slice(0, startErrorCount);
922                                return this.createError(ErrorCodes.ONE_OF_MULTIPLE, {index1: validIndex, index2: i}, "", "/oneOf");
923                        }
924                        if (this.trackUnknownProperties) {
925                                for (var knownKey in this.knownPropertyPaths) {
926                                        oldKnownPropertyPaths[knownKey] = true;
927                                        delete oldUnknownPropertyPaths[knownKey];
928                                }
929                                for (var unknownKey in this.unknownPropertyPaths) {
930                                        if (!oldKnownPropertyPaths[unknownKey]) {
931                                                oldUnknownPropertyPaths[unknownKey] = true;
932                                        }
933                                }
934                        }
935                } else if (error) {
936                        errors.push(error.prefixWith(null, "" + i).prefixWith(null, "oneOf"));
937                }
938        }
939        if (this.trackUnknownProperties) {
940                this.unknownPropertyPaths = oldUnknownPropertyPaths;
941                this.knownPropertyPaths = oldKnownPropertyPaths;
942        }
943        if (validIndex === null) {
944                errors = errors.concat(this.errors.slice(startErrorCount));
945                this.errors = this.errors.slice(0, startErrorCount);
946                return this.createError(ErrorCodes.ONE_OF_MISSING, {}, "", "/oneOf", errors);
947        } else {
948                this.errors = this.errors.slice(0, startErrorCount);
949        }
950        return null;
951};
952
953ValidatorContext.prototype.validateNot = function validateNot(data, schema, dataPointerPath) {
954        if (schema.not === undefined) {
955                return null;
956        }
957        var oldErrorCount = this.errors.length;
958        var oldUnknownPropertyPaths, oldKnownPropertyPaths;
959        if (this.trackUnknownProperties) {
960                oldUnknownPropertyPaths = this.unknownPropertyPaths;
961                oldKnownPropertyPaths = this.knownPropertyPaths;
962                this.unknownPropertyPaths = {};
963                this.knownPropertyPaths = {};
964        }
965        var error = this.validateAll(data, schema.not, null, null, dataPointerPath);
966        var notErrors = this.errors.slice(oldErrorCount);
967        this.errors = this.errors.slice(0, oldErrorCount);
968        if (this.trackUnknownProperties) {
969                this.unknownPropertyPaths = oldUnknownPropertyPaths;
970                this.knownPropertyPaths = oldKnownPropertyPaths;
971        }
972        if (error === null && notErrors.length === 0) {
973                return this.createError(ErrorCodes.NOT_PASSED, {}, "", "/not");
974        }
975        return null;
976};
977
978// parseURI() and resolveUrl() are from https://gist.github.com/1088850
979//   -  released as public domain by author ("Yaffle") - see comments on gist
980
981function parseURI(url) {
982        var m = String(url).replace(/^\s+|\s+$/g, '').match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);
983        // authority = '//' + user + ':' + pass '@' + hostname + ':' port
984        return (m ? {
985                href     : m[0] || '',
986                protocol : m[1] || '',
987                authority: m[2] || '',
988                host     : m[3] || '',
989                hostname : m[4] || '',
990                port     : m[5] || '',
991                pathname : m[6] || '',
992                search   : m[7] || '',
993                hash     : m[8] || ''
994        } : null);
995}
996
997function resolveUrl(base, href) {// RFC 3986
998
999        function removeDotSegments(input) {
1000                var output = [];
1001                input.replace(/^(\.\.?(\/|$))+/, '')
1002                        .replace(/\/(\.(\/|$))+/g, '/')
1003                        .replace(/\/\.\.$/, '/../')
1004                        .replace(/\/?[^\/]*/g, function (p) {
1005                                if (p === '/..') {
1006                                        output.pop();
1007                                } else {
1008                                        output.push(p);
1009                                }
1010                });
1011                return output.join('').replace(/^\//, input.charAt(0) === '/' ? '/' : '');
1012        }
1013
1014        href = parseURI(href || '');
1015        base = parseURI(base || '');
1016
1017        return !href || !base ? null : (href.protocol || base.protocol) +
1018                (href.protocol || href.authority ? href.authority : base.authority) +
1019                removeDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) +
1020                (href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) +
1021                href.hash;
1022}
1023
1024function getDocumentUri(uri) {
1025        return uri.split('#')[0];
1026}
1027function normSchema(schema, baseUri) {
1028        if (schema && typeof schema === "object") {
1029                if (baseUri === undefined) {
1030                        baseUri = schema.id;
1031                } else if (typeof schema.id === "string") {
1032                        baseUri = resolveUrl(baseUri, schema.id);
1033                        schema.id = baseUri;
1034                }
1035                if (Array.isArray(schema)) {
1036                        for (var i = 0; i < schema.length; i++) {
1037                                normSchema(schema[i], baseUri);
1038                        }
1039                } else if (typeof schema['$ref'] === "string") {
1040                        schema['$ref'] = resolveUrl(baseUri, schema['$ref']);
1041                } else {
1042                        for (var key in schema) {
1043                                if (key !== "enum") {
1044                                        normSchema(schema[key], baseUri);
1045                                }
1046                        }
1047                }
1048        }
1049}
1050
1051var ErrorCodes = {
1052        INVALID_TYPE: 0,
1053        ENUM_MISMATCH: 1,
1054        ANY_OF_MISSING: 10,
1055        ONE_OF_MISSING: 11,
1056        ONE_OF_MULTIPLE: 12,
1057        NOT_PASSED: 13,
1058        // Numeric errors
1059        NUMBER_MULTIPLE_OF: 100,
1060        NUMBER_MINIMUM: 101,
1061        NUMBER_MINIMUM_EXCLUSIVE: 102,
1062        NUMBER_MAXIMUM: 103,
1063        NUMBER_MAXIMUM_EXCLUSIVE: 104,
1064        // String errors
1065        STRING_LENGTH_SHORT: 200,
1066        STRING_LENGTH_LONG: 201,
1067        STRING_PATTERN: 202,
1068        // Object errors
1069        OBJECT_PROPERTIES_MINIMUM: 300,
1070        OBJECT_PROPERTIES_MAXIMUM: 301,
1071        OBJECT_REQUIRED: 302,
1072        OBJECT_ADDITIONAL_PROPERTIES: 303,
1073        OBJECT_DEPENDENCY_KEY: 304,
1074        // Array errors
1075        ARRAY_LENGTH_SHORT: 400,
1076        ARRAY_LENGTH_LONG: 401,
1077        ARRAY_UNIQUE: 402,
1078        ARRAY_ADDITIONAL_ITEMS: 403,
1079        // Format errors
1080        FORMAT_CUSTOM: 500,
1081        // Schema structure
1082        CIRCULAR_REFERENCE: 600,
1083        // Non-standard validation options
1084        UNKNOWN_PROPERTY: 1000
1085};
1086var ErrorMessagesDefault = {
1087        INVALID_TYPE: "invalid type: {type} (expected {expected})",
1088        ENUM_MISMATCH: "No enum match for: {value}",
1089        ANY_OF_MISSING: "Data does not match any schemas from \"anyOf\"",
1090        ONE_OF_MISSING: "Data does not match any schemas from \"oneOf\"",
1091        ONE_OF_MULTIPLE: "Data is valid against more than one schema from \"oneOf\": indices {index1} and {index2}",
1092        NOT_PASSED: "Data matches schema from \"not\"",
1093        // Numeric errors
1094        NUMBER_MULTIPLE_OF: "Value {value} is not a multiple of {multipleOf}",
1095        NUMBER_MINIMUM: "Value {value} is less than minimum {minimum}",
1096        NUMBER_MINIMUM_EXCLUSIVE: "Value {value} is equal to exclusive minimum {minimum}",
1097        NUMBER_MAXIMUM: "Value {value} is greater than maximum {maximum}",
1098        NUMBER_MAXIMUM_EXCLUSIVE: "Value {value} is equal to exclusive maximum {maximum}",
1099        // String errors
1100        STRING_LENGTH_SHORT: "String is too short ({length} chars), minimum {minimum}",
1101        STRING_LENGTH_LONG: "String is too long ({length} chars), maximum {maximum}",
1102        STRING_PATTERN: "String does not match pattern: {pattern}",
1103        // Object errors
1104        OBJECT_PROPERTIES_MINIMUM: "Too few properties defined ({propertyCount}), minimum {minimum}",
1105        OBJECT_PROPERTIES_MAXIMUM: "Too many properties defined ({propertyCount}), maximum {maximum}",
1106        OBJECT_REQUIRED: "Missing required property: {key}",
1107        OBJECT_ADDITIONAL_PROPERTIES: "Additional properties not allowed",
1108        OBJECT_DEPENDENCY_KEY: "Dependency failed - key must exist: {missing} (due to key: {key})",
1109        // Array errors
1110        ARRAY_LENGTH_SHORT: "Array is too short ({length}), minimum {minimum}",
1111        ARRAY_LENGTH_LONG: "Array is too long ({length}), maximum {maximum}",
1112        ARRAY_UNIQUE: "Array items are not unique (indices {match1} and {match2})",
1113        ARRAY_ADDITIONAL_ITEMS: "Additional items not allowed",
1114        // Format errors
1115        FORMAT_CUSTOM: "Format validation failed ({message})",
1116        // Schema structure
1117        CIRCULAR_REFERENCE: "Circular $refs: {urls}",
1118        // Non-standard validation options
1119        UNKNOWN_PROPERTY: "Unknown property (not in schema)"
1120};
1121
1122function ValidationError(code, message, dataPath, schemaPath, subErrors) {
1123        Error.call(this);
1124        if (code === undefined) {
1125                throw new Error ("No code supplied for error: "+ message);
1126        }
1127        this.message = message;
1128        this.code = code;
1129        this.dataPath = dataPath || "";
1130        this.schemaPath = schemaPath || "";
1131        this.subErrors = subErrors || null;
1132
1133        var err = new Error(this.message);
1134        this.stack = err.stack || err.stacktrace;
1135        if (!this.stack) {
1136                try {
1137                        throw err;
1138                }
1139                catch(err) {
1140                        this.stack = err.stack || err.stacktrace;
1141                }
1142        }
1143}
1144ValidationError.prototype = Object.create(Error.prototype);
1145ValidationError.prototype.constructor = ValidationError;
1146ValidationError.prototype.name = 'ValidationError';
1147
1148ValidationError.prototype.prefixWith = function (dataPrefix, schemaPrefix) {
1149        if (dataPrefix !== null) {
1150                dataPrefix = dataPrefix.replace(/~/g, "~0").replace(/\//g, "~1");
1151                this.dataPath = "/" + dataPrefix + this.dataPath;
1152        }
1153        if (schemaPrefix !== null) {
1154                schemaPrefix = schemaPrefix.replace(/~/g, "~0").replace(/\//g, "~1");
1155                this.schemaPath = "/" + schemaPrefix + this.schemaPath;
1156        }
1157        if (this.subErrors !== null) {
1158                for (var i = 0; i < this.subErrors.length; i++) {
1159                        this.subErrors[i].prefixWith(dataPrefix, schemaPrefix);
1160                }
1161        }
1162        return this;
1163};
1164
1165function isTrustedUrl(baseUrl, testUrl) {
1166        if(testUrl.substring(0, baseUrl.length) === baseUrl){
1167                var remainder = testUrl.substring(baseUrl.length);
1168                if ((testUrl.length > 0 && testUrl.charAt(baseUrl.length - 1) === "/")
1169                        || remainder.charAt(0) === "#"
1170                        || remainder.charAt(0) === "?") {
1171                        return true;
1172                }
1173        }
1174        return false;
1175}
1176
1177var languages = {};
1178function createApi(language) {
1179        var globalContext = new ValidatorContext();
1180        var currentLanguage = language || 'en';
1181        var api = {
1182                addFormat: function () {
1183                        globalContext.addFormat.apply(globalContext, arguments);
1184                },
1185                language: function (code) {
1186                        if (!code) {
1187                                return currentLanguage;
1188                        }
1189                        if (!languages[code]) {
1190                                code = code.split('-')[0]; // fall back to base language
1191                        }
1192                        if (languages[code]) {
1193                                currentLanguage = code;
1194                                return code; // so you can tell if fall-back has happened
1195                        }
1196                        return false;
1197                },
1198                addLanguage: function (code, messageMap) {
1199                        var key;
1200                        for (key in ErrorCodes) {
1201                                if (messageMap[key] && !messageMap[ErrorCodes[key]]) {
1202                                        messageMap[ErrorCodes[key]] = messageMap[key];
1203                                }
1204                        }
1205                        var rootCode = code.split('-')[0];
1206                        if (!languages[rootCode]) { // use for base language if not yet defined
1207                                languages[code] = messageMap;
1208                                languages[rootCode] = messageMap;
1209                        } else {
1210                                languages[code] = Object.create(languages[rootCode]);
1211                                for (key in messageMap) {
1212                                        if (typeof languages[rootCode][key] === 'undefined') {
1213                                                languages[rootCode][key] = messageMap[key];
1214                                        }
1215                                        languages[code][key] = messageMap[key];
1216                                }
1217                        }
1218                        return this;
1219                },
1220                freshApi: function (language) {
1221                        var result = createApi();
1222                        if (language) {
1223                                result.language(language);
1224                        }
1225                        return result;
1226                },
1227                validate: function (data, schema, checkRecursive, banUnknownProperties) {
1228                        var context = new ValidatorContext(globalContext, false, languages[currentLanguage], checkRecursive, banUnknownProperties);
1229                        if (typeof schema === "string") {
1230                                schema = {"$ref": schema};
1231                        }
1232                        context.addSchema("", schema);
1233                        var error = context.validateAll(data, schema, null, null, "");
1234                        if (!error && banUnknownProperties) {
1235                                error = context.banUnknownProperties();
1236                        }
1237                        this.error = error;
1238                        this.missing = context.missing;
1239                        this.valid = (error === null);
1240                        return this.valid;
1241                },
1242                validateResult: function () {
1243                        var result = {};
1244                        this.validate.apply(result, arguments);
1245                        return result;
1246                },
1247                validateMultiple: function (data, schema, checkRecursive, banUnknownProperties) {
1248                        var context = new ValidatorContext(globalContext, true, languages[currentLanguage], checkRecursive, banUnknownProperties);
1249                        if (typeof schema === "string") {
1250                                schema = {"$ref": schema};
1251                        }
1252                        context.addSchema("", schema);
1253                        context.validateAll(data, schema, null, null, "");
1254                        if (banUnknownProperties) {
1255                                context.banUnknownProperties();
1256                        }
1257                        var result = {};
1258                        result.errors = context.errors;
1259                        result.missing = context.missing;
1260                        result.valid = (result.errors.length === 0);
1261                        return result;
1262                },
1263                addSchema: function () {
1264                        return globalContext.addSchema.apply(globalContext, arguments);
1265                },
1266                getSchema: function () {
1267                        return globalContext.getSchema.apply(globalContext, arguments);
1268                },
1269                getSchemaMap: function () {
1270                        return globalContext.getSchemaMap.apply(globalContext, arguments);
1271                },
1272                getSchemaUris: function () {
1273                        return globalContext.getSchemaUris.apply(globalContext, arguments);
1274                },
1275                getMissingUris: function () {
1276                        return globalContext.getMissingUris.apply(globalContext, arguments);
1277                },
1278                dropSchemas: function () {
1279                        globalContext.dropSchemas.apply(globalContext, arguments);
1280                },
1281                reset: function () {
1282                        globalContext.reset();
1283                        this.error = null;
1284                        this.missing = [];
1285                        this.valid = true;
1286                },
1287                missing: [],
1288                error: null,
1289                valid: true,
1290                normSchema: normSchema,
1291                resolveUrl: resolveUrl,
1292                getDocumentUri: getDocumentUri,
1293                errorCodes: ErrorCodes
1294        };
1295        return api;
1296}
1297
1298var tv4 = createApi();
1299tv4.addLanguage('en-gb', ErrorMessagesDefault);
1300
1301//legacy property
1302tv4.tv4 = tv4;
1303
1304if (typeof module !== 'undefined' && module.exports){
1305        module.exports = tv4;
1306}
1307else {
1308        global.tv4 = tv4;
1309}
1310
1311})(this);
Note: See TracBrowser for help on using the repository browser.