Git Product home page Git Product logo

swaggerize-express's Introduction

swaggerize-express

Build Status
NPM version

swaggerize-express is a design-driven approach to building RESTful apis with Swagger and Express.

swaggerize-express provides the following features:

  • API schema validation.
  • Routes based on the Swagger document.
  • API documentation route.
  • Input validation.

See also:

Why "Design Driven"

There are already a number of modules that help build RESTful APIs for node with swagger. However, these modules tend to focus on building the documentation or specification as a side effect of writing the application business logic.

swaggerize-express begins with the swagger document first. This facilitates writing APIs that are easier to design, review, and test.

Quick Start with a Generator

This guide will let you go from an api.json to a service project in no time flat.

First install generator-swaggerize (and yo if you haven't already):

$ npm install -g yo
$ npm install -g generator-swaggerize

Now run the generator.

$ mkdir petstore && cd $_
$ yo swaggerize

Follow the prompts (note: make sure to choose express as your framework choice).

When asked for a swagger document, you can try this one:

https://raw.githubusercontent.com/wordnik/swagger-spec/master/examples/v2.0/json/petstore.json

You now have a working api and can use something like Swagger UI to explore it.

Manual Usage

var swaggerize = require('swaggerize-express');

app.use(swaggerize({
    api: require('./api.json'),
    docspath: '/api-docs',
    handlers: './handlers'
}));

Options:

  • api - a valid Swagger 2.0 document.
  • docspath - the path to expose api docs for swagger-ui, etc. Defaults to /.
  • handlers - either a directory structure for route handlers or a premade object (see Handlers Object below).
  • express - express settings overrides.

After using this middleware, a new property will be available on the app called swagger, containing the following properties:

  • api - the api document.
  • routes - the route definitions based on the api document.

Example:

var http = require('http');
var express = require('express');
var swaggerize = require('swaggerize-express');

app = express();

var server = http.createServer(app);

app.use(swaggerize({
    api: require('./api.json'),
    docspath: '/api-docs',
    handlers: './handlers'
}));

server.listen(port, 'localhost', function () {
    app.swagger.api.host = server.address().address + ':' + server.address().port;
});

Mount Path

Api path values will be prefixed with the swagger document's basePath value.

Handlers Directory

The options.handlers option specifies a directory to scan for handlers. These handlers are bound to the api paths defined in the swagger document.

handlers
  |--foo
  |    |--bar.js
  |--foo.js
  |--baz.js

Will route as:

foo.js => /foo
foo/bar.js => /foo/bar
baz.js => /baz

Path Parameters

The file and directory names in the handlers directory can also represent path parameters.

For example, to represent the path /users/{id}:

handlers
  |--users
  |    |--{id}.js

This works with directory names as well:

handlers
  |--users
  |    |--{id}.js
  |    |--{id}
  |        |--foo.js

To represent /users/{id}/foo.

Handlers File

Each provided javascript file should export an object containing functions with HTTP verbs as keys.

Example:

module.exports = {
    get: function (req, res) { ... },
    put: function (req, res) { ... },
    ...
}

Handler Middleware

Handlers can also specify middleware chains by providing an array of handler functions under the verb:

module.exports = {
    get: [
        function m1(req, res, next) { ... },
        function m2(req, res, next) { ... },
        function handler(req, res)  { ... }
    ],
    ...
}

Handlers Object

The directory generation will yield this object, but it can be provided directly as options.handlers.

Note that if you are programatically constructing a handlers obj this way, you must namespace HTTP verbs with $ to avoid conflicts with path names. These keys should also be lowercase.

Example:

{
    'foo': {
        '$get': function (req, res) { ... },
        'bar': {
            '$get': function (req, res) { ... },
            '$post': function (req, res) { ... }
        }
    }
    ...
}

Handler keys in files do not have to be namespaced in this way.

Security Middleware

If a security definition exists for a path in the swagger API definition, and an appropriate authorize function exists (defined using x-authorize in the securityDefinitions as per swaggerize-routes), then it will be used as middleware for that path.

In addition, a requiredScopes property will be injected onto the request object to check against.

For example:

Swagger API definition:

    .
    .
    .

    //A route with security object.
    "security": [
        {
            "petstore_auth": [
                "write_pets",
                "read_pets"
            ]
        }
    ]
    .
    .
    .
    //securityDefinitions
    "securityDefinitions": {
        "petstore_auth": {
            "x-authorize": "lib/auth_oauth.js", // This path has to be relative to the project root.
            "scopes": {
                "write_pets": "modify pets in your account",
                "read_pets": "read your pets"
            }
        }
    },

Sample x-authorize code - lib/auth_oauth.js :

//x-authorize: auth_oauth.js
function authorize(req, res, next) {
    validate(req, function (error, availablescopes) {
        /*
         * `req.requiredScopes` is set by the `swaggerize-express` module to help
         * with the scope and security validation.
         *
         */
        if (!error) {
            for (var i = 0; i < req.requiredScopes.length; i++) {
                if (availablescopes.indexOf(req.requiredScopes[i]) > -1) {
                    next();
                    return;
                }
            }

            error = new Error('Do not have the required scopes.');
            error.status = 403;

            next(error);
            return;
        }

        next(error);
    });
}

The context for authorize will be bound to the security definition, such that:

function authorize(req, res, next) {
    this.authorizationUrl; //from securityDefinition for this route's type.
    //...
}

swaggerize-express's People

Contributors

aredridel avatar duncanhall avatar fastlorenzo avatar gabrielcsapo avatar grawk avatar jamesblack avatar jasisk avatar kenjones-cisco avatar mikestead avatar nbcraft avatar normpng avatar pvenkatakrishnan avatar shad7 avatar subeeshcbabu avatar tlivings avatar totherik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swaggerize-express's Issues

yo swaggerize

You can bypass the prompt for a Path or URL to a swagger document. When you do a very misleading stack trace is printed out.

yo swaggerize should behavior similar to the yo kraken generator that is when a prompt is required don't allow the user to skip.

Using paraType body/form

can you provide some examples using paramType body and form, pref in combination with express 4?

re the body I can get some to work:

  • added app.use(bodyParser.text())
  • ran curl <url> -v -d "" -H 'Content-Type: text/plain' -X POST
  • results in 'Error: invalid type: object (expected string)' (expected: required body missing)
  • using any value in -d like -d " " works fine

the form is where I fail to get it working...

features explanation

Hi

I am just looking into list of available features of swaggerize-express and do not understand ( or simply can not find implementation bits of) API schema validation and Input validation. Regarding Input validation i would expect that appropriate handler method would simple check if body conforms to definitions. Unfortunately https://raw.githubusercontent.com/wordnik/swagger-spec/master/examples/v2.0/json/petstore.json only has GET method so i didn't manage to check my assumptions. Can you explain what you meant exactly by API schema validation and Input validation?

BTW I tried use generator against http://petstore.swagger.io/v2/swagger.json or https://raw.githubusercontent.com/swagger-api/swagger-spec/master/examples/v2.0/json/petstore-expanded.json but unfortuantely generator always throws an exception:
undefined:10
_.forEach(Object.keys(properties), function (prop) {;
^
ReferenceError: properties is not defined

PetStore example fails with latest generator

$ node -v
v4.3.1

$ npm -v
3.7.2

$ yo swaggerize
Swaggerize Generator
? What would you like to call this project: p
? Your name: 
? Your github user name: 
? Your email: 
? Path (or URL) to swagger document: https://raw.githubusercontent.com/wordnik/swagger-spec/master/examples/v2.0/json/petstore.json
? Express, Hapi or Restify: express
identical .jshintrc
identical .gitignore
identical .npmignore
identical server.js
identical package.json
identical README.md
identical config/petstore.json
undefined:11
_.forEach(Object.keys(properties), function (prop) {;
                      ^

ReferenceError: properties is not defined
    at eval (eval at template (/usr/local/lib/node_modules/generator-swaggerize/node_modules/lodash/dist/lodash.js:6306:22), <anonymous>:11:23)
    at underscore [as _engine] (/usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/util/engines.js:32:30)
    at engine (/usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/actions/actions.js:303:10)
    at template (/usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/actions/actions.js:281:15)
    at /usr/local/lib/node_modules/generator-swaggerize/app/index.js:287:18
    at Array.forEach (native)
    at yeoman.generators.Base.extend.models (/usr/local/lib/node_modules/generator-swaggerize/app/index.js:276:49)
    at /usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/base.js:341:43
    at /usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/node_modules/async/lib/async.js:551:21
    at /usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/node_modules/async/lib/async.js:227:13

Travis seems to have the same issue: https://travis-ci.org/krakenjs/generator-swaggerize/jobs/110153740

Support for binding variables into handler

From the docs it appears that you can bind url parameters to unique handlers but not sure how/if you can bind a variable into the handler.

i.e.

/person -> people.js
/person/:id -> person.js with id available inside

required query parameters always fail validation

I believe there's a bug where query string parameters are not being correctly read of the request object. As a result any parameters marked as required fail validation. Offending code is in expressroutes.js in the switch statement. Currently query and path are being treated the same:

switch (parameter.in) {
    case 'path':
    case 'query':
        isPath = true;
        value = req.params[parameter.name];
        break;
    case 'header':
        value = req.get(parameter.name);
        break;
    case 'body':
    case 'formData':
        value = req.body;
}

This should probably be the following instead:

switch (parameter.in) {
    case 'path':
        isPath = true;
        value = req.params[parameter.name];
        break;
    case 'query':
        value = req.query[parameter.name];
        break;
    case 'header':
        value = req.get(parameter.name);
        break;
    case 'body':
    case 'formData':
        value = req.body;
}

api.host is changing it's content based on express hostname

Hi there,
First, thanks for this code... that's awesome.

Second, when i access the api-docs url, it is changing the host property of the api configuration by it's own. The problem is, if i don't put any hostname in express, the host generated is like ":::8000", and that is creating some problems with the swagger-UI.

I really could not find where it is changing that... had to make a modification in "expressroutes.js " to make it work.

router.get(mountpath + options.docspath, function (req, res) { options.api.host = req.headers.host; res.json(options.api); });

Thanks

overriding express options

It seems that swaggerize express is overriding some of my options that I set for my express app. I have set json spaces to 2, but it gets overridden here.

Here is my example code:

'use strict';

var http = require('http');
var express = require('express');
var swaggerize = require('swaggerize-express');
var app = express();
var server = http.createServer(app);

app.disable('x-powered-by');
app.set('json spaces', 2);

var compression = require('compression');
var bodyParser = require('body-parser');

app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.use(swaggerize({
  api: require('./resources'),
  docspath: '/api-docs',
  handlers: './routes'
});

server.listen(process.env.PORT || 8000)
  .on('listening', console.log.bind(null, 'Server started'))
  .on('error', console.log.bind(null));

Expected Behavior:

Api endpoints should have the correctly set json spaces option (for pretty printed json)

Actual Behavior:

Api endpoints are not pretty printed because the setting is getting overridden.

Proposed Solution:

I know that json spaces isn't the only option that is being overriden, but I agree that the ones being overridden are sensible defaults for a majority of people. My only problem with it is that it is unexpected behavior based on my settings. I don't think that you can detect whether or not a setting was set on the user's end, but perhaps just leaving it up to the user to choose the settings would be best.

ValidationError: "body" is required

I send request with body, but still get this error

$ http POST :3000/login email=a password=b

My api spec

  "paths": {
    "/login": {
      "post": {
        "parameters": [
          {"name": "body", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Login"}}
        ]
      }
    }
  },
ValidationError: "body" is required
    at Object.exports.process (/home/guten/a/swagger/swaggerize-express/node_modules/joi/lib/errors.js:140:17)
    at internals.Any.validate (/home/guten/a/swagger/swaggerize-express/node_modules/joi/lib/any.js:667:25)
    at validateParameter (/home/guten/a/swagger/swaggerize-express/node_modules/swaggerize-routes/lib/validator.js:111:28)
    at validateInput (/home/guten/a/swagger/swaggerize-express/node_modules/swaggerize-express/lib/expressroutes.js:82:9)
    at Layer.handle [as handle_request] (/home/guten/a/swagger/swaggerize-express/node_modules/express/lib/router/layer.js:95:5)
    at next (/home/guten/a/swagger/swaggerize-express/node_modules/express/lib/router/route.js:131:13)
    at Route.dispatch (/home/guten/a/swagger/swaggerize-express/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/home/guten/a/swagger/swaggerize-express/node_modules/express/lib/router/layer.js:95:5)
    at /home/guten/a/swagger/swaggerize-express/node_modules/express/lib/router/index.js:277:22
    at Function.process_params (/home/guten/a/swagger/swaggerize-express/node_modules/express/lib/router/index.js:330:12)

CORS support

Is there a known way to implement CORS, especially preflight, with swaggerize-express?

Thanks!

Attemping to parse body when using formData

When creating my swagger.yaml I have decided to use formData for creating users. So /users POST would contain the following parameters.

  parameters:
    -
      name: first_name
      in: formData
      description: First name
      required: true
      type: string
      x-example: Jane
    -
      name: last_name
      in: formData
      description: Last name
      required: true
      type: string
      x-example: Doe
    -
      name: username
      in: formData
      description: Username
      required: true
      type: string
      x-example: janedoe
    -
      name: password
      in: formData
      description: Password
      required: true
      type: string
      x-example: abc123

According to the swagger spec and all the examples I've seen this is how you're supposed to do this, however I get an "invalid json" error because it's trying to parse the body, which doesn't exist. I have gotten the in: body type to work by including body-parser in my project. Without it even the in: body type fails saying that "body is required".

var bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.use(swaggerize({
    api: './app/docs/swagger.yml',
    docspath: '/',
    handlers: path.resolve('./app/handlers')
}));

Is body-parser causing the conflict? If so, how can I remove body parser and have both in: body and in: formData work?

Error creating handler directories

I used the example definition provided in the Swagger spec:
https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#521-object-example

Followed the QUICKSTART, then ran:

node_modules/.bin/swaggerize --api spec.json --handlers handlers

I get this error

fs.js:642
  return binding.mkdir(pathModule._makeLong(path),
                 ^
Error: ENOENT, no such file or directory '/Users/jasharmon/src/swaggerize-test/handlers/store/order'
    at Object.fs.mkdirSync (fs.js:642:18)
    at /Users/jasharmon/src/swaggerize-test/node_modules/swaggerize-express/bin/lib/create.js:87:20
    at Array.forEach (native)
    at Object.createHandlers [as handlers] (/Users/jasharmon/src/swaggerize-test/node_modules/swaggerize-express/bin/lib/create.js:72:25)
    at Object.<anonymous> (/Users/jasharmon/src/swaggerize-test/node_modules/swaggerize-express/bin/swaggerize.js:63:24)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)

Only use the schema validation feature

Hello,

I'm just trying to use the schema validation aspect of this repo for now. Is it possible for this middleware to simply validate incoming requests based on the schema outlined in the swagger documentation and respond accordingly (400)?

I assume it's as simple using the repository like so:

    app.use(swaggerize({
      api: require('./swagger.json'),
    }));

Allow multiple Schemas / API versions using a router in place of app

Currently, the module can only be applied to a top level Express app object.

This prevents the option of having multiple instances of swaggerize-express running in the same Express instance. Versioning of APIs is therefore not currently possible in the same application.

For example, I might have 2 schemas, describing different versions of the same API:

swagger-v1.json
swagger-v2.json

Each schema describes it's own operations / vaildations, but wants to be run from the same application, for example exposing /api/v1/ and /api/v2/ as swagger base paths.

Additionally, applying the module at the app level makes it difficult to apply conditional middleware for different paths.

For example, I might want to pass all /api/ routes through an aunthentication() middleware, but allow calls to /something/public/ to bypass it. Because swaggerize-express gets applied at an app level, if I want to call some middleware before hitting the Swagger routes, I have to declare that middleware at the app level also:

app.use(customMiddleware());
app.use(swaggerize({/* options */}));

//Everything here now has to pass through customMiddleware();

router.use('/something/public/', customHandler);

Both of these use-cases can be handled by allowing swaggerize-express to be applied as a more generic router-level middleware.

I'm tempted to create a separate project, based on swaggerize-builder that would work in this way, but if this makes sense and seems useful, I'd be happy to do this work under a fork of this project. It would almost certainly not be be backwards compatible though.

Validation failure of parameter in path should be routing mismatch

Currently, swaggerize-express returns error 400 on failing validation even when parameter is in path ๐Ÿ˜Ÿ
However, I think, It should be routing mismatch, at least, return error 404.

And then, when it becomes routing mismatch, flowing routing will work expected ๐Ÿ˜‰:

/article/{id}:
  parameters:
    - in: path
      type: integer
/article/{slug}:
  parameters:
    - in: path
      type: string

solution 1

Call next('route') instead of next(error), when validation is failed.

refs: http://expressjs.com/guide/routing.html#route-handlers

solution 2

Make the routing path with validation regex.

Using the demo dont show anything

Hi, i try to use the seed project what comes with generator, but dont show anything. When i put "localhost:8000/api" only retrieves OK.

Swagger 2.0 File Type is Unsupported

You cannot use the type file in your swagger spec, because Enjoi doens't know how to parse the type, this creates a failure to be 2.0 spec compliant. Despite using all these fancy words, I am so not the guy to know how to fix it.

Error Messaging

You need better logging (messaging) when an unknown type is used.

Difficulty with handler for file upload

Running into a similar issue to #82 with an empty body on form data. This is specific to file upload however.

I'm wanting a handler to upload a file and have the following route specified:

post:
  description: Upload a zip
  operationId: uploadZip
  consumes: multipart/form-data
  parameters:
    - name: zip
      in: formData
      type: file
      required: true
  responses:
    '204':
      description: OK

For file upload it seems that adding a global middleware is not a great option in express (see Upload Per Route), so instead it's recommended to add it to handlers which require it.

The issue I'm having is that validation appears to run before any route middleware so I have no chance to process the stream and update req.body.

The validator also assumes that form data will reside in the req.body property, however this may not be the case, for example with https://github.com/expressjs/multer it's dropped in a req.files property. I guess if I could add middleware to the route ahead of validation I could move these into the body.

Edit: Pull request #90 moves middleware before validation.

Optional Security Definitions

Is it possible to have optional security definitions? I have to spec an API that either has basic auth or is publicly accessible. However Swagger doesn't seem to cater for this scenario and as a result I can't use swaggerize-express correctly. If I specify basic auth in the swagger yaml file, then swaggerize-express always results in a validation failure when no Authorization header is present.

support for operationId

Would you take a pull request that uses operationId (from the swagger spec) instead of the $method pattern?

We use operationId to declare method names for our (generated) client library, and it'd be cool to use that same pattern when exporting our controllers.

Options related error - "options.api.basePath" undefined

Hey @tlivings,

I was working through the swaggerize-express example when I came across the error below. It appears to be related to options.api.basePath => ./lib/index.js#L20.

url.parse is throwing an error because options.api.basePath is undefined

url.js:107
    throw new TypeError("Parameter 'url' must be a string, not " + typeof url)
          ^
TypeError: Parameter 'url' must be a string, not undefined
    at Url.parse (url.js:107:11)
    at Object.urlParse [as parse] (url.js:101:5)
    at swaggerize (/Users/jhalpert/node_modules/swaggerize-express/lib/index.js:20:20)
    at Object.<anonymous> (/Users/jhalpert/Dropbox/Stuff/paypal/swaggerize-express/app.js:13:11)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)

I don't see options.api.basePath mentioned anywhere in the docs. Maybe I am missing something?

Any direction would be appreciated - thanks.

Relevant code

Url.prototype.parse
swaggerize-express index.js #L20

My code

gist (a close mirror of the example in the docs)

Better Explanation for Security Middleware Section

I cannot make the "Security Middleware" work for my specification.
My security definition is the following in YAML:

securityDefinitions:
myauth:
type: oauth2
authorizationUrl: "http://api.mywebsite.com/oauth/dialog"
flow: implicit
x-authorize: "authorize"
scopes:
"write:user": "modifies users' information"
"read:user": "reads users' information"
"write:request": "needed to write requests on the server"
"read:request": "authorization to read requests on the server"
"read:driver": "reads drivers' information"
"write:driver": "modifies drivers' information"

But I don't think I have added properly the x-authorize parameter.

What does "an appropriate authorize function exists (defined using x-authorize in the securityDefinitions as per swaggerize-routes)" mean? Could it be explained in a detailed way, possibly with an example?
Thank you in advance,

Luca

swaggerize-express should allow swagger 2.0 vendor extensions

if I try to add vendor extension to the root of the swagger schema, it would fail saying the extension is not allowed.
Example:

{
    "swagger": "2.0",
    "x-property": "custom property",
    "info": {
        "version": "1.0.0",
        "title": "Swagger Petstore",
        "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
        "termsOfService": "http://helloreverb.com/terms/",
        "contact": {
            "name": "Wordnik API Team"
        },
        "license": {
            "name": "MIT"
        }
    },
    "basePath": "/svc/v1",
    "host": "petstore.swagger.wordnik.com",
    "schemes": [
        "http"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {}
}

Converting ValidationError stack trace into JSON payload

I have an API which requires a JSON response body containing error information. I receive a ValidationError stack trace from enjoi when required parameters (defined in swagger) are not present. Is it possible to map this stack trace to a JSON model?

Enhancement: Show how to perform basic auth

I apologize, but I'm having a hard time finding out how to properly build a basic auth handler - I have a file pointed at with x-authorize, and it loads that file, but I don't quite understand the underlying mechanic that I should build - I have the x-ssl-client-common-name in the header, and I'm trying to validate against that.

//in swagger json
clientcert: {
    type: 'basic',
    description: 'Authenticate clients with a client certificate',
    'x-authorize': './clientcert-auth-handler'
}

//inside clientcert-auth-handler'
var sslClientCn = _.get(req.headers, 'x-ssl-client-cn');
var subjectName = sslClientCn;
      if(_.contains(value.subjectNames, subjectName) ){
        req.authenticated = true;
        return next();
      }

and that works when the headers are set correctly, but I don't know how to build this handler to behave when it should return a 401 instead - do I have it return an error on next? should I just have it return without calling next? I'm not certain the desired path - most examples just show oauth2 examples or are empty so I'm not fully understanding.

Thanks in advance,
-Paul

Unit tests fail

Somewhere in the midst of the tests running, there's an error "Error: invalid type: string (expected integer)". Transcript below.

swaggerize-express jasharmon$ npm test

[email protected] test /Users/jasharmon/src/swaggerize-express
tape test/*.js

TAP version 13

express routes

test api

ok 1 routes added.
ok 2 api-docs added.
ok 3 hello added.
ok 4 sub added.
ok 5 sub added (head).
ok 6 sub/path added.

test no handlers

ok 7 only api-docs route added.
ok 8 api-docs added.

test variable filenames

ok 9 three routes added.
ok 10 api-docs added.
ok 11 /stuffs added.
ok 12 /stuffs/:id added.

makereply

make

ok 13 reply made
ok 14 called next.
ok 15 is an error.
ok 16 called redirect.
ok 17 called end
ok 18 _raw is an object.
ok 19 _raw is the response.

routebuilder

build

ok 20 added 5 routes.
ok 21 has method property.
ok 22 has name property.
ok 23 has path property.
ok 24 has validators property.
ok 25 has method property.
ok 26 has name property.
ok 27 has path property.
ok 28 has validators property.
ok 29 has method property.
ok 30 has name property.
ok 31 has path property.
ok 32 has validators property.
ok 33 has method property.
ok 34 has name property.
ok 35 has path property.
ok 36 has validators property.
ok 37 has method property.
ok 38 has name property.
ok 39 has path property.
ok 40 has validators property.

collections

ok 41 added 2 routes.
ok 42 has method property.
ok 43 has name property.
ok 44 has path property.
ok 45 has validators property.
ok 46 has method property.
ok 47 has name property.
ok 48 has path property.
ok 49 has validators property.

bad dir

ok 50 throws error for bad directory.

schema

good api

ok 51 no errors

bad api

ok 52 bad
ok 53 has error.

good model

ok 54 no errors

bad model

ok 55 bad
ok 56 has error.

swaggycat valid input/output

api

ok 57 has _api property.
ok 58 _api is an object.
ok 59 has setUrl property.
ok 60 setUrl is a function.
ok 61 should be equal

docs

ok 62 no error.
ok 63 200 status.

route

ok 64 no error.
ok 65 404 required param missing.

route

ok 66 no error.
ok 67 200 status.
ok 68 body is correct.

route

ok 69 no error.
ok 70 200 status.

route

ok 71 no error.
ok 72 200 status.

swaggycat invalid input/output

bad input

ok 73 no error.
ok 74 400 status.
Error: invalid type: string (expected integer)
at /Users/jasharmon/src/swaggerize-express/lib/validation.js:47:26
at validate (/Users/jasharmon/src/swaggerize-express/lib/validation.js:109:9)
at validateInput (/Users/jasharmon/src/swaggerize-express/lib/validation.js:43:13)
at Layer.handle as handle_request
at next (/Users/jasharmon/src/swaggerize-express/node_modules/express/lib/router/route.js:100:13)
at Route.dispatch (/Users/jasharmon/src/swaggerize-express/node_modules/express/lib/router/route.js:81:3)
at Layer.handle as handle_request
at /Users/jasharmon/src/swaggerize-express/node_modules/express/lib/router/index.js:227:24
at param (/Users/jasharmon/src/swaggerize-express/node_modules/express/lib/router/index.js:324:14)
at param (/Users/jasharmon/src/swaggerize-express/node_modules/express/lib/router/index.js:340:14)

bad output

Error: invalid type: string (expected integer)
at new ValidationError (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:1405:12)
at ValidatorContext.createError (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:365:9)
at ValidatorContext.validateType (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:749:14)
at ValidatorContext.validateBasic (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:719:19)
at ValidatorContext.validateAll (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:599:19)
at Object.api.validate (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:1505:24)
at Object.api.validateResult (/Users/jasharmon/src/swaggerize-express/node_modules/tv4/tv4.js:1516:18)
at Object.validate (/Users/jasharmon/src/swaggerize-express/lib/schema/index.js:39:23)
at validate (/Users/jasharmon/src/swaggerize-express/lib/validation.js:106:25)
at validateOutput (/Users/jasharmon/src/swaggerize-express/lib/validation.js:78:13)
ok 75 no error.
ok 76 500 status.

null input not found

ok 77 no error.
ok 78 404 status.

output not enabled

bad output

ok 79 no error.
ok 80 It is ok, validation is off!

utils

convertParam

ok 81 is converted.

convertPath

ok 82 is converted.

prefix

ok 83 string had prefix so is the same.
ok 84 string did not have prefix so was changed.
ok 85 handled undefined.

unprefix

ok 86 string had prefix so is changed.
ok 87 string did not have prefix so was not changed.
ok 88 handled undefined.

suffix

ok 89 string had suffix so is the same.
ok 90 string did not have suffix so was changed.
ok 91 handled undefined.

unsuffix

ok 92 string had suffix so is changed.
ok 93 string did not have suffix so was not changed.
ok 94 handled undefined.

ends with

ok 95 foobar ends with bar
ok 96 foobar doesn't end with x

1..96

tests 96

pass 96

ok

Api Path Generation Wrong in Windows

Under Windows, the path generated by Swaggerize in server.js is wrong, since it returns something in the like of:

api: path.resolve('./config\swagger.json'),

Note: error in **./config**, which should be ./config/

The same happens in all the automated tests generated, which is a rather more serious problem

Override JOI options feature request

It would be nice to have the possibility to add JOI options override, so that, if present, the JOI.any.options would be overridden.
This would make it possible to add options like "allowUnknown" as seen in the JOI API documentation.

This would of course need to be passed down through swaggerize-routes and enjoi.

Additional middleware for routes, attaching passport auth

Hi there,

I really like this project. Thanks for publishing it.

I have an api with most routes protected via passport-http-bearer (i.e. your standard OAuth2 access_token). I'm trying to figure out how to apply my auth middleware to the handlers without a lot of boilerplate code.

var auth = require( '../auth' );

module.exports = {

    get: function( req, res ) {
        auth.requireUser( req, res, function() {
            res.json( {
                meta: { code: 200 },
                response: {
                    firstName: req.user.firstName,
                    lastName:  req.user.lastName,
                    email:     req.user.email
                }
            } );
        } );
    }

};

I suppose that's not too bad. Maybe it would be even better to define security middleware functions that are automatically applied based on the security setting in the swagger spec.

So if my swagger spec had

    "paths": {
        "/me": {
            "get": {
                "summary": "Returns the current logged in user.",
                "security": [
                    {
                        "myAuthDefinition": []
                    }
                ],
                ...

Then could initialize swaggerize with something like

app.use(swaggerize({
    api: require('./api.json'),
    docspath: '/api-docs',
    handlers: './handlers',
    security: {
        myAuthDefinition: auth.requireUser
    }
});

Then swaggerize would automatically put the auth.requireUser middleware before the me GET handler.

Thanks. I'm curious to hear if there are alternatives to this approach.

Bug in routing

I've got the swagger spec like this:

paths:
  /users/{query}: (get, put, delete)
  /users/findUser: (get)
  /users/newUser: (post)

The paths are defined in this order. After the implementation of handlers, if i try to call the path /users/findUser it will execute the handler for path /users/{query}.
I know that usually in an express application if i define a path with path parameter before another path without path parameter express will execute the handler of path with path parameter, but in this case swagger specs doesn't have to be depending to implementation.

Thank you

Handler for root request not working

I usually create a root request, that is some information about the API that is returned when a GET is made to '/' of the API. I can specify this in my swagger.json file. However adding a listener to it did not seem to work :/

I tried:

app.use(swaggerize({
    api: spec,
    handlers: {
    '/': {
        '$get': function(req, res) {
            res.send(infoObject);
        }
    }
}));

but with no luck. I also tried an empty string...

Is there any other way, than adding a handler manually?

Generation fails on Windows machine

When "yo swaggerize" is executed on a Windows machine, it fails after trying to execute the following (autogenerated) package.json script:

"regenerate": "yo swaggerize --only=handlers,models,tests --framework express --apiPath config\swagger.yaml"

Problematic part is the "config\swagger.yaml" - it should be "config/swagger.yaml" to work properly in Windows.

Authorization Handler not being called

Utilized swaggerize generator to initialize project from a pre-defined yaml spec. Authorization handler is not being called for applicable routes.

/ticket:
    put:
      tags:
        - ticket
      summary: submit a request ticket
      operationId: addRequestTicket
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - in: body
          name: body
          description: Request ticket object to be submitted
          required: true
          schema:
            $ref: '#/definitions/Ticket'
      responses:
        '400':
          description: Invalid ID supplied
        '401':
          description: Unauthorized
        '404':
          description: Not found
        '405':
          description: Validation exception
      security:
        - token: []
securityDefinitions:
  token:
    type: apiKey
    name: auth
    in: header

in security/token.js

module.exports = function authorize(req, res, next) {
    //The context('this') for authorize will be bound to the 'securityDefinition'
    //this.name - The name of the header or query parameter to be used for securityDefinitions:token apiKey security scheme.
    //this.in - The location of the API key ("query" or "header") for securityDefinitions:token apiKey security scheme.

    var auth = req.headers[this.name];
    console.log("In Token.js Authorize, token: %s", auth);
    next();
};

Problem token.js authorize code is never executed, attempted x-authorize specified implementation as well same result:

PUT /api/ticket HTTP/1.1
auth: 987654321
Content-Type: application/json
Host: localhost:8000
Connection: close
User-Agent: Paw/2.3.2 (Macintosh; OS X/10.11.6) GCDHTTPRequest
Content-Length: 204

{"id":"123456789","username":"uname","lastName":"Doe","firstName":"John","email":"[email protected]","deviceId":"01","message":"It is broken and does not start, tried turning it off and back on again"}

401 Unauthorized response is returned

Error: Unauthorized.
   at passed (/Documents/code/git/test-api/node_modules/swaggerize-express/lib/expressroutes.js:182:25)
   at /Documents/code/git/test-api/node_modules/async/lib/async.js:360:13
   at /Documents/code/git/test-api/node_modules/async/lib/async.js:122:13
   at _each (/Documents/code/git/test-api/node_modules/async/lib/async.js:46:13)
   at Object.async.each (/Documents/code/git/test-api/node_modules/async/lib/async.js:121:9)
   at Object.async.some (/Documents/code/git/test-api/node_modules/async/lib/async.js:359:15)
   at authorize (/Documents/code/git/test-api/node_modules/swaggerize-express/lib/expressroutes.js:194:15)
   at Layer.handle [as handle_request] (/Documents/code/git/test-api/node_modules/express/lib/router/layer.js:95:5)
   at next (/Documents/code/git/test-api/node_modules/express/lib/router/route.js:131:13)
   at Route.dispatch (/Documents/code/git/test-api/node_modules/express/lib/router/route.js:112:3)

code available at following repo

[https://github.com/karlmoad/test-api]

Authorization using Security Parameters not working

Code is not reaching till authorization handler..

...
 "security": [
                    {
                        "autopass": [
                            "read_user"
                        ]
                    }
                ],

..

and security definitions:

..
"securityDefinitions": {
        "autopass": {
            "x-authorize": "security/autopass_authorize.js",
            "type": "oauth2",
            "scopes": {
               "read_user": "read your pets"
            },
            "flow": "implicit",
            "authorizationUrl": "security/autopass_authorize.js"

        }
    }

and the file security/autopass_authorize.js:

'use strict';

module.exports = function authorize(req, res, next) {
    console.log("IN authorize");
};

Please help!!!!!!!!!!!!!!!!

broken with getting started directions

I am unable to install based on your directions

$ yo swaggerize
Swaggerize Generator
? What would you like to call this project: petstore
? Your name:
? Your github user name:
? Your email:
? Path (or URL) to swagger document: https://raw.githubusercontent.com/wordnik/swagger-spec/mast
er/examples/v2.0/json/petstore.json
? Express, Hapi or Restify: express
   create .jshintrc
   create .gitignore
   create .npmignore
   create server.js
   create package.json
   create README.md
   create config/petstore.json
   create handlers/pets.js
   create handlers/pets/{petId}.js
undefined:11
_.forEach(Object.keys(properties), function (prop) {;
                      ^

ReferenceError: properties is not defined
    at eval (eval at template (/usr/local/lib/node_modules/generator-swaggerize/node_modules/lodash/dist/lodash.js:6306:22), <anonymous>:11:23)
    at underscore [as _engine] (/usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/util/engines.js:32:30)
    at engine (/usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/actions/actions.js:303:10)
    at template (/usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/actions/actions.js:281:15)
    at /usr/local/lib/node_modules/generator-swaggerize/app/index.js:287:18
    at Array.forEach (native)
    at yeoman.generators.Base.extend.models (/usr/local/lib/node_modules/generator-swaggerize/app/index.js:276:49)
    at /usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/lib/base.js:341:43
    at /usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/node_modules/async/lib/async.js:551:21
    at /usr/local/lib/node_modules/generator-swaggerize/node_modules/yeoman-generator/node_modules/async/lib/async.js:227:13

Attempting to use multiple security definitions for a single path

I have 2 security definitions:

    "securityDefinitions": {
        "api_key1": {
            "type": "apiKey",
            "name": "Authorization",
            "in": "header",
            "x-authorize": "middleware/manageApiKey1"
        },
        "api_key2": {
            "type": "apiKey",
            "name": "X-API-Key",
            "in": "header",
            "x-authorize": "middleware/manageApiKey2"
        }
    }

I have a path that I'd like to use both security middleware pieces for:

        "/apiCheck/": {
            "get": {
                "summary": "apiCheck for the service",
                "description": "apiCheck for the service",
                "operationId": "apiCheck",
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "apiCheck success",
                        "schema": {
                            "type": "string"
                        }
                    },
                    "500": {
                        "description": "apiCheck failure",
                        "schema": {
                            "type": "string"
                        }
                    }                    
                },
                "security": [
                    {
                        "api_key1": [],
                        "api_key2": []
                    }
                ]       
            }
        }

Using the above, I thought it would run both middleware functions in the order of "api_key1" and then "api_key2". Using some console logging, I am seeing that function api_key1 gets called, but, before it finishes, api_key2 is called. In my tests, api_key1 is invalid and api_key2 is valid, but, since api_key2 gets finished before api_key1, the application is returning a 200 rather than the expected 500.

To see if I'm running into callback issues, I removed the use of security in the swagger and put them as middleware in the express app and it works as expected (returning a 500 instead of a 200).

Am I doing something wrong on my swagger security or does it not function this way?

in:body failing

Hi,
I had the intention on using this library to sync validation rules of local express server with those on our production java application container. When testing all GET methods this seems to work fine once the handlers in place. Yet when I am testing POST methods it seems there is an error in /lib/expressroutes.js valueAccessor method on the block for (param.in === "body").

When I have the declaration below

{
...
"/v1/publications": {
        "post": {
            "tags": ["publications"],
            "summary": "Launch Publication",
            "description": "Launches a new publication",
            "operationId": "createPublication",
            "parameters": [{
                "in": "body",
                "name": "title",
                "description": "Magazine Title",
                "required": false,
                "schema": {
                    "type": "string"
                }
            }, {
                "in": "body",
                "name": "url",
                "description": "Unique URL",
                "required": false,
                "schema": {
                    "type": "string"
                }
            }
...
}

A post to the url host:port/v1/publications with body

{title: "Mag", url: "mag"}

Throws and error

ValidationError: "title" must be a string
at Object.exports.process (/Users/skin/Projects/activeProjects/0123_ST_STProjects/smt-account-management/node_modules/swaggerize-express/node_modules/swaggerize-routes/node_modules/enjoi/node_modules/joi/lib/errors.js:140:17)
at internals.Any.validate (/Users/skin/Projects/activeProjects/0123_ST_STProjects/smt-account-management/node_modules/swaggerize-express/node_modules/swaggerize-routes/node_modules/enjoi/node_modules/joi/lib/any.js:667:25)
at validateParameter (/Users/skin/Projects/activeProjects/0123_ST_STProjects/smt-account-management/node_modules/swaggerize-express/node_modules/swaggerize-routes/lib/validator.js:107:28)
at validateInput (/Users/skin/Projects/activeProjects/0123_ST_STProjects/smt-account-management/node_modules/swaggerize-express/lib/expressroutes.js:82:9)

I have inspected request and it is definitely as it should be and is definitely reaching express.

This if block in question in expressroutes.js at present is as below.

if (param.in === 'body') {
    return {
        get: function(req) {
            return req.body;
        },
        set: function(req, key, val) {
            req.body = val;
        }
    };
}

How ever when inspection of the code believe it should be...

    if (param.in === 'body') {
    return {
        get: function(req, key) {
            return req.body[key];
        },
        set: function(req, key, val) {
            req.body[key] = val;
        }
    };
}

When this patch is applied everything works as expected.

issue.txt

Please could you confirm that this finding is correct or that I am reading this wrong.

Gary

req.body being overwritten

#55 introduced a bug where it would overwrite req.body even if it didn't make sense.

The offending block of code is

               switch (parameter.in) {
                    case 'path':
                    case 'query':
                        isPath = true;
                        value = req.params[parameter.name];
                        break;
                    case 'header':
                        value = req.get(parameter.name);
                        break;
                    case 'body':
                    case 'formData':
                        isBody = true;
                        value = req.body;
                }

                validate(value, function (error, newvalue) {
                    if (error) {
                        res.statusCode = 400;
                        next(error);
                        return;
                    }

                    if (isPath) {
                        req.params[parameter.name] = newvalue;
                    }

                    if (isBody) {
                      req.body = newvalue;
                    }

                    next();
                });

I had a request that took a username, password, remeber_me, it would loop through each of these things and run the validator against them, each time this would force the entire req.body to be the last thing validated, which in my case was remeber_me, causing my req.body to just be true, in the handlers.

I believe that the ACTUAL bug here is

                    case 'body':
                    case 'formData':
                        isBody = true;
                        value = req.body;

formData is not a singular entry like body, and it shouldn't be considered body. I think that isBody should be put in the case of body only, not in the case of formData.

ALSO while investigating this I realized that this is even worse if you are using non-object formData types, because enjoi will string them, turning your req.body, into a "[object Object]" as a string, which then means your req.body will be a string of "[object Object]" and you will find yourself cursing the weekend for changing your code.

I will be submitting my proposed fix for this bug shortly.

Relative paths not supported

I kept getting this with something like "$ref": "Address.json" in one of my definitions. The definition file had a sibling Address.json file in the same directory.

Here's the error:
callstack-exceeded-error

Not really sure how it could work though as swaggerize-express is expecting definitions to be an object:

must-be-an-object

See the docs for reference object

swagger-relative

"Missing required property: apis"

I tried creating a sample project based on the Swagger petstore (http://petstore.swagger.wordnik.com/). I downloaded the Swagger JSON from http://editor.swagger.wordnik.com/#/, and followed QUICKSTART.

When I run the following command:

node_modules/.bin/swaggerize --api spec.json --handlers handlers --tests tests

I get this message (as though it's not finding the 'apis' section in the Swagger file):

Missing required property: apis

Swagger file details below:

{
  "apiVersion": "1.0.0",
  "swaggerVersion": 1.2,
  "basePath": "http://petstore.swagger.wordnik.com/api",
  "authorizations": {
    "oauth2": {
      "type": "oauth2",
      "scopes": {
        "scope": "write",
        "description": "write to your albums"
      },
      "grantTypes": {
        "implicit": {
          "loginEndpoint": {
            "url": "http://petstore.swagger.wordnik.com/oauth/dialog"
          },
          "tokenName": "access_token"
        }
      }
    }
  },
  "info": {
    "title": "Petstore example API",
    "description": "This is a sample server Petstore server.  You can find out more about Swagger \\n    at <a href=\\http://swagger.wordnik.com\\>http://swagger.wordnik.com</a> or on irc.freenode.net,",
    "termsOfServiceUrl": "http://helloreverb.com/terms/",
    "contact": "[email protected]",
    "license": "Creative Commons 4.0 International",
    "licenseUrl": "http://creativecommons.org/licenses/by/4.0/"
  },
  "apiDeclarations": [
    {
      "apiVersion": 1,
      "swaggerVersion": 1.2,
      "basePath": "http://localhost:8002/api",
      "description": "Operations for Albums",
      "resourcePath": "/albums",
      "produces": [
        "application/json",
        "application/xml"
      ],
      "consumes": [
        "application/json",
        "application/xml"
      ],
      "apis": [
        {
          "path": "/albums",
          "operations": [
            {
              "method": "GET",
              "summary": "Retrieve albums of the user.",
              "notes": "Returns a list of all albums for the user.",
              "type": "array",
              "items": {
                "$ref": "Album"
              },
              "nickname": "getAlbums",
              "parameters": [
                {
                  "name": "quality",
                  "paramType": "query",
                  "description": "The quality of the album",
                  "type": "string",
                  "enum": [
                    "good",
                    "bad",
                    "terrible"
                  ],
                  "required": true
                },
                {
                  "name": "limit",
                  "description": "The maximum number of records to return.  Depending on the data, less values may be returned",
                  "required": false,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "query",
                  "minimum": 1,
                  "maximum": 20,
                  "defaultValue": 10
                },
                {
                  "name": "skip",
                  "description": "An offset for the first record to retrieve.  If there are less records than the skip, an empty result will be returned",
                  "required": false,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "query",
                  "minimum": 0,
                  "defaultValue": 0
                }
              ]
            },
            {
              "method": "POST",
              "summary": "Create a new album.",
              "notes": "Create a new album for the logged in user and returns its ID.",
              "type": "ID",
              "nickname": "createAlbum",
              "parameters": [
                {
                  "name": "body",
                  "description": "The new album to be created.",
                  "required": true,
                  "type": "InputAlbum",
                  "paramType": "body"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Invalid input."
                }
              ]
            }
          ]
        },
        {
          "path": "/albums/{albumId}",
          "operations": [
            {
              "method": "GET",
              "summary": "Retrieve a specific album.",
              "notes": "Returns a specific album of the logged-in user.",
              "type": "Album",
              "nickname": "getAlbum",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "Album not found."
                }
              ]
            },
            {
              "method": "DELETE",
              "summary": "Deletes an album.",
              "notes": "Deletes an album for the logged-in user.",
              "type": "void",
              "nickname": "deleteAlbum",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "Album not found."
                }
              ]
            },
            {
              "method": "PATCH",
              "summary": "Updates an album.",
              "notes": "Updates an album for the logged-in user.",
              "type": "void",
              "nickname": "updateAlbum",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "body",
                  "description": "The updated album.",
                  "required": true,
                  "type": "InputAlbum",
                  "paramType": "body"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "Album not found."
                }
              ]
            }
          ]
        },
        {
          "path": "/albums/{albumId}/images",
          "operations": [
            {
              "method": "GET",
              "summary": "Retrieve images.",
              "notes": "Returns a list of images in a given album.",
              "type": "array",
              "items": {
                "$ref": "Image"
              },
              "nickname": "getImages",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "limit",
                  "description": "The maximum number of records to return.  Depending on the data, less values may be returned",
                  "required": false,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "query",
                  "minimum": 1,
                  "maximum": 20,
                  "defaultValue": 10
                },
                {
                  "name": "skip",
                  "description": "An offset for the first record to retrieve.  If there are less records than the skip, an empty result will be returned",
                  "required": false,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "query",
                  "minimum": 0,
                  "defaultValue": 0
                }
              ]
            },
            {
              "method": "POST",
              "summary": "Upload a new image.",
              "notes": "Upload a new image into the specified album.",
              "type": "ID",
              "nickname": "uploadImage",
              "consumes": [
                "multipart/form-data"
              ],
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "image",
                  "description": "The new album to be created.",
                  "required": true,
                  "type": "File",
                  "paramType": "form"
                },
                {
                  "name": "title",
                  "description": "The title of the image.",
                  "required": false,
                  "type": "string",
                  "paramType": "query"
                },
                {
                  "name": "height",
                  "description": "The height of the image.",
                  "required": false,
                  "type": "integer",
                  "format": "int32",
                  "minimum": 1,
                  "paramType": "query"
                },
                {
                  "name": "width",
                  "description": "The width of the image.",
                  "required": false,
                  "type": "integer",
                  "format": "int32",
                  "minimum": 1,
                  "paramType": "query"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Either both height and width must be provided or neither."
                },
                {
                  "code": 500,
                  "message": "Failed to process or save the image."
                }
              ]
            }
          ]
        },
        {
          "path": "/albums/{albumId}/images/{imageId}",
          "operations": [
            {
              "method": "GET",
              "summary": "Retrieve a specific image.",
              "notes": "Returns a specific image from a given album of the logged-in user.",
              "type": "Image",
              "nickname": "getImage",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "imageId",
                  "description": "The ID of the image to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "Image not found."
                }
              ]
            },
            {
              "method": "DELETE",
              "summary": "Deletes an image.",
              "notes": "Deletes an image from an album of the logged-in user.",
              "type": "void",
              "nickname": "deleteAlbum",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "imageId",
                  "description": "The ID of the image to delete.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "Image not found."
                }
              ]
            },
            {
              "method": "PATCH",
              "summary": "Updates an image's metadata.",
              "notes": "Updates the metadata of a specific image. The image itself cannot be updated. In order to replace it, it must be deleted and added again.",
              "type": "void",
              "nickname": "updateImage",
              "parameters": [
                {
                  "name": "albumId",
                  "description": "The ID of the album to retrieve.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "imageId",
                  "description": "The ID of the image to delete.",
                  "required": true,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "path"
                },
                {
                  "name": "title",
                  "description": "The title of the image.",
                  "required": false,
                  "type": "string",
                  "paramType": "query"
                },
                {
                  "name": "height",
                  "description": "The height of the image.",
                  "required": false,
                  "type": "integer",
                  "format": "int32",
                  "minimum": 1,
                  "paramType": "query"
                },
                {
                  "name": "width",
                  "description": "The width of the image.",
                  "required": false,
                  "type": "integer",
                  "format": "int32",
                  "minimum": 1,
                  "paramType": "query"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Either both height and width must be provided or neither."
                },
                {
                  "code": 404,
                  "message": "Image not found."
                }
              ]
            }
          ]
        }
      ],
      "models": {
        "Album": {
          "id": "Album",
          "description": "An object describing an album that is received from various API calls.",
          "required": [
            "id",
            "name",
            "creationDate",
            "owner"
          ],
          "properties": {
            "id": {
              "$ref": "ID"
            },
            "name": {
              "type": "string"
            },
            "creationDate": {
              "type": "string",
              "format": "date-time"
            },
            "owner": {
              "type": "string"
            }
          }
        },
        "ID": {
          "id": "ID",
          "description": "A general identifier object.",
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "type": "integer",
              "format": "int64",
              "description": "A unique identifier for the album."
            }
          }
        },
        "InputAlbum": {
          "id": "InputAlbum",
          "description": "An object describing an album that is used to create or update albums.",
          "required": [
            "name"
          ],
          "properties": {
            "name": {
              "type": "string"
            }
          }
        },
        "Image": {
          "id": "Image",
          "description": "An object describing an image that is received from various API calls.",
          "required": [
            "id",
            "title",
            "creationDate",
            "url"
          ],
          "properties": {
            "id": {
              "$ref": "ID"
            },
            "title": {
              "type": "string",
              "description": "the title of the album"
            },
            "creationDate": {
              "type": "string",
              "format": "date-time",
              "description": "timestamp this album was created"
            },
            "url": {
              "type": "string",
              "description": "the public URL to the album"
            },
            "dimensions": {
              "$ref": "Dimensions"
            }
          }
        },
        "Dimensions": {
          "id": "Dimensions",
          "description": "The dimensions of an Image file.",
          "required": [
            "height",
            "width"
          ],
          "properties": {
            "height": {
              "type": "integer",
              "format": "int32",
              "minimum": 1,
              "description": "height of an image"
            },
            "width": {
              "type": "integer",
              "format": "int32",
              "minimum": 1,
              "description": "width of an image"
            }
          }
        }
      }
    },
    {
      "apiVersion": 1,
      "swaggerVersion": 1.2,
      "basePath": "http://localhost:8002/api",
      "description": "A sample API",
      "resourcePath": "/sample",
      "produces": [
        "application/json",
        "application/xml"
      ],
      "consumes": [
        "application/json",
        "application/xml"
      ],
      "apis": [
        {
          "path": "/demo",
          "operations": [
            {
              "method": "GET",
              "summary": "gets some demo items",
              "nickname": "demoGet",
              "type": "Widget",
              "parameters": [
                {
                  "name": "status",
                  "description": "the status of the demo items",
                  "paramType": "query",
                  "type": "integer",
                  "format": "int32",
                  "required": true,
                  "enum": [
                    1,
                    2,
                    3,
                    4
                  ],
                  "defaultValue": 1
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "invalid status requested"
                }
              ]
            }
          ]
        }
      ],
      "models": {
        "Widget": {
          "id": "Widget",
          "required": [
            "width",
            "height"
          ],
          "properties": {
            "height": {
              "type": "integer",
              "format": "int32",
              "description": "the height of the widget"
            },
            "width": {
              "type": "integer",
              "format": "int32",
              "description": "the height of the widget"
            }
          }
        }
      }
    },
    {
      "apiVersion": 1,
      "swaggerVersion": 1.2,
      "basePath": "http://localhost:8002/api",
      "description": "Operations for User Accounts",
      "resourcePath": "/users",
      "produces": [
        "application/json",
        "application/xml"
      ],
      "consumes": [
        "application/json",
        "application/xml"
      ],
      "apis": [
        {
          "path": "/users",
          "operations": [
            {
              "method": "POST",
              "summary": "Create user",
              "notes": "Registers a new user in the system.",
              "type": "void",
              "nickname": "createUser",
              "parameters": [
                {
                  "name": "body",
                  "description": "Created user object",
                  "required": true,
                  "type": "InputUser",
                  "paramType": "body"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Insufficient fields (must have at least username, email and password)."
                }
              ]
            },
            {
              "method": "GET",
              "summary": "Retrieves users in the system.",
              "notes": "Returns a list of all users in the system. This operation is allowed by admins only.",
              "type": "array",
              "items": {
                "$ref": "User"
              },
              "nickname": "getUsers",
              "parameters": [
                {
                  "enum": [
                    "good",
                    "bad",
                    "terrible"
                  ],
                  "name": "status",
                  "type": "string",
                  "paramType": "query"
                },
                {
                  "name": "limit",
                  "description": "The maximum number of records to return.  Depending on the data, less values may be returned",
                  "required": false,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "query",
                  "maximum": 20,
                  "defaultValue": 10
                },
                {
                  "name": "skip",
                  "description": "An offset for the first record to retrieve.  If there are less records than the skip, an empty result will be returned",
                  "required": false,
                  "type": "integer",
                  "format": "int64",
                  "paramType": "query",
                  "minimum": 0,
                  "defaultValue": 0
                }
              ],
              "responseMessages": [
                {
                  "code": 401,
                  "message": "Unauthorized request."
                }
              ]
            }
          ]
        },
        {
          "path": "/users/self",
          "operations": [
            {
              "method": "GET",
              "summary": "Get the logged-in user.",
              "notes": "Get the full information about the user that's currently logged-in.",
              "type": "User",
              "nickname": "getSelf",
              "parameters": []
            },
            {
              "method": "PUT",
              "summary": "Updates a user.",
              "notes": "Updates the currently logged-in user.",
              "type": "void",
              "nickname": "updateSelf",
              "parameters": [
                {
                  "name": "body",
                  "description": "Updated user object",
                  "required": true,
                  "type": "InputUser",
                  "paramType": "body"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Insufficient fields (must have at least email and password). Username cannot be modified."
                }
              ]
            },
            {
              "method": "DELETE",
              "summary": "Delete user.",
              "notes": "Deletes the account of the currently logged-in user.",
              "type": "void",
              "nickname": "deleteSelf"
            }
          ]
        },
        {
          "path": "/users/{username}",
          "operations": [
            {
              "method": "GET",
              "summary": "Get user by username.",
              "notes": "Get a user by a username. This operation is allowed only to admins.",
              "type": "User",
              "nickname": "getUser",
              "parameters": [
                {
                  "name": "username",
                  "description": "The username of the user to retrieve.",
                  "required": true,
                  "type": "string",
                  "paramType": "path"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "User not found."
                }
              ]
            },
            {
              "method": "PUT",
              "summary": "Updates a user.",
              "notes": "This can only be done by an admin.",
              "type": "void",
              "nickname": "updateUser",
              "parameters": [
                {
                  "name": "username",
                  "description": "The username to operate on",
                  "required": true,
                  "type": "string",
                  "paramType": "path"
                },
                {
                  "name": "body",
                  "description": "Updated user object",
                  "required": true,
                  "type": "InputUser",
                  "paramType": "body"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Insufficient fields (must have at least email and password). Username cannot be modified."
                }
              ]
            },
            {
              "method": "DELETE",
              "summary": "Delete user",
              "notes": "This can only be done by an admin.",
              "type": "void",
              "nickname": "deleteUser",
              "parameters": [
                {
                  "name": "username",
                  "description": "The username to operate on",
                  "required": true,
                  "type": "string",
                  "paramType": "path"
                }
              ],
              "responseMessages": [
                {
                  "code": 404,
                  "message": "User not found"
                }
              ]
            }
          ]
        },
        {
          "path": "/users/login",
          "operations": [
            {
              "method": "POST",
              "summary": "Logs user into the system.",
              "notes": null,
              "type": "string",
              "nickname": "loginUser",
              "parameters": [
                {
                  "name": "username",
                  "description": "The username to operate on",
                  "required": true,
                  "type": "string",
                  "paramType": "query"
                },
                {
                  "name": "password",
                  "description": "The password for login in clear text",
                  "required": true,
                  "type": "string",
                  "format": "password",
                  "paramType": "query"
                }
              ],
              "responseMessages": [
                {
                  "code": 400,
                  "message": "Invalid username and password combination."
                }
              ]
            }
          ]
        },
        {
          "path": "/users/logout",
          "operations": [
            {
              "method": "POST",
              "summary": "Logs out current logged in user session.",
              "notes": null,
              "type": "void",
              "nickname": "logoutUser",
              "parameters": []
            }
          ]
        }
      ],
      "models": {
        "User": {
          "id": "User",
          "required": [
            "username",
            "email"
          ],
          "properties": {
            "firstName": {
              "type": "string"
            },
            "username": {
              "type": "string"
            },
            "lastName": {
              "type": "string"
            },
            "email": {
              "type": "string"
            },
            "phone": {
              "type": "string"
            },
            "userStatus": {
              "type": "integer",
              "format": "int32",
              "description": "User Status",
              "enum": [
                "1-registered",
                "2-active",
                "3-closed"
              ]
            }
          }
        },
        "InputUser": {
          "id": "InputUser",
          "properties": {
            "firstName": {
              "type": "string"
            },
            "username": {
              "type": "string"
            },
            "lastName": {
              "type": "string"
            },
            "email": {
              "type": "string"
            },
            "password": {
              "type": "string"
            },
            "phone": {
              "type": "string"
            }
          }
        }
      }
    }
  ]
}

Why enjoi?

Probably missing something obvious, but was curious as to why you dont use joi directly in the express version but do in the hapi version!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.