respectify

Restify specification tool

Respectify

Route specification for restify

With npm

npm install respectify
var restify = require('restify')
  , Respectify = require('respectify')
 
// Create the restify server 
var server = restify.createServer()
 
server.get({
  path: '/things/:id'
, version: '1.0.0'
, params: {
    id: 'number'
  , another: {
      dataTypes: ['number', 'string']
    , default: 20
    }
  }
}, function(reqresnext) {
  // ... 
})
 
// Create the respectify instance with the new server 
var respect = new Respectify(server, options)
 
// Add the respectify middleware to validate routes 
server.use(respect.middleware)

A params object must be added to the route options in order for Respectify to parse and / or validate the route.

  • dataTypes - one of the following: number, date, string, array, object, boolean
  • default - the default value to use if the param was not sent, functions can be used
  • dataValues - array of specific values considered valid
  • min - minimum value allowed (number type only)
  • max - maximum value allowed (number type only)
  • notes - array of string information about the param (useful for documentation)
  • description - parameter description (useful for documentation)
  • transform - param value transformation function (synchronous, optional)
  • validate - param value validation function (synchronous, optional)
server.get({
  path: '/users/:id'
, version: '1.0.0'
, params: {
    one: 'boolean'
  , two: ['date', 'number']
  , three: {
      dataTypes: ['string', 'number']
    , default: 20
    , dataValues: [10, 'a', 30]
    , transformfunction(valreqparam) {
        if (val === 'a') return 20
        return val
      }
    }
  , pagesize: {
      dataTypes: 'number'
    , desc: 'page size'
    , default: 50
    , min: 0
    , max: 250
    }
  , random: {
      dataTypes: 'number'
    , defaultfunction() { return Math.round(Math.random()) }
    , validatefunction(valreqparam) {
        if (val === 10) {
          return new Error('i hate that number')
        }
        return false
      }
    }
  }
}, function(reqresnext) {
  // ... 
})

Parameter specific validation method, should return false if valid, or any Error instance for failures. Synchronous.

Note: This will only be called if the parameter has a value

  • value - incoming request value, after initial validation
  • req - restify request object
  • param - normalized parameter reference

Example:

server.get({path: '/users'
, versions: ['2.0.0']
, flags: 'i'
, params: {
    pagesize: {
      dataTypes: 'number'
    , description: 'page size'
    , default: 50
    , min: 1
    , max: 1000
    , validatefunction(valreq) {
        var ttl = req.params.ttl;
        if (val && +val > 200 && (!ttl || +ttl < 1800)) {
          var msg = ''
            + 'Invalid param `pagesize`, value must be under `200`'
            + ' if no `ttl` was provided, or if `ttl` is less than `1800`'
            ;
          return new restify.InvalidArgumentError(msg);
        }
        return false;
      }
    , notes: [
        'if a value of over `200` is used, a `ttl` value of `1800` or more must also be used'
      ]
    }
  }
})

Parameter specific transform method. This value returned from this method will always be used, even if undefined returned. Synchronous.

Note: This will only be called if the parameter has a value

  • value - incoming request value, after initial validation
  • req - restify request object
  • param - normalized parameter reference

Example:

SQL_SORT_MAP = {
  asc:        'ASC'
, acending:   'ASC'
, desc:       'DESC'
, descending: 'DESC'
};
 
server.get({path: '/users'
, versions: ['2.0.0']
, flags: 'i'
, params: {
    sortby: {
      dataTypes: 'string'
    , dataValues: Object.keys(SQL_SORT_MAP)
    , description: 'sorting order for `sortstat` parameter'
    , default: 'desc'
    , transformfunction(val) {
        return SQL_SORT_MAP[val];
      }
    }
  }
})

If array is used as a dataType, the elements of array will be re-validated against the remaining dataTypes.

For example, a parameter defined with both array and number is interpreted to be either a single number value, or an array of numbers.

{
  ids: {
    dataTypes: ['array', 'number']
  }
}
// http://localhost/users?id=1 
req.params.id === 1
 
// http://localhost/users?id=1,2,3 
req.params.id === [1, 2, 3]
 
// http://localhost/users?id=10&id=40 
req.params.id === [10, 40]

If using object as a dataType, a nested params definition may be added to specify property values. When using required on a nested parameter definition, only the current object level is looked at.

{
  user: {
    dataTypes: ['object']
  , params: {
      id: 'number'
    , email: {
        dataTypes: ['string']
 
        // This will only be required if the `user` param is sent. 
      , required: true
      }
    , name: {
        dataTypes: ['object']
      , params: {
          first: {
            dataTypes: 'string'
          }
        , last: {
            dataTypes: 'string'
 
            // This parameter is only required if `name` is sent, because  
            // the parent parameter is not required.  
          , required: true
          }
        }
      }
    }
  }
}

By default, respectify will only use what has been populated in the req.params object from restify, which should cover most use cases. If you are using the restify queryParser and / or bodyParser, restify will map these to the req.params object (unless you specified mapParams: false)

You can specify param targeting by passing a paramTarget option to the respectify constructor, or by adding it as a route property, valid options are query, params, and body.

Note: The original target object of the request, params, body, and query may have its properties overwitten or deleted by the respectify.middleware. If you need the original values, you will need to use a middleware function to preserve them.

// Shallow clone the `param` object to preserve original values 
server.use(function(reqresnext) {
  req.__params = {}
  for (var prop in req.params) {
    req.__params[prop] = req.params[prop]
  }
})
server.use(respectify.middleware)

Note: These methods are currently somewhat disorganized, as this module evolves, these methods and their responses will be normalized and hopefully easier to use.

Respectify constructor

Alias: [Respectify.factory(server)]

  • server - restify server instance
  • options - respectify options
    • routeProperties - array of route properties to store (optional, default ['description'])
    • paramProperties - array of route parameter properties to store (optional, default ['description'])
    • paramTarget - target for parameter extraction (optional, default params)

Example:

var server = restify.createServer()
var respect = new Respectify(server)

Route middleware to add parameter validation, this will filter all properties of req.params according to the param definition of the route. Parameters received that have not been defined will be removed.

Note: The middleware should come after restify.queryParser and / or restify.bodyParser

  • options - middleware options (optional)
    • mapParams - map all parsed query and body properties to the params object (optional, default true)
    • filterParams - remove all unspecified input params (optional, default true)

Example:

server.use(restify.queryParser())
server.use(respect.middleware)
 
server.get({
  path: '/'
, version: '1.0.0'
, params: {
    foo: {
      dataTypes: 'string'
    , default: 'bar'
    }
  }
}, function(reqresnext) {
  res.send(200)
})
 
server.get({
  path: '/'
, version: '2.0.0'
, params: {}
}, function(reqresnext) {
  res.send(200)
})

Get the specification of a given restify route object. The route itself can be retrieved using restify's router.find() method or the instance.findRoutes() method above.

See route-information for example usage.

  • route - restify route object
server.router.find(req, res, function(errrouteparams) {
  var spec = instance.getSpecByRoute(route)  
})

Find parameter defaults for a given route

  • path - route pathname as defined for restify
  • version - load only supplied version (optional, default latest version)
var defaults = instance.getDefaults('/', '1.0.0')
{
  "foo": "bar"
}
  • version - load only supplied version (optional, default latest version)
var specs = instance.loadSpecs('1.0.0')
[
  {
    "route": "/",
    "parameters": [
      {
        "name": "foo",
        "required": false,
        "paramType": "querystring",
        "dataTypes": [
          "string"
        ],
        "default": "bar"
      }
    ],
    "method": "GET",
    "versions": [
      "1.0.0"
    ]
  }
]

Find restify route objects, mainly used internally.

  • path - route pathname as defined for restify
  • version - load only supplied version (optional, default latest version)
var routes = instance.findRoutes('/', '2.0.0')
[
  {
    "name": "get200",
    "method": "GET",
    "path": {
      "restifyParams": []
    },
    "spec": {
      "path": "/",
      "version": "2.0.0",
      "params": {},
      "method": "GET",
      "versions": [
        "2.0.0"
      ],
      "name": "get200"
    },
    "types": [],
    "versions": [
      "2.0.0"
    ]
  }
]

Find restify route objects, mainly used internally.

Alias: [find]

  • path - route pathname as defined for restify
  • version - load only supplied version (optional, default latest version)
var specs = instance.findSpecs('/', '2.0.0')
[
  {
    "route": "/",
    "parameters": [
      {
        "name": "foo",
        "required": false,
        "paramType": "querystring",
        "dataTypes": [
          "string"
        ],
        "default": "bar"
      }
    ],
    "method": "GET",
    "versions": [
      "1.0.0"
    ]
  }
]

Returns an array of all routable versions found.

Get a copy of the parameters for a given route as a key value mapping.

  • path - route pathname as defined for restify
  • version - load only supplied version (optional, default latest version)
var params = instance.getRouteParams('/', '2.0.0')
{
  "foo": {
      "name": "foo",
      "required": false,
      "paramType": "querystring",
      "dataTypes": [
        "string"
      ],
      "default": "bar"
    }
  }
}

Get a merged copy of the route parameters found with parameters given. This is primarily a shortcut method for creating new routes while building upon the previously defined parameters.

  • path - route pathname as defined for restify
  • version - load only supplied version (optional, default latest version)
  • params - any number of parameter objects to merge
var params = instance.getMergedParams('/', '2.0.0', {
  pagesize: {
    dataTypes: 'number'
  , description: 'Page size'
  }
})
{
  "foo": {
      "name": "foo",
      "required": false,
      "paramType": "querystring",
      "dataTypes": [
        "string"
      ],
      "default": "bar"
    }
  },
  "pagesize"{
      "dataTypes": "number",
      "description": "Page size"
    }
  }
}

This project uses the debug module for logging, and can be activated using DEBUG=respectify or DEBUG=respectify:verbose

(The MIT License)

Copyright (c) 2014 Major League Soccer

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.