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

9976060bfb26220dd042340394d06b31?s=128

Mihael Konjević

June 03, 2014
Tweet

Transcript

  1. JSON Schema and APItizer @mihaelkonjevic

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

    format for defining the structure of JSON data • More info at http://json-schema.org
  3. 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”
  4. 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
  5. 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" } } } } } !
  6. Validation • Two steps validation: 1. Structural 2. Semantic (Business

    logic)
  7. Validation { "type" : "string", "minLength" : 6, "maxLength" :

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

    • Array • Object • Null • Boolean
  9. Types / String • maxLength • minLength • pattern (regex)

  10. 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).+$" }
  11. Types / Integer • minimum • maximum • multipleOf •

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

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

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

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

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

    the provided! schemas! ! { "oneOf" : [{ "type" : "string", "minLength" : 6 }, { "type" : "integer", "minimum" : 20, "maximum" : 100 }] }
  21. 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 }] }
  22. 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
  23. 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" } } } !
  24. APItizer JSON schema based mock objects and APIs http://www.github.com/retro/apitizer

  25. APItizer • Reuse JSON schema definitions to generate data •

    Simple mocking of API fixtures • Extendible and customizable
  26. 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));
  27. 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 })
  28. 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!') });
  29. 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++) } })() }));
  30. 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(); }) );
  31. 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)) }) })
  32. Questions? @mihaelkonjevic http://github.com/retro/apitizer