1+ /**
2+ * JSONSchema Validator - Validates JavaScript objects using JSON Schemas
3+ * (http://www.json.com/json-schema-proposal/)
4+ *
5+ * Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com)
6+ * Licensed under the MIT (MIT-LICENSE.txt) license.
7+ To use the validator call JSONSchema.validate with an instance object and an optional schema object.
8+ If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
9+ that schema will be used to validate and the schema parameter is not necessary (if both exist,
10+ both validations will occur).
11+ The validate method will return an array of validation errors. If there are no errors, then an
12+ empty list will be returned. A validation error will have two properties:
13+ "property" which indicates which property had the error
14+ "message" which indicates what the error was
15+ */
16+
17+ // setup primitive classes to be JSON Schema types
18+ String . type = "string" ;
19+ Boolean . type = "boolean" ;
20+ Number . type = "number" ;
21+ exports . Integer = { type :"integer" } ;
22+ Object . type = "object" ;
23+ Array . type = "array" ;
24+ Date . type = "date" ;
25+
26+ exports . validate = function ( /*Any*/ instance , /*Object*/ schema ) {
27+ // Summary:
28+ // To use the validator call JSONSchema.validate with an instance object and an optional schema object.
29+ // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
30+ // that schema will be used to validate and the schema parameter is not necessary (if both exist,
31+ // both validations will occur).
32+ // The validate method will return an object with two properties:
33+ // valid: A boolean indicating if the instance is valid by the schema
34+ // errors: An array of validation errors. If there are no errors, then an
35+ // empty list will be returned. A validation error will have two properties:
36+ // property: which indicates which property had the error
37+ // message: which indicates what the error was
38+ //
39+ return validate ( instance , schema , false ) ;
40+ } ;
41+ exports . checkPropertyChange = function ( /*Any*/ value , /*Object*/ schema , /*String*/ property ) {
42+ // Summary:
43+ // The checkPropertyChange method will check to see if an value can legally be in property with the given schema
44+ // This is slightly different than the validate method in that it will fail if the schema is readonly and it will
45+ // not check for self-validation, it is assumed that the passed in value is already internally valid.
46+ // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for
47+ // information.
48+ //
49+ return validate ( value , schema , property || "property" ) ;
50+ } ;
51+ var validate = exports . _validate = function ( /*Any*/ instance , /*Object*/ schema , /*Boolean*/ _changing ) {
52+
53+ var errors = [ ] ;
54+ // validate a value against a property definition
55+ function checkProp ( value , schema , path , i ) {
56+ var l ;
57+ path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i ;
58+ function addError ( message ) {
59+ errors . push ( { property :path , message :message } ) ;
60+ }
61+
62+ if ( ( typeof schema != 'object' || schema instanceof Array ) && ( path || typeof schema != 'function' ) && ! ( schema && schema . type ) ) {
63+ if ( typeof schema == 'function' ) {
64+ if ( ! ( value instanceof schema ) ) {
65+ addError ( "is not an instance of the class/constructor " + schema . name ) ;
66+ }
67+ } else if ( schema ) {
68+ addError ( "Invalid schema/property definition " + schema ) ;
69+ }
70+ return null ;
71+ }
72+ if ( _changing && schema . readonly ) {
73+ addError ( "is a readonly field, it can not be changed" ) ;
74+ }
75+ if ( schema [ 'extends' ] ) { // if it extends another schema, it must pass that schema as well
76+ checkProp ( value , schema [ 'extends' ] , path , i ) ;
77+ }
78+ // validate a value against a type definition
79+ function checkType ( type , value ) {
80+ if ( type ) {
81+ if ( typeof type == 'string' && type != 'any' &&
82+ ( type == 'null' ? value !== null : typeof value != type ) &&
83+ ! ( value instanceof Array && type == 'array' ) &&
84+ ! ( value instanceof Date && type == 'date' ) &&
85+ ! ( type == 'integer' && value % 1 === 0 ) ) {
86+ return [ { property :path , message :( typeof value ) + " value found, but a " + type + " is required" } ] ;
87+ }
88+ if ( type instanceof Array ) {
89+ var unionErrors = [ ] ;
90+ for ( var j = 0 ; j < type . length ; j ++ ) { // a union type
91+ if ( ! ( unionErrors = checkType ( type [ j ] , value ) ) . length ) {
92+ break ;
93+ }
94+ }
95+ if ( unionErrors . length ) {
96+ return unionErrors ;
97+ }
98+ } else if ( typeof type == 'object' ) {
99+ var priorErrors = errors ;
100+ errors = [ ] ;
101+ checkProp ( value , type , path ) ;
102+ var theseErrors = errors ;
103+ errors = priorErrors ;
104+ return theseErrors ;
105+ }
106+ }
107+ return [ ] ;
108+ }
109+ if ( value === undefined ) {
110+ if ( ! schema . optional && ! schema . get ) {
111+ addError ( "is missing and it is not optional" ) ;
112+ }
113+ } else {
114+ errors = errors . concat ( checkType ( schema . type , value ) ) ;
115+ if ( schema . disallow && ! checkType ( schema . disallow , value ) . length ) {
116+ addError ( " disallowed value was matched" ) ;
117+ }
118+ if ( value !== null ) {
119+ if ( value instanceof Array ) {
120+ if ( schema . items ) {
121+ if ( schema . items instanceof Array ) {
122+ for ( i = 0 , l = value . length ; i < l ; i ++ ) {
123+ errors . concat ( checkProp ( value [ i ] , schema . items [ i ] , path , i ) ) ;
124+ }
125+ } else {
126+ for ( i = 0 , l = value . length ; i < l ; i ++ ) {
127+ errors . concat ( checkProp ( value [ i ] , schema . items , path , i ) ) ;
128+ }
129+ }
130+ }
131+ if ( schema . minItems && value . length < schema . minItems ) {
132+ addError ( "There must be a minimum of " + schema . minItems + " in the array" ) ;
133+ }
134+ if ( schema . maxItems && value . length > schema . maxItems ) {
135+ addError ( "There must be a maximum of " + schema . maxItems + " in the array" ) ;
136+ }
137+ } else if ( schema . properties || schema . additionalProperties ) {
138+ errors . concat ( checkObj ( value , schema . properties , path , schema . additionalProperties ) ) ;
139+ }
140+ if ( schema . pattern && typeof value == 'string' && ! value . match ( schema . pattern ) ) {
141+ addError ( "does not match the regex pattern " + schema . pattern ) ;
142+ }
143+ if ( schema . maxLength && typeof value == 'string' && value . length > schema . maxLength ) {
144+ addError ( "may only be " + schema . maxLength + " characters long" ) ;
145+ }
146+ if ( schema . minLength && typeof value == 'string' && value . length < schema . minLength ) {
147+ addError ( "must be at least " + schema . minLength + " characters long" ) ;
148+ }
149+ if ( typeof schema . minimum !== undefined && typeof value == typeof schema . minimum &&
150+ schema . minimum > value ) {
151+ addError ( "must have a minimum value of " + schema . minimum ) ;
152+ }
153+ if ( typeof schema . maximum !== undefined && typeof value == typeof schema . maximum &&
154+ schema . maximum < value ) {
155+ addError ( "must have a maximum value of " + schema . maximum ) ;
156+ }
157+ if ( schema [ 'enum' ] ) {
158+ var enumer = schema [ 'enum' ] ;
159+ l = enumer . length ;
160+ var found ;
161+ for ( var j = 0 ; j < l ; j ++ ) {
162+ if ( enumer [ j ] === value ) {
163+ found = 1 ;
164+ break ;
165+ }
166+ }
167+ if ( ! found ) {
168+ addError ( "does not have a value in the enumeration " + enumer . join ( ", " ) ) ;
169+ }
170+ }
171+ if ( typeof schema . maxDecimal == 'number' &&
172+ ( value . toString ( ) . match ( new RegExp ( "\\.[0-9]{" + ( schema . maxDecimal + 1 ) + ",}" ) ) ) ) {
173+ addError ( "may only have " + schema . maxDecimal + " digits of decimal places" ) ;
174+ }
175+ }
176+ }
177+ return null ;
178+ }
179+ // validate an object against a schema
180+ function checkObj ( instance , objTypeDef , path , additionalProp ) {
181+
182+ if ( typeof objTypeDef == 'object' ) {
183+ if ( typeof instance != 'object' || instance instanceof Array ) {
184+ errors . push ( { property :path , message :"an object is required" } ) ;
185+ }
186+
187+ for ( var i in objTypeDef ) {
188+ if ( objTypeDef . hasOwnProperty ( i ) && ! ( i . charAt ( 0 ) == '_' && i . charAt ( 1 ) == '_' ) ) {
189+ var value = instance [ i ] ;
190+ var propDef = objTypeDef [ i ] ;
191+ // set default
192+ if ( value === undefined && propDef [ "default" ] ) {
193+ value = instance [ i ] = propDef [ "default" ] ;
194+ }
195+ if ( exports . coerce ) {
196+ value = instance [ i ] = exports . coerce ( value , propDef ) ;
197+ }
198+ checkProp ( value , propDef , path , i ) ;
199+ }
200+ }
201+ }
202+ for ( i in instance ) {
203+ if ( instance . hasOwnProperty ( i ) && ! ( i . charAt ( 0 ) == '_' && i . charAt ( 1 ) == '_' ) && objTypeDef && ! objTypeDef [ i ] && additionalProp === false ) {
204+ errors . push ( { property :path , message :( typeof value ) + "The property " + i +
205+ " is not defined in the schema and the schema does not allow additional properties" } ) ;
206+ }
207+ var requires = objTypeDef && objTypeDef [ i ] && objTypeDef [ i ] . requires ;
208+ if ( requires && ! ( requires in instance ) ) {
209+ errors . push ( { property :path , message :"the presence of the property " + i + " requires that " + requires + " also be present" } ) ;
210+ }
211+ value = instance [ i ] ;
212+ if ( additionalProp && ( ! ( objTypeDef && typeof objTypeDef == 'object' ) || ! ( i in objTypeDef ) ) ) {
213+ if ( exports . coerce ) {
214+ value = instance [ i ] = exports . coerce ( value , additionalProp ) ;
215+ }
216+ checkProp ( value , additionalProp , path , i ) ;
217+ }
218+ if ( ! _changing && value && value . $schema ) {
219+ errors = errors . concat ( checkProp ( value , value . $schema , path , i ) ) ;
220+ }
221+ }
222+ return errors ;
223+ }
224+ if ( schema ) {
225+ checkProp ( instance , schema , '' , _changing || '' ) ;
226+ }
227+ if ( ! _changing && instance && instance . $schema ) {
228+ checkProp ( instance , instance . $schema , '' , '' ) ;
229+ }
230+ return { valid :! errors . length , errors :errors } ;
231+ } ;
232+ exports . mustBeValid = function ( result ) {
233+ // summary:
234+ // This checks to ensure that the result is valid and will throw an appropriate error message if it is not
235+ // result: the result returned from checkPropertyChange or validate
236+ if ( ! result . valid ) {
237+ throw new TypeError ( result . errors . map ( function ( error ) { return "for property " + error . property + ': ' + error . message ; } ) . join ( ", \n" ) ) ;
238+ }
239+ }
240+ /* will add this later
241+ newFromSchema : function() {
242+ }
243+ */
244+
245+ exports . cacheLinks = true ;
246+ exports . getLink = function ( relation , instance , schema ) {
247+ // gets the URI of the link for the given relation based on the instance and schema
248+ // for example:
249+ // getLink(
250+ // "brother",
251+ // {"brother_id":33},
252+ // {links:[{rel:"brother", href:"Brother/{brother_id}" }] }) ->
253+ // "Brother/33"
254+ var links = schema . __linkTemplates ;
255+ if ( ! links ) {
256+ links = { } ;
257+ var schemaLinks = schema . links ;
258+ if ( schemaLinks && schemaLinks instanceof Array ) {
259+ schemaLinks . forEach ( function ( link ) {
260+ /* // TODO: allow for multiple same-name relations
261+ if(links[link.rel]){
262+ if(!(links[link.rel] instanceof Array)){
263+ links[link.rel] = [links[link.rel]];
264+ }
265+ }*/
266+ links [ link . rel ] = link . href ;
267+ } ) ;
268+ }
269+ if ( exports . cacheLinks ) {
270+ schema . __linkTemplates = links ;
271+ }
272+ }
273+ var linkTemplate = links [ relation ] ;
274+ return linkTemplate && exports . substitute ( linkTemplate , instance ) ;
275+ } ;
276+
277+ exports . substitute = function ( linkTemplate , instance ) {
278+ return linkTemplate . replace ( / \{ ( [ ^ \} ] * ) \} / g, function ( t , property ) {
279+ var value = instance [ decodeURIComponent ( property ) ] ;
280+ if ( value instanceof Array ) {
281+ // the value is an array, it should produce a URI like /Table/(4,5,8) and store.get() should handle that as an array of values
282+ return '(' + value . join ( ',' ) + ')' ;
283+ }
284+ return value ;
285+ } ) ;
286+ } ;
0 commit comments