diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..59ab549 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +PRs are always welcome, and we'll be happy to merge them if the test suite passes. + +To run the entire test suite would take a long time, so we've broken it down into various groups: + +``` shell +TEST_TARGET=lint make test +TEST_TARGET=unit make test +TEST_TARGET=e2e make test +TEST_TARGET=cov make test +``` + +All of these will be run by Travis, but you could save the ice caps from melting a little by running at least `unit` and `e2e` before you push. + +Our sole requirement is that organizations that want to extend API-Flow to support their format write both a parser and a serializer, and not simply a serializer. diff --git a/README.md b/README.md index 4e0b4f2..4708e47 100644 --- a/README.md +++ b/README.md @@ -4,60 +4,72 @@ A flow written in ES6 using Immutable to convert between API description formats (Swagger, etc.) and other programs such as cURL command lines. -## What formats are supported and what will be in the future +## Format Support + We currently support: -- `Swagger v2.0 (in/out)` -- `RAML v1.0 (in/out)` -- `Postman Collection v2.0 (in/out)` -- `Paw v3.1 (in/out)` + +- Swagger v2.0 (in/out) +- RAML v1.0 (in/out) +- Postman Collection v2.0 (in/out) +- Paw v3.1 (in/out) We intend to support: -- `Swagger v3.0` -- `RAML v0.8` -- `Postman Collection v1.0` -- `Postman Dump v1.0` -- `Insomnia v3.0` -- `Api-Blueprint` + +- Swagger v3.0 +- RAML v0.8 +- Postman Collection v1.0 +- Postman Dump v1.0 +- Insomnia v3.0 +- API Blueprint - and many more. ## Installation -### from a cloned repository -just run +You can install this library in two different ways. + + +### Install via NPM/Yarn + +```shell +$ yarn add api-flow +# or +$ npm install api-flow +``` + +### Install from source + +Just run: ```sh git clone https://github.com/luckymarmot/API-Flow.git cd API-Flow -make install +yarn install +make ``` -This will install the node module dependencies - -## Building the different libraries -### node, web, and webworker - -run the following command to build API-Flow for the different environments that you need +This will install the node module dependencies, but you will need to build API-Flow for the different [environments](src/environments) that you need: ```sh # use TARGET="node" if you only want the node library make runners TARGET="node web webworker" ``` -### Paw - -You can use the following command to add the different extensions to Paw +You can use the following command to compile different extensions. ```sh # use TARGET="swagger" if you only want the swagger bindings make transfer TARGET="swagger raml1 postman2" ``` -## Using the npm module -### as a standard library +## Usage + +### Standard Library ```js -const ApiFlow = require('api-flow'); // if from npm -const ApiFlow = require('./dist/node/api-flow.js'); // if from `make runners TARGET="node"` +const ApiFlow = require('api-flow').default; // if from yarn/npm +const ApiFlow = require('./dist/node/api-flow.js').default; // if from `make runners TARGET="node"` + +const path = require('path'); const options = { source: { @@ -80,22 +92,19 @@ promise.then((data) => { }) ``` -### Using as a CLI (coming soon) -```sh -node ./bin/api-flow.js some_swagger.json -f swagger -t raml > converted.yml -``` - ### User Interface API-Flow is one of the main components of [Console.REST](https://github.com/luckymarmot/console-rest). If you're an API user, you can easily use [https://console.rest/](https://console.rest/) to convert API description files. If you're an API provider, you can add a button to your API docs to let your users open and play with your API in client apps including Paw or Postman. ## Contributing -PRs are welcomed! +Read [CONTRIBUTING.md](CONTRIBUTING.md) for more tips on testing and contributing. + Our sole requirement is that organizations that want to extend API-Flow to support their format write both a parser and a serializer, and not simply a serializer. ## Documentation -You can find more information about the internal structure of API-Flow in [src](https://github.com/luckymarmot/API-Flow/tree/develop/src). We've also created a set of templates to help speed up the extension process: [loader](https://github.com/luckymarmot/API-Flow/tree/develop/src/loaders/template/v1.0), [parser](https://github.com/luckymarmot/API-Flow/tree/develop/src/parsers/template/v1.0/), and [environment](https://github.com/luckymarmot/API-Flow/tree/develop/src/environments/template) + +You can find more information about the internal structure of API-Flow in [src](src). We've also created a set of templates to help speed up the extension process: [loader](src/loaders/template/v1.0), [parser](src/parsers/template/v1.0/), and [environment](src/environments/template) ## License @@ -104,4 +113,4 @@ Copyright © 2016 Paw Inc. ## Contributors -See [Contributors](https://github.com/luckymarmot/API-Flow/graphs/contributors). +See [Contributors](https://github.com/JonathanMontane/API-Flow/graphs/contributors). diff --git a/configs/node/api-flow-config.js b/configs/node/api-flow-config.js index f6cad0f..66b58de 100644 --- a/configs/node/api-flow-config.js +++ b/configs/node/api-flow-config.js @@ -2,34 +2,34 @@ import Environment from '../../src/environments/node/Environment' import SwaggerLoader from '../../src/loaders/swagger/Loader' import RAMLLoader from '../../src/loaders/raml/Loader' -import PostmanV2Loader from '../../src/loaders/postman/v2.0/Loader' +import PostmanCollectionV2Loader from '../../src/loaders/postman/v2.0/Loader' import SwaggerV2Parser from '../../src/parsers/swagger/v2.0/Parser' import RAMLV1Parser from '../../src/parsers/raml/v1.0/Parser' -import PostmanV2Parser from '../../src/loaders/postman/v2.0/Parser' +import PostmanCollectionV2Parser from '../../src/parsers/postman/v2.0/Parser' import SwaggerV2Serializer from '../../src/serializers/swagger/v2.0/Serializer' import RAMLV1Serializer from '../../src/serializers/raml/v1.0/Serializer' -import PostmanV2Serializer from '../../src/serializers/postman/v2.0/Serializer' +import PostmanCollectionV2Serializer from '../../src/serializers/postman/v2.0/Serializer' import InternalSerializer from '../../src/serializers/internal/Serializer' export const loaders = [ SwaggerLoader, RAMLLoader, - PostmanV2Loader + PostmanCollectionV2Loader ] export const parsers = [ SwaggerV2Parser, RAMLV1Parser, - PostmanV2Parser + PostmanCollectionV2Parser ] export const serializers = [ SwaggerV2Serializer, RAMLV1Serializer, InternalSerializer, - PostmanV2Serializer + PostmanCollectionV2Serializer ] export const environment = Environment diff --git a/configs/web/api-flow-config.js b/configs/web/api-flow-config.js index 12e25e9..6c3bbf7 100644 --- a/configs/web/api-flow-config.js +++ b/configs/web/api-flow-config.js @@ -2,21 +2,21 @@ import Environment from '../../src/environments/web/Environment' import SwaggerLoader from '../../src/loaders/swagger/Loader' import RAMLLoader from '../../src/loaders/raml/Loader' -import PostmanV2Loader from '../../src/loaders/postman/v2.0/Loader' +import PostmanCollectionV2Loader from '../../src/loaders/postman/v2.0/Loader' import SwaggerV2Parser from '../../src/parsers/swagger/v2.0/Parser' import RAMLV1Parser from '../../src/parsers/raml/v1.0/Parser' -import PostmanV2Parser from '../../src/loaders/postman/v2.0/Parser' +import PostmanCollectionV2Parser from '../../src/parsers/postman/v2.0/Parser' import SwaggerV2Serializer from '../../src/serializers/swagger/v2.0/Serializer' import RAMLV1Serializer from '../../src/serializers/raml/v1.0/Serializer' -import PostmanV2Serializer from '../../src/serializers/postman/v2.0/Serializer' +import PostmanCollectionV2Serializer from '../../src/serializers/postman/v2.0/Serializer' import InternalSerializer from '../../src/serializers/internal/Serializer' export const loaders = [ SwaggerLoader, RAMLLoader, - PostmanV2Loader + PostmanCollectionV2Loader ] export const parsers = [ @@ -29,7 +29,7 @@ export const serializers = [ SwaggerV2Serializer, RAMLV1Serializer, InternalSerializer, - PostmanV2Serializer + PostmanCollectionV2Serializer ] export const environment = Environment diff --git a/configs/webworker/api-flow-config.js b/configs/webworker/api-flow-config.js index 12e25e9..25a0ace 100644 --- a/configs/webworker/api-flow-config.js +++ b/configs/webworker/api-flow-config.js @@ -2,34 +2,34 @@ import Environment from '../../src/environments/web/Environment' import SwaggerLoader from '../../src/loaders/swagger/Loader' import RAMLLoader from '../../src/loaders/raml/Loader' -import PostmanV2Loader from '../../src/loaders/postman/v2.0/Loader' +import PostmanCollectionV2Loader from '../../src/loaders/postman/v2.0/Loader' import SwaggerV2Parser from '../../src/parsers/swagger/v2.0/Parser' import RAMLV1Parser from '../../src/parsers/raml/v1.0/Parser' -import PostmanV2Parser from '../../src/loaders/postman/v2.0/Parser' +import PostmanCollectionV2Parser from '../../src/parsers/postman/v2.0/Parser' import SwaggerV2Serializer from '../../src/serializers/swagger/v2.0/Serializer' import RAMLV1Serializer from '../../src/serializers/raml/v1.0/Serializer' -import PostmanV2Serializer from '../../src/serializers/postman/v2.0/Serializer' +import PostmanCollectionV2Serializer from '../../src/serializers/postman/v2.0/Serializer' import InternalSerializer from '../../src/serializers/internal/Serializer' export const loaders = [ SwaggerLoader, RAMLLoader, - PostmanV2Loader + PostmanCollectionV2Loader ] export const parsers = [ SwaggerV2Parser, RAMLV1Parser, - PostmanV2Parser + PostmanCollectionV2Parser ] export const serializers = [ SwaggerV2Serializer, RAMLV1Serializer, InternalSerializer, - PostmanV2Serializer + PostmanCollectionV2Serializer ] export const environment = Environment diff --git a/configs/webworker/webpack.config.babel.js b/configs/webworker/webpack.config.babel.js index f47ef76..df6072b 100644 --- a/configs/webworker/webpack.config.babel.js +++ b/configs/webworker/webpack.config.babel.js @@ -4,7 +4,7 @@ const config = { target: 'webworker', entry: path.resolve(__dirname, './api-flow.js'), output: { - path: path.resolve(__dirname, '../../../dist/node/'), + path: path.resolve(__dirname, '../../../dist/webworker/'), filename: 'api-flow.js', libraryTarget: 'umd' }, diff --git a/src/api-flow-config.js b/src/api-flow-config.js index f7500aa..5d7b0ce 100644 --- a/src/api-flow-config.js +++ b/src/api-flow-config.js @@ -13,7 +13,7 @@ import PostmanCollectionV2Parser from './parsers/postman/v2.0/Parser' import SwaggerV2Serializer from './serializers/swagger/v2.0/Serializer' import RAMLV1Serializer from './serializers/raml/v1.0/Serializer' import InternalSerializer from './serializers/internal/Serializer' -import PostmanV2Serializer from './serializers/postman/v2.0/Serializer' +import PostmanCollectionV2Serializer from './serializers/postman/v2.0/Serializer' import ApiBlueprint1ASerializer from './serializers/api-blueprint/1A/Serializer' export const loaders = [ @@ -34,7 +34,7 @@ export const serializers = [ SwaggerV2Serializer, RAMLV1Serializer, InternalSerializer, - PostmanV2Serializer, + PostmanCollectionV2Serializer, ApiBlueprint1ASerializer ] diff --git a/src/loaders/postman/v2.0/Loader.js b/src/loaders/postman/v2.0/Loader.js index 96f8481..822e8e4 100644 --- a/src/loaders/postman/v2.0/Loader.js +++ b/src/loaders/postman/v2.0/Loader.js @@ -270,17 +270,27 @@ methods.normalizeRequestURL = (item) => { return item } - if (typeof item.request.url === 'string') { - item.request.urlString = item.request.url - item.request.url = methods.createPostmanURLObjectFromURLString(item.request.urlString) - } - else { - if (item.request.url && !item.request.url.domain && item.request.url.host) { - item.request.url.domain = item.request.url.host + let { url, urlString } = item.request + + if (typeof url === 'object') { + // Lets just use this raw string, its gonna have everything + if (url.raw) { + item.request.urlString = url.raw + item.request.url = methods.createPostmanURLObjectFromURLString(url.raw) + return item + } + // cater to some random bug before we do the object normalization + if (!url.domain && url.host) { + item.request.url.domain = url.host } item.request.urlString = methods.createPostmanURLStringFromURLObject(item.request.url) + return item } + // It's not an object, hope its a string or numeric that'll act like a string + item.request.urlString = url + item.request.url = methods.createPostmanURLObjectFromURLString(url) + return item } diff --git a/src/loaders/postman/v2.0/__tests__/Loader.spec.js b/src/loaders/postman/v2.0/__tests__/Loader.spec.js index 7131432..25153d0 100644 --- a/src/loaders/postman/v2.0/__tests__/Loader.spec.js +++ b/src/loaders/postman/v2.0/__tests__/Loader.spec.js @@ -436,12 +436,14 @@ describe('loaders/postman/v2.0/Loader.js', () => { const inputs = [ { request: { url: '123' } }, { request: { url: 234 } }, - { request: { url: { host: 345 } } } + { request: { url: { raw: 345 } } }, + { request: { url: { host: 456 } } } ] const expected = [ { request: { url: '123123', urlString: '123' } }, - { request: { url: 234, urlString: 234 / 2 } }, - { request: { url: { host: 345, domain: 345 }, urlString: 345 / 2 } } + { request: { url: 234 * 2, urlString: 234 } }, + { request: { url: 345 * 2, urlString: 345 } }, + { request: { url: { host: 456, domain: 456 }, urlString: 456 / 2 } } ] const actual = inputs.map(input => __internals__.normalizeRequestURL(input)) expect(actual).toEqual(expected) diff --git a/src/parsers/swagger/v2.0/Parser.js b/src/parsers/swagger/v2.0/Parser.js index a2f9043..5c30291 100644 --- a/src/parsers/swagger/v2.0/Parser.js +++ b/src/parsers/swagger/v2.0/Parser.js @@ -1252,6 +1252,12 @@ methods.convertParameterObjectIntoParameter = (parameterEntry) => { applicableContexts } + if (typeof parameter.example !== 'undefined') { + paramInstance.examples = List([parameter.example]) + } else if (typeof parameter['x-example'] !== 'undefined') { + paramInstance.examples = List([parameter['x-example']]) + } + if (parameter.type === 'array' && parameter.items) { const { value } = methods.convertParameterObjectIntoParameter({ key: null, diff --git a/src/parsers/swagger/v2.0/__tests__/Parser.spec.js b/src/parsers/swagger/v2.0/__tests__/Parser.spec.js index cd2469d..fa7faa7 100644 --- a/src/parsers/swagger/v2.0/__tests__/Parser.spec.js +++ b/src/parsers/swagger/v2.0/__tests__/Parser.spec.js @@ -2772,7 +2772,8 @@ describe('parsers/swagger/v2.0/Parser.js', () => { maximum: 321, minimum: 123, multipleOf: 5, - default: 100 + default: 100, + example: 12345 } } @@ -2787,6 +2788,9 @@ describe('parsers/swagger/v2.0/Parser.js', () => { required: true, type: 'integer', default: 100, + examples: List([ + 12345 + ]), constraints: List([ new Constraint.Maximum(321), new Constraint.Minimum(123), @@ -2816,7 +2820,8 @@ describe('parsers/swagger/v2.0/Parser.js', () => { maximum: 321, minimum: 123, multipleOf: 5, - default: 100 + default: 100, + example: 12345 } } } @@ -2838,6 +2843,7 @@ describe('parsers/swagger/v2.0/Parser.js', () => { required: true, type: 'integer', default: 100, + examples: List([12345]), constraints: List([ new Constraint.Maximum(321), new Constraint.Minimum(123), @@ -2920,6 +2926,66 @@ describe('parsers/swagger/v2.0/Parser.js', () => { const actual = inputs.map(input => __internals__.convertParameterObjectIntoParameter(input)) expect(actual).toEqual(expected) }) + + it('should respect x-example too', () => { + const inputs = [ + { + key: 'UserId', + value: { + name: 'userId', + in: 'query', + type: 'integer', + required: true, + 'x-example': 23456 + } + }, + { + key: 'ShowThing', + value: { + name: 'showThing', + in: 'query', + type: 'boolean', + 'x-example': false + } + } + ] + + const expected = [ + { + key: 'UserId', + value: new Parameter({ + key: 'userId', + name: 'userId', + in: 'queries', + uuid: 'UserId', + required: true, + type: 'integer', + examples: List([ + 23456 + ]), + constraints: List() + }) + }, + { + key: 'ShowThing', + value: new Parameter({ + key: 'showThing', + name: 'showThing', + in: 'queries', + uuid: 'ShowThing', + required: false, + type: 'boolean', + examples: List([ + false + ]), + constraints: List() + }) + } + ] + + const actual = inputs.map(input => __internals__.convertParameterObjectIntoParameter(input)) + expect(actual).toEqual(expected) + }) }) describe('@convertParameterObjectArrayIntoParameterMap', () => { diff --git a/src/serializers/postman/v2.0/Serializer.js b/src/serializers/postman/v2.0/Serializer.js index 70f68f1..ac767fa 100644 --- a/src/serializers/postman/v2.0/Serializer.js +++ b/src/serializers/postman/v2.0/Serializer.js @@ -620,7 +620,7 @@ methods.createHeaderFromParameter = (param) => { return { key, value: schema.enum[0] } } - return { key, value: null } + return { key, value: '' } } /** diff --git a/src/serializers/postman/v2.0/__tests__/Serializer.spec.js b/src/serializers/postman/v2.0/__tests__/Serializer.spec.js index 94ffeb4..711d41e 100644 --- a/src/serializers/postman/v2.0/__tests__/Serializer.spec.js +++ b/src/serializers/postman/v2.0/__tests__/Serializer.spec.js @@ -844,7 +844,7 @@ describe('serializers/swagger/v2.0/Serializer.js', () => { ] const expected = [ null, - { key: 123, value: null }, + { key: 123, value: '' }, { key: 234, value: 'abc' }, { key: 345, value: 'def' } ] diff --git a/src/serializers/swagger/v2.0/Serializer.js b/src/serializers/swagger/v2.0/Serializer.js index 77b27e9..d809985 100644 --- a/src/serializers/swagger/v2.0/Serializer.js +++ b/src/serializers/swagger/v2.0/Serializer.js @@ -493,18 +493,12 @@ methods.getSchemaFromResponse = (response) => { * @returns {SwaggerResponseObject} the corresponding swagger response object. */ methods.convertResponseRecordToResponseObject = (store, { key, value }) => { - const response = {} - - if (value.get('description')) { - response.description = value.get('description') - } - else { - response.description = 'no description was provided for this response' + const response = { + description: value.get('description') || '', + headers: methods.getHeadersFromResponse(store, value), + schema: methods.getSchemaFromResponse(value) } - response.headers = methods.getHeadersFromResponse(store, value) - response.schema = methods.getSchemaFromResponse(value) - return { key, value: response @@ -1167,7 +1161,7 @@ methods.getResponsesFromRequest = (store, request) => { if (Object.keys(responses).length === 0) { return { default: { - description: 'no response description was provided for this operation' + description: '' } } } diff --git a/src/serializers/swagger/v2.0/__tests__/Serializer.spec.js b/src/serializers/swagger/v2.0/__tests__/Serializer.spec.js index e5765f9..585e4fe 100644 --- a/src/serializers/swagger/v2.0/__tests__/Serializer.spec.js +++ b/src/serializers/swagger/v2.0/__tests__/Serializer.spec.js @@ -909,7 +909,7 @@ describe('serializers/swagger/v2.0/Serializer.js', () => { } const expectedValue = { - description: 'no description was provided for this response', + description: '', headers: { userId: { type: 'string' }, petId: { type: 'number' } @@ -2408,21 +2408,21 @@ describe('serializers/swagger/v2.0/Serializer.js', () => { get: { responses: { default: { - description: 'no response description was provided for this operation' + description: '' } } }, post: { responses: { default: { - description: 'no response description was provided for this operation' + description: '' } } }, put: { responses: { default: { - description: 'no response description was provided for this operation' + description: '' } } } diff --git a/testing/e2e/internal-postman2/test-case-0/output.json b/testing/e2e/internal-postman2/test-case-0/output.json index 91c1177..8759556 100644 --- a/testing/e2e/internal-postman2/test-case-0/output.json +++ b/testing/e2e/internal-postman2/test-case-0/output.json @@ -170,7 +170,7 @@ "header": [ { "key": "api_key", - "value": null + "value": "" }, { "key": "Content-Type", @@ -362,4 +362,4 @@ "name": "Content-Type" } ] -} \ No newline at end of file +} diff --git a/testing/e2e/internal-swagger2/e2e.spec.js b/testing/e2e/internal-swagger2/e2e.spec.js index 882e69a..f698da1 100644 --- a/testing/e2e/internal-swagger2/e2e.spec.js +++ b/testing/e2e/internal-swagger2/e2e.spec.js @@ -81,8 +81,7 @@ describe('internal -> swagger v2', () => { } catch (e) { console.error(e.stack) - expect(true).toEqual(false) - done() + done(new Error('unexpected error')) } /* eslint-enable no-console */ }) diff --git a/testing/e2e/internal-swagger2/test-case-1/output.json b/testing/e2e/internal-swagger2/test-case-1/output.json index 607cb61..3193461 100644 --- a/testing/e2e/internal-swagger2/test-case-1/output.json +++ b/testing/e2e/internal-swagger2/test-case-1/output.json @@ -29,7 +29,7 @@ ], "responses": { "default": { - "description": "no response description was provided for this operation" + "description": "" } }, "security": [ @@ -55,7 +55,7 @@ ], "responses": { "default": { - "description": "no response description was provided for this operation" + "description": "" } } } @@ -67,7 +67,7 @@ ], "responses": { "200": { - "description": "no description was provided for this response", + "description": "", "schema": { "$ref": "#/definitions/AnotherEntry" } @@ -81,7 +81,7 @@ ], "responses": { "200": { - "description": "no description was provided for this response", + "description": "", "schema": { "type": "object" } @@ -109,7 +109,7 @@ ], "responses": { "default": { - "description": "no response description was provided for this operation" + "description": "" } }, "security": [ @@ -129,7 +129,7 @@ ], "responses": { "default": { - "description": "no response description was provided for this operation" + "description": "" } } } @@ -142,7 +142,7 @@ ], "responses": { "200": { - "description": "no description was provided for this response", + "description": "", "schema": { "$ref": "#/definitions/SongsLib.Song" } diff --git a/testing/e2e/postman-collection2-internal/test-case-0/input.json b/testing/e2e/postman-collection2-internal/test-case-0/input.json index e2a5e26..6d1ac07 100644 --- a/testing/e2e/postman-collection2-internal/test-case-0/input.json +++ b/testing/e2e/postman-collection2-internal/test-case-0/input.json @@ -362,17 +362,6 @@ "request": { "url": { "raw": "https://6-dot-authentiqio.appspot.com/scope/:job", - "protocol": "https", - "auth": {}, - "host": [ - "6-dot-authentiqio", - "appspot", - "com" - ], - "path": [ - "scope", - ":job" - ], "variable": [ { "value": "{{job}}",