Share
Embed
JSON Schema and APItizer
Slides from my JavaScript Zagreb talk about the JSON schema and APItizer
Mihael Konjević
June 03, 2014
Transcript
32 slides
-
JSON schema • IETF draft - v4 • JSON based
format for defining the structure of JSON data • More info at http://json-schema.org -
What is a schema? • XML Schema, Relax NG •
Set of constraints that have to be satisfied to consider an object valid! • Declarative format for “describing the structure of other data” -
Why? { "name": "George Washington", "birthday": "February 22, 1732", "address":
"Mount Vernon, Virginia, United States" } ! ! ! ! ! { "first_name": "George", "last_name": "Washington", "birthday": "22-02-1732", "address": { "street_address": "3200 Mount Vernon Memorial Highway", "city": "Mount Vernon", "state": "Virginia", "country": "United States" } } VS -
Schema { "type": "object", "properties": { "first_name": { "type": "string"
}, "last_name": { "type": "string" }, "birthday": { "type": "string", "format": "date-time" }, "address": { "type": "object", "properties": { "street_address": { "type": "string" }, "city": { "type": "string" }, "state": { "type": "string" }, "country": { "type" : "string" } } } } } ! -
Validation { "type" : "string", "minLength" : 6, "maxLength" :
255 } password === passwordConfirmation Structural Semantic ✓ × -
Types • String • Integer • Number (integer or decimal)
• Array • Object • Null • Boolean -
Types / String Password: ! • at least 1 uppercase
letter • at least 1 number • at least 8 chars long ! { "type" : "string", "minLength" : 8, "maxLength" : 255, "pattern" : "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$" } -
Types / Integer • minimum • maximum • multipleOf •
exclusiveMinimum • exclusiveMaximum -
Types / Integer Integer between 0 - 99 that is
a multiple of 3 ! { "type" : “integer”, "minimum" : 0, "maximum" : 100, "exclusiveMaximum" : true, "multipleOf" : 3 } -
Types / Object • properties • maxProperties • minProperties •
required • additionalProperties (boolean or JSON schema object) • patternProperties -
Types / Object User object: ! • id is an
integer • username is a string • password is a string • username and password are required { "type" : "object", "properties" : { "id" : { "type" : "integer" }, "username" : { "type" : "string" }, "password" : { "type" : "string" } }, "required" : ["username", "password"] } -
Types / Array • items (array or JSON schema object)
• minItems • maxItems • additionalItems (boolean or JSON schema object) -
Types / Array Array of user objects: ! • id
is an integer • username is a string • password is a string • username and password are required { "type" : "array", "items" : { "type" : "object", "properties" : { "id" : { "type" : "integer", }, "username" : { "type" : "string", }, "password" : { "type" : "string", } }, "required" : ["username", "password"] } } -
Types / Array (Tuple) HTTP error response ! • 1st
element is an integer • 2nd element is a string { "type" : "array", "items" : [{ "type" : "integer" }, { "type" : "string" }] } ! // [404, "Not Found"] -
anyOf Data is valid if it matches any of the
provided schemas! ! { "anyOf" : [{ "type" : "string", "minLength" : 6 }, { "type" : "string", "maxLength" : 255 }, { "type" : "integer", "minimum" : 20, "maximum" : 100 }] } -
allOf Data is valid if it matches all of the
provided schemas! ! { "allOf" : [{ "type" : "string", "minLength" : 6 }, { "type" : "string", "maxLength" : 255 }] } -
oneOf Data is valid if it matches exactly one of
the provided! schemas! ! { "oneOf" : [{ "type" : "string", "minLength" : 6 }, { "type" : "integer", "minimum" : 20, "maximum" : 100 }] } -
not Data is valid if it doesn’t match any of
the provided! schemas! ! { "not" : [{ "type" : "string", "minLength" : 6 }, { "type" : "string", "maxLength" : 255 }, { "type" : "integer", "minimum" : 20, "maximum" : 100 }] } -
Schema references • Reference part of the same schema, another
schema or part of the another schema • Uses a simple JSON path expression: schemaName#path/to/subschema -
Schema references { "definitions": { "address": { "type": "object", "properties":
{ "street_address": { "type": "string" }, "city": { "type": "string" }, "state": { "type": "string" } }, "required": ["street_address", "city", "state"] } }, ! "type": "object", ! "properties": { "billing_address": { "$ref": "#/definitions/address" }, "shipping_address": { "$ref": "#/definitions/address" } } } ! -
APItizer • Reuse JSON schema definitions to generate data •
Simple mocking of API fixtures • Extendible and customizable -
Example var userSchema = { type : "object", properties :
{ id : { type : "integer" }, username : { type : "string" }, password : { type : "string" } } }; ! apitizer.addSchema('user', userSchema); apitizer.fixture.resource('/users', apitizer.schemaStore('user', 10)); -
Example $.get('/users').then(function(users){ // 10 users returned }) ! $.get('/users/1').then(function(user){ //
user with id 1 returned }) ! $.post('/users', { username : 'retro', password : '1337' }).then(function(user){ // user is created and returned }) $.ajax('/users/1', { type : 'put', data : { username : 'retro', password : '1337' } }).then(function(user){ // user is updated and returned }) ! $.ajax('/users/1', { type : 'delete' }).then(function(user){ // user is destroyed }) -
Custom API endpoints var userSchema = { type : "object",
properties : { id : { type : "integer" }, username : { type : "string" }, password : { type : "string" } } }; ! apitizer.addSchema('user', userSchema); var store = apitizer.schemaStore('user', 10); ! apitizer.fixture('POST /login', function(urlParams, params){ var users = userStore.db(params) // Search the data in the store's database if(users.count() === 0){ throw {errors: ['Wrong credentials'], status: 401} } else { return users.first(); } }) $.post('/login', { username : 'retro', password : 1338 }).then(function(user){ alert('You logged in!') }, function(error){ alert('Wrong credentials!') }); -
Overriding generators var userSchema = { type : "object", properties
: { id : { type : "integer" }, username : { type : "string" }, password : { type : "string" } } }; ! apitizer.addSchema('user', userSchema); apitizer.fixture.resource('/users', apitizer.schemaStore('user', 10, { id : apitizer.types.autoincrement(), username : (function(){ var i = 1; return function(){ return "User " + (i++) } })() })); -
Related objects var userSchema = { type : "object", properties
: { id : { type : "integer" }, username : { type : "string" }, password : { type : "string" } } }; ! var articleSchema = { properties : { title : { type : "string" }, user : { $ref : "user" } } } apitizer.addSchema('user', userSchema); apitizer.addSchema('article', articleSchema); ! var userStore = apitizer.schemaStore('user', 10); ! apitizer.fixture.resource('/user', userStore); ! apitizer.fixture.resource('/article', apitizer.schemaStore('article', 10, { user : userStore.one(); }) ); -
Validation example module('apiTest', { setup : function(){ var userSchema =
{ type : "object", properties : { id : { type : "integer" }, username : { type : "string" }, password : { type : "string" } } }; ! apitizer.addSchema('user', userSchema); } }) ! asyncTest('Getting a user from the API', function(){ expect(1); $.get('/users/1').then(function(user){ ok(apitizer.validateWithSchema('user', user)) }) })