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