defmoduleJsonSchemado@moduledoc~S""" A service which validates objects according to types defined in `schema.json`."""@doc~S""" Validates an object by type. Returns a list of {msg, [columns]} tuples describing any validation errors, or [] if validation succeeded."""defvalidate(server\\:json_schema,object,type)doGenServer.call(server,{:validate,object,type})end@doc~S""" Returns true if the object is valid according to the specified type, false otherwise."""defvalid?(server\\:json_schema,object,type)do[]==validate(server,object,type)end@doc~S""" Converts the output of `validate/3` into a JSON-compatible structure, a list of error messages."""deferrors_to_json(errors)doerrors|>Enum.map(fn({msg,_cols})->msgend)enduseGenServerdefinit(_)doschema=File.read!(Application.app_dir(:myapp)<>"/priv/schema.json")|>Poison.decode!|>ExJsonSchema.Schema.resolve{:ok,schema}enddefhandle_call({:validate,object,type},_from,schema)doerrors=get_validation_errors(object,type,schema)|>transform_errors{:reply,errors,schema}enddefpget_validation_errors(object,type,schema)dotype_string=type|>to_stringtype_schema=schema.schema["definitions"][type_string]not_a_struct=caseobjectdo%{__struct__: _}->Map.from_struct(object)_->objectendstring_keyed_object=ensure_key_strings(not_a_struct)## validate throws a BadMapError on certain kinds of invalid## input; absorb it (TODO fix ExJsonSchema upstream)trydoExJsonSchema.Validator.validate(schema,type_schema,string_keyed_object)rescue_->[{"Failed validation",[]}]endend@doc~S""" Makes sure that all the keys in the map are strings and not atoms. Works on nested data structures."""defpensure_key_strings(x)doconddois_mapx->Enum.reducex,%{},fn({k,v},acc)->Map.putacc,to_string(k),ensure_key_strings(v)endis_listx->Enum.map(x,fn(v)->ensure_key_strings(v)end)true->xendendend