json-schema.coffee | |
---|---|
is a json-schema validator with a twist: instead of just validating json documents, it will also cast the values of a document to fit a schema. This is very handy if you need to process form submissions where all data comes in form of strings. | |
UsageTo process a schema: | |
JsonErrors | class JsonErrors
constructor: ->
@base = []
@attr = {}
|
Add an error to a property. If the error is another JsonErrors object the errors from that object will be merged into the current error object. | add: (property, error) ->
return unless property || error
if not error?
error = property
property = null
if property
if error instanceof JsonErrors then @_mergeErrors(property, error) else @_addError(property, error)
else
@base.push error
|
Add an error with no property associated | addToBase: (error) -> @add(error) |
Get any errors on a property. Returns an array of errors | on: (property) -> if property then @attr[property] else @base |
Get any errors on the base property. Returns and array of errors. | onBase: -> @on() |
Returns an array of all errors: [[property, [errors]]] | all: ->
base = if @base.length then [["", @base]] else []
base.concat([key, err] for key, err of @attr) |
Return true if no errors have been added | isEmpty: -> @base.length == 0 && Object.keys(@attr).length == 0
_addError: (property, error) ->
@attr[property] ||= []
if error.length
@attr[property] = @attr[property].concat(error)
else
@attr[property].push error
_mergeErrors: (property, errors) ->
return if errors.isEmpty()
for [prop, err] in errors.all()
newProp = if prop then "#{property}.#{prop}" else property
@add(newProp, err) |
JsonProperty | |
The base class in the JsonSchema hierachy. | class JsonProperty
constructor: (@attr) -> |
Case a value to the type specified by this propery | cast: (val) -> val |
Return any errors for this property | errors: (val) ->
errors = new JsonErrors
errors.add("required") unless @validate "required", (r) -> !(r && typeof(val) == "undefined")
errors |
Process a value. Return an object with the keys valid, doc and errors | process: (val) ->
val = if val? then @cast(val) else @attr.default
errors = @errors(val)
valid: errors.isEmpty()
doc: val
errors: errors |
Helper method to perform a validtion if an attribute is present | validate: (attr, fn) -> if (attr of @attr) then fn.call(this, @attr[attr]) else true |
JsonString | |
A property of type "string" | class JsonString extends JsonProperty
cast: (val) ->
switch @attr.format
when "date", "date-time"
new Date(val)
else
val.toString()
errors: (val) ->
errors = super(val)
if val?
errors.add("minLength") unless @validate "minLength", (len) -> val.length >= len
errors.add("maxLength") unless @validate "maxLength", (len) -> val.length <= len
errors.add("pattern") unless @validate "pattern", (pat) -> new RegExp(pat).test(val)
errors.add("enum") unless @validate "enum", (opts) -> val in opts
errors.add("format") unless @validate "format", (format) -> @validFormat(format, val)
errors
validFormat: (format, val) ->
switch @attr.format
when "date"
/^\d\d\d\d-\d\d-\d\d$/.test(val)
when "date-time"
/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/.test(val)
else
true |
JsonNumber | |
A property of type "number" | class JsonNumber extends JsonProperty
cast: (val) ->
val = parseFloat(val)
if isNaN(val) then null else val
errors: (val) ->
errors = super(val)
if val?
errors.add("minimum") unless @validate "minimum", (min) -> if @attr.excludeMinimum then val > min else val >= min
errors.add("maximum") unless @validate "maximum", (max) -> if @attr.excludeMaximum then val < max else val <= max
errors.add("divisibleBy") unless @validate "divisibleBy", (div) -> val % div == 0
errors |
JsonInteger | |
A property of type "integer" | class JsonInteger extends JsonNumber
cast: (val) ->
val = parseInt(val, 10)
if isNaN(val) then null else val |
JsonArray | |
A property of class "array". If the array items are specified with a "$ref" ({items: {"$ref": "uri"}}) the JsonSchema.resolver will be used to return a schema object for the items. | class JsonArray extends JsonProperty
constructor: (@attr) ->
if @attr.items
ref = @attr.items["$ref"]
@itemSchema = if ref then JsonSchema.resolver(@attr.items["$ref"], this) else JsonProperty.for(@attr.items)
cast: (val) ->
cast = if @itemSchema then (v) => @itemSchema.cast(v) else (v) -> v
cast(item) for item in val
errors: (val) ->
errors = super(val)
if val?
errors.add("minItems") unless @validate "minItems", (min) -> val.length >= min
errors.add("maxItems") unless @validate "maxItems", (max) -> val.length <= max
if @itemSchema
errors.add("#{i}", @itemSchema.errors(item)) for item, i in val
errors |
JsonObject | |
A property of type "object". If the properties are specified with a "$ref" (properties: {"$ref" : "uri"}) the JsonSchema.resolver will be used to lookup a schema object used for cast, errors and process | class JsonObject extends JsonProperty
constructor: (@attr) ->
@properties = attr.properties
if @properties["$ref"]
@ref = JsonSchema.resolver(@properties["$ref"].replace(/#.+$/, ''), this)
cast: (val) ->
return @ref.cast(val) if @ref
obj = {}
for key, attrs of @properties
obj[key] = if val && (key of val) then JsonProperty.for(attrs).cast(val[key]) else attrs.default
obj
process: (val) ->
if @ref then @ref.process(val) else super(val)
errors: (val) ->
return super(val) unless val?
return @ref.errors(val) if @ref
errors = super(val)
for key, attrs of @properties
err = JsonProperty.for(attrs).errors(val && val[key])
errors.add(key, err)
errors |
Factory method for JsonProperties | JsonProperty.for = (attr) ->
type = attr.type || "any"
klass = {
"any" : JsonProperty
"string" : JsonString
"number" : JsonNumber
"integer" : JsonInteger
"array" : JsonArray
"object" : JsonObject
}[type]
throw "Bad Schema - Unknown property type #{type}" unless klass
new klass(attr) |
JsonSchema | |
The public interface to the JsonSchema processor. Requires the main schema to be of type "object" | class JsonSchema extends JsonObject
constructor: (attr) ->
throw "The main schema must be of type \"object\"" unless attr.type == "object"
super(attr) |
The JsonSchema.resolver | |
This function will be used to resolve any url used in "$ref" references. The function should return an object responding to cast, errors and process. | JsonSchema.resolver = (url) -> |
Override to resolve references | throw "No resolver defined for references" unless JsonSchema.resolver |
Export public interface | e = (exports? && exports) || (window? && window)
e.JsonSchema = JsonSchema
e.JsonErrors = JsonErrors
|