From d7d7b6a320ed0028140b423f8236d1edbe54241d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Mass=C3=A9?= Date: Fri, 8 Sep 2017 14:32:39 +0200 Subject: [PATCH] serve a swagger file for each exposed api --- server.js | 61 ++++++++++++++- swagger.json | 205 +++++++++++++++++--------------------------------- swagger.yaml | 119 ----------------------------- test/specs.js | 29 +++++++ 4 files changed, 158 insertions(+), 256 deletions(-) delete mode 100644 swagger.yaml diff --git a/server.js b/server.js index 405d629..576118a 100644 --- a/server.js +++ b/server.js @@ -4,6 +4,8 @@ var _ = require("underscore"); var bodyParser = require('body-parser'); var util = require('util'); +var sample_swagger = require('./swagger.json'); + // ExpressJS Setup var app = express(); var router = express.Router(); @@ -18,8 +20,8 @@ var config = { }, "config": { "fields": { - "name": { "required": true }, - "price": { "required": true } + "name": { "required": true, "type": "string" }, + "price": { "required": true, "type": "integer" } } } } @@ -27,7 +29,9 @@ var config = { var admin_endpoints = [ { "url": "/debug/state", "verbs": [ "GET" ] }, - { "url": "/config/", "verbs": [ "GET" ] } + { "url": "/config/", "verbs": [ "GET" ] }, + { "url": "/config/{id}", "verbs": [ "GET" ] }, + { "url": "/config/{id}/swagger", "verbs": [ "GET" ] } ]; // Log every request @@ -54,6 +58,57 @@ router.get("/config",function(req,res){ ); }); +router.get("/config/:object/",function(req,res){ + var resource = req.params.object; + if (! (resource in config)) { + return error(res, 400, util.format("There is no resource '%s', try one of those resources : %s", resource, _.keys(config).join(", "))); + } + + success(res, 200, config[resource].config); +}); + +router.get("/config/:object/swagger",function(req,res){ + var resource = req.params.object; + if (! (resource in config)) { + return error(res, 400, util.format("There is no resource '%s', try one of those resources : %s", resource, _.keys(config).join(", "))); + } + + // Search and replace all occurences of __THINGS__ + var swagger = recursivedo(sample_swagger, (str) => { + return str.replace("__THINGS__", resource); + }); + + _.each(config[resource].config.fields, (params, id) => { + swagger.definitions.Thing.properties[id] = params; + swagger.definitions.PersistedThing.properties[id] = params; + }); + + success(res, 200, swagger); +}); + +// Recursively traverse a structure to do something +function recursivedo(input, fn) { + if (typeof input == "string") { + return fn(input); + } else if (input instanceof Array) { + var ret = []; + for (var i = 0; i < input.length; i++) { + ret.push(recursivedo(input[i], fn)); + } + return ret; + } else if (input instanceof Object) { + var ret = {}; + for (var property in input) { + if (input.hasOwnProperty(property)) { + ret[fn(property)] = recursivedo(input[property], fn); + } + } + return ret; + } else { + return input; + } +} + // Any GET on / ends up with a nice documentation as JSON router.get("/",function(req,res){ var response = { diff --git a/swagger.json b/swagger.json index bfee388..977c5ba 100644 --- a/swagger.json +++ b/swagger.json @@ -1,192 +1,129 @@ { "swagger": "2.0", "info": { - "version": "1.0.0", - "title": "Mockup API", - "description": "A simple API that exposes CRUD operations", + "title": "Mockup (__THINGS__)", + "description": "A simple API Mock that manages __THINGS__", "contact": { - "name": "Nicolas Masse" + "name": "Nicolas Massé", + "url": "https://github.com/nmasse-itix/API-Mockup" }, "license": { - "name": "MIT" - } + "name": "MIT License", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "1.0.0" }, - "host": "TODO", - "basePath": "/", - "schemes": [ - "https" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], "paths": { - "/things": { + "/__THINGS__/": { "get": { - "description": "Returns all available things", - "produces": [ - "application/json" - ], + "summary": "Get all __THINGS__", + "description": "Get all the __THINGS__.", "responses": { "200": { - "description": "A List of Things", + "description": "Success", "schema": { - "$ref": "#/definitions/ThingsList" + "type": "array", + "items": { + "type": "#/definitions/PersistedThing" + } } } - }, + } + }, + "post": { + "summary": "Create a __THINGS__", "parameters": [ { - "$ref": "#/parameters/user-key" + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/Thing" + } } - ] - }, - "post": { - "description": "Create a thing", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" ], "responses": { - "200": { - "description": "The created Thing", + "201": { + "description": "Success", "schema": { - "$ref": "#/definitions/Thing" + "$ref": "#/definitions/PersistedThing" } } - }, - "parameters": [ - { - "$ref": "#/parameters/user-key" - }, - { - "$ref": "#/parameters/body" - } - ] + } } }, - "/things/{id}": { + "/__THINGS__/{id}": { "get": { - "description": "Returns all available things", - "produces": [ - "application/json" - ], + "summary": "Get a __THINGS__ by id", "responses": { "200": { - "description": "A List of Things", + "description": "Success", "schema": { - "$ref": "#/definitions/ThingsList" + "$ref": "#/definitions/PersistedThing" } } - }, + } + }, + "put": { + "summary": "Update a __THINGS__", "parameters": [ { - "$ref": "#/parameters/user-key" - }, - { - "$ref": "#/parameters/id" + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/PersistedThing" + } } - ] - }, - "put": { - "description": "Update a Thing", - "produces": [ - "application/json" ], "responses": { "200": { - "description": "The updated Thing", + "description": "Success", "schema": { - "$ref": "#/definitions/Thing" + "$ref": "#/definitions/PersistedThing" } } - }, - "parameters": [ - { - "$ref": "#/parameters/user-key" - }, - { - "$ref": "#/parameters/id" - }, - { - "$ref": "#/parameters/body" - } - ] + } }, "delete": { - "description": "Delete a Thing", - "produces": [ - "application/json" - ], + "summary": "Delete a __THINGS__", "responses": { "200": { - "description": "The delete Thing", + "description": "Success", "schema": { - "$ref": "#/definitions/Thing" + "$ref": "#/definitions/PersistedThing" } } - }, - "parameters": [ - { - "$ref": "#/parameters/user-key" - }, - { - "$ref": "#/parameters/id" - } - ] - } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The __THINGS__ id", + "required": true, + "type": "integer" + } + ] } }, "definitions": { - "ThingsList": { - "type": "array", - "items": { - "$ref": "#/definitions/Thing" - } - }, "Thing": { + "title": "Definition of __THINGS__", + "description": "All __THINGS__ follow this definition", + "type": "object", + "properties": { } + }, + "PersistedThing": { + "title": "Definition of persisted __THINGS__", + "description": "All persisted __THINGS__ follow this definition", "type": "object", "properties": { - "name": { - "type": "string" - }, "id": { - "type": "number" - }, - "price": { - "type": "number" + "format": "int32", + "description": "The __THINGS__ id", + "type": "integer", + "required": true } } } - }, - "parameters": { - "user-key": { - "name": "user-key", - "in": "header", - "required": true, - "description": "The 3scale API Key", - "type": "string", - "x-data-threescale-name": "user_keys" - }, - "id": { - "name": "id", - "in": "path", - "required": true, - "description": "The things id", - "type": "number" - }, - "body": { - "name": "body", - "in": "body", - "required": true, - "description": "The Thing to create/update", - "schema": { - "$ref": "#/definitions/Thing" - } - } } } diff --git a/swagger.yaml b/swagger.yaml deleted file mode 100644 index 22a40a4..0000000 --- a/swagger.yaml +++ /dev/null @@ -1,119 +0,0 @@ ---- - swagger: "2.0" - info: - version: "1.0.0" - title: "Mockup API" - description: "A simple API that exposes CRUD operations" - contact: - name: "Nicolas Masse" - license: - name: "MIT" - host: "TODO" - basePath: "/" - schemes: - - "https" - consumes: - - "application/json" - produces: - - "application/json" - paths: - '/things': - get: - description: "Returns all available things" - produces: - - "application/json" - responses: - "200": - description: "A List of Things" - schema: - $ref: "#/definitions/ThingsList" - parameters: - - $ref: "#/parameters/user-key" - post: - description: "Create a thing" - consumes: - - "application/json" - produces: - - "application/json" - responses: - "200": - description: "The created Thing" - schema: - $ref: "#/definitions/Thing" - parameters: - - $ref: "#/parameters/user-key" - - $ref: "#/parameters/body" - '/things/{id}': - get: - description: "Returns all available things" - produces: - - "application/json" - responses: - "200": - description: "A List of Things" - schema: - $ref: "#/definitions/ThingsList" - parameters: - - $ref: "#/parameters/user-key" - - $ref: "#/parameters/id" - put: - description: "Update a Thing" - produces: - - "application/json" - responses: - "200": - description: "The updated Thing" - schema: - $ref: "#/definitions/Thing" - parameters: - - $ref: "#/parameters/user-key" - - $ref: "#/parameters/id" - - $ref: "#/parameters/body" - delete: - description: "Delete a Thing" - produces: - - "application/json" - responses: - "200": - description: "The delete Thing" - schema: - $ref: "#/definitions/Thing" - parameters: - - $ref: "#/parameters/user-key" - - $ref: "#/parameters/id" - definitions: - ThingsList: - type: "array" - items: - $ref: "#/definitions/Thing" - Thing: - type: "object" - properties: - name: - type: "string" - id: - type: "number" - price: - type: "number" - parameters: - user-key: - name: "user-key" - in: "header" - required: true - description: "The 3scale API Key" - type: "string" - x-data-threescale-name: "user_keys" - id: - name: "id" - in: "path" - required: true - description: "The things id" - type: "number" - body: - name: "body" - in: "body" - required: true - description: "The Thing to create/update" - schema: - $ref: "#/definitions/Thing" - diff --git a/test/specs.js b/test/specs.js index 326a352..7c6a553 100644 --- a/test/specs.js +++ b/test/specs.js @@ -35,6 +35,35 @@ describe('server.js tests', function () { .end(done); }); + it("Default config (by id)", (done) => { + request(server) + .get('/config/things/') + .expect('Content-Type', /^application[/]json/) + .expect(200) + .expect((res) => { + assert.ok(res.body instanceof Object, "body is Object"); + assert.ok(res.body.fields instanceof Object, "body.things.fields has fields"); + assert.ok('name' in res.body.fields, "body.things.fields has a field named 'name'"); + assert.ok('price' in res.body.fields, "body.things.fields has a field named 'price'"); + }) + .end(done); + }); + + it("Get the swagger of the default config", (done) => { + request(server) + .get('/config/things/swagger') + .expect('Content-Type', /^application[/]json/) + .expect(200) + .expect((res) => { + assert.ok(res.body instanceof Object, "body is Object"); + assert.equal(res.body.info.title, "Mockup (things)", "search-and-replace worked"); + assert.equal(res.body.definitions.PersistedThing.properties.id.required, true, "PersistedThing have a mandatory id"); + assert.equal(res.body.definitions.Thing.properties.name.required, true, "Thing have a mandatory name"); + assert.equal(res.body.definitions.Thing.properties.price.required, true, "Thing have a mandatory price"); + }) + .end(done); + }); + it("By default, the list of things is empty", (done) => { request(server) .get('/things/')