Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JSON Schema and APItizer

JSON Schema and APItizer

Slides from my JavaScript Zagreb talk about the JSON schema and APItizer

Mihael Konjević

June 03, 2014
Tweet

More Decks by Mihael Konjević

Other Decks in Programming

Transcript

  1. JSON schema • IETF draft - v4 • JSON based

    format for defining the structure of JSON data • More info at http://json-schema.org
  2. 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”
  3. 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
  4. 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" } } } } } !
  5. Validation { "type" : "string", "minLength" : 6, "maxLength" :

    255 } password === passwordConfirmation Structural Semantic ✓ ×
  6. Types • String • Integer • Number (integer or decimal)

    • Array • Object • Null • Boolean
  7. 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).+$" }
  8. Types / Integer • minimum • maximum • multipleOf •

    exclusiveMinimum • exclusiveMaximum
  9. Types / Integer Integer between 0 - 99 that is

    a multiple of 3 ! { "type" : “integer”, "minimum" : 0, "maximum" : 100, "exclusiveMaximum" : true, "multipleOf" : 3 }
  10. Types / Object • properties • maxProperties • minProperties •

    required • additionalProperties (boolean or JSON schema object) • patternProperties
  11. 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"] }
  12. Types / Array • items (array or JSON schema object)

    • minItems • maxItems • additionalItems (boolean or JSON schema object)
  13. 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"] } }
  14. 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"]
  15. 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 }] }
  16. allOf Data is valid if it matches all of the

    provided schemas! ! { "allOf" : [{ "type" : "string", "minLength" : 6 }, { "type" : "string", "maxLength" : 255 }] }
  17. oneOf Data is valid if it matches exactly one of

    the provided! schemas! ! { "oneOf" : [{ "type" : "string", "minLength" : 6 }, { "type" : "integer", "minimum" : 20, "maximum" : 100 }] }
  18. 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 }] }
  19. 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
  20. 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" } } } !
  21. APItizer • Reuse JSON schema definitions to generate data •

    Simple mocking of API fixtures • Extendible and customizable
  22. 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));
  23. 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 })
  24. 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!') });
  25. 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++) } })() }));
  26. 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(); }) );
  27. 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)) }) })