├── .gitignore ├── .travis.yml ├── index.js ├── package.json ├── readme.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 7 5 | - 8 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var restify = require('restify') 4 | var semver = require('semver') 5 | 6 | module.exports = function (options) { 7 | options = options || {} 8 | 9 | options.prefix = options.prefix || '' 10 | 11 | return function (req, res, next) { 12 | req.originalUrl = req.url 13 | req.url = req.url.replace(options.prefix, '') 14 | 15 | var pieces = req.url.replace(/^\/+/, '').split('/') 16 | var version = pieces[0] 17 | 18 | version = version.replace(/v(\d{1})\.(\d{1})\.(\d{1})/, '$1.$2.$3') 19 | version = version.replace(/v(\d{1})\.(\d{1})/, '$1.$2.0') 20 | version = version.replace(/v(\d{1})/, '$1.0.0') 21 | 22 | if (semver.valid(version)) { 23 | req.url = req.url.substr(pieces[0].length + 1) 24 | req.headers = req.headers || [] 25 | req.headers['accept-version'] = version 26 | } else { 27 | return next(new restify.InvalidVersionError('This is an invalid version')) 28 | } 29 | 30 | next() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-url-semver", 3 | "version": "1.1.1", 4 | "description": "Get restify version from URL and set as header", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/markotom/restify-url-semver" 12 | }, 13 | "keywords": [ 14 | "restify", 15 | "version", 16 | "versioning", 17 | "url", 18 | "rest", 19 | "api", 20 | "semver", 21 | "API-Version" 22 | ], 23 | "author": "Marco Godínez ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/markotom/restify-url-semver/issues" 27 | }, 28 | "homepage": "https://github.com/markotom/restify-url-semver", 29 | "devDependencies": { 30 | "mocha": "^3.4.2", 31 | "sinon": "^1.17.7", 32 | "supertest": "^3.0.0" 33 | }, 34 | "dependencies": { 35 | "restify": "^4.3.1", 36 | "semver": "^5.3.0" 37 | }, 38 | "standard": { 39 | "env": [ 40 | "mocha" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Restify URL SemVer 2 | ================== 3 | > Extract semantic version from URL for [restify](http://mcavage.me/node-restify/). 4 | 5 | [![Build Status](https://travis-ci.org/markotom/restify-url-semver.svg?branch=master)](https://travis-ci.org/markotom/restify-url-semver) 6 | [![npm version](https://badge.fury.io/js/restify-url-semver.svg)](http://badge.fury.io/js/restify-url-semver) 7 | 8 | # Install 9 | 10 | ```js 11 | $ npm install restify-url-semver --save 12 | ``` 13 | 14 | # Usage 15 | 16 | ```js 17 | var restify = require('restify') 18 | var versioning = require('restify-url-semver') 19 | var server = restify.createServer() 20 | 21 | // Add restify-url-semver middleware 22 | server.pre(versioning({ prefix: '/api' })) 23 | 24 | // [protocol]://[host]/api/v1/foo 25 | server.get({ path: '/foo', version: '1.0.0' }, function (req, res, next) { 26 | console.log(req.headers['accept-version']) // 1.0.0 27 | }) 28 | 29 | // [protocol]://[host]/api/v1.2/foo 30 | server.get({ path: '/foo', version: '1.2.0' }, function (req, res, next) { 31 | console.log(req.headers['accept-version']) // 1.2.0 32 | }) 33 | ``` 34 | 35 | Now these formats are available: 36 | 37 | + `[protocol]://[host]/api/v[x]/foo` 38 | + `[protocol]://[host]/api/v[x].[y]/foo` 39 | + `[protocol]://[host]/api/v[x].[y].[z]/foo` 40 | 41 | 42 | ## License 43 | 44 | The MIT License (MIT) 45 | 46 | Copyright (c) 2017 Marco Godínez 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var assert = require('assert') 4 | var request = require('supertest') 5 | var restify = require('restify') 6 | var sinon = require('sinon') 7 | var versioning = require('./index') 8 | 9 | describe('Middleware', function () { 10 | beforeEach(function () { 11 | this.next = sinon.spy() 12 | }) 13 | 14 | it('should be a function', function () { 15 | assert.equal(typeof versioning, 'function') 16 | }) 17 | 18 | it('should returns a function as middleware', function () { 19 | assert.equal(typeof versioning(), 'function') 20 | }) 21 | 22 | it('should returns an invalid version error', function () { 23 | var req = { url: '/foo' } 24 | versioning()(req, null, this.next) 25 | assert(this.next.calledOnce) 26 | assert(this.next.calledWith(new restify.InvalidVersionError('This is an invalid version'))) 27 | assert(this.next.args[0][0] instanceof restify.InvalidVersionError) 28 | }) 29 | 30 | it('should returns a valid version in format v[x]', function () { 31 | var req = { url: '/v1/foo' } 32 | versioning()(req, null, this.next) 33 | assert(this.next.calledOnce) 34 | assert(this.next.calledWithExactly()) 35 | assert.equal(req.headers['accept-version'], '1.0.0') 36 | }) 37 | 38 | it('should returns a valid version in format v[x].[y]', function () { 39 | var req = { url: '/v1.2/foo' } 40 | versioning()(req, null, this.next) 41 | assert(this.next.calledOnce) 42 | assert(this.next.calledWithExactly()) 43 | assert.equal(req.headers['accept-version'], '1.2.0') 44 | }) 45 | 46 | it('should returns a valid version in format v[x].[y].[z]', function () { 47 | var req = { url: '/v1.2.3/foo' } 48 | versioning()(req, null, this.next) 49 | assert(this.next.calledOnce) 50 | assert(this.next.calledWithExactly()) 51 | assert.equal(req.headers['accept-version'], '1.2.3') 52 | }) 53 | 54 | it('should returns a valid version in format /prefix/v[x].[y].[z]', function () { 55 | var req = { url: '/api/v2/foo' } 56 | versioning({ prefix: '/api' })(req, null, this.next) 57 | assert(this.next.calledOnce) 58 | assert(this.next.calledWithExactly()) 59 | assert.equal(req.headers['accept-version'], '2.0.0') 60 | }) 61 | }) 62 | 63 | describe('Restify URL without prefix', function () { 64 | before(function () { 65 | this.server = restify.createServer() 66 | this.server.pre(versioning()) 67 | 68 | this.agent = request(this.server) 69 | }) 70 | 71 | before(function () { 72 | this.server.get({ path: '/example', version: '1.0.0' }, function (req, res) { 73 | res.send({ message: 'accept-version: 1.0.0' }) 74 | }) 75 | 76 | this.server.get({ path: '/example', version: '1.2.0' }, function (req, res) { 77 | res.send({ message: 'accept-version: 1.2.0' }) 78 | }) 79 | 80 | this.server.get({ path: '/example', version: '1.2.3' }, function (req, res) { 81 | res.send({ message: 'accept-version: 1.2.3' }) 82 | }) 83 | }) 84 | 85 | it('should be able to parse a version in format /v[x]', function (done) { 86 | this.agent.get('/v1/example').expect(200, function (err, res) { 87 | if (err) { return done(err) } 88 | assert.equal(res.body.message, 'accept-version: 1.0.0') 89 | done() 90 | }) 91 | }) 92 | 93 | it('should be able to parse a version in format /v[x].[y]', function (done) { 94 | this.agent.get('/v1.2/example').expect(200, function (err, res) { 95 | if (err) { return done(err) } 96 | assert.equal(res.body.message, 'accept-version: 1.2.0') 97 | done() 98 | }) 99 | }) 100 | 101 | it('should be able to parse a version in format /v[x].[y].[z]', function (done) { 102 | this.agent.get('/v1.2.3/example').expect(200, function (err, res) { 103 | if (err) { return done(err) } 104 | assert.equal(res.body.message, 'accept-version: 1.2.3') 105 | done() 106 | }) 107 | }) 108 | }) 109 | 110 | describe('Restify URL with prefix', function () { 111 | before(function () { 112 | this.server = restify.createServer() 113 | this.server.pre(versioning({ prefix: '/api' })) 114 | 115 | this.agent = request(this.server) 116 | }) 117 | 118 | before(function () { 119 | this.server.get({ path: '/example', version: '1.0.0' }, function (req, res) { 120 | res.send({ message: 'accept-version: 1.0.0' }) 121 | }) 122 | 123 | this.server.get({ path: '/example', version: '1.2.0' }, function (req, res) { 124 | res.send({ message: 'accept-version: 1.2.0' }) 125 | }) 126 | 127 | this.server.get({ path: '/example', version: '1.2.3' }, function (req, res) { 128 | res.send({ message: 'accept-version: 1.2.3' }) 129 | }) 130 | }) 131 | 132 | it('should be able to parse a version in format /[prefix]/v[x]', function (done) { 133 | this.agent.get('/api/v1/example').expect(200, function (err, res) { 134 | if (err) { return done(err) } 135 | assert.equal(res.body.message, 'accept-version: 1.0.0') 136 | done() 137 | }) 138 | }) 139 | 140 | it('should be able to parse a version in format /[prefix]/v[x].[y]', function (done) { 141 | this.agent.get('/api/v1.2/example').expect(200, function (err, res) { 142 | if (err) { return done(err) } 143 | assert.equal(res.body.message, 'accept-version: 1.2.0') 144 | done() 145 | }) 146 | }) 147 | 148 | it('should be able to parse a version in format /[prefix]/v[x].[y].[z]', function (done) { 149 | this.agent.get('/api/v1.2.3/example').expect(200, function (err, res) { 150 | if (err) { return done(err) } 151 | assert.equal(res.body.message, 'accept-version: 1.2.3') 152 | done() 153 | }) 154 | }) 155 | }) 156 | --------------------------------------------------------------------------------