├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── greenkeeper.json ├── package.json ├── src ├── adapter.js ├── annotations.js ├── ast.js ├── generator.js ├── headers.js ├── json-schema.js ├── link.js ├── media-type.js ├── parser.js ├── schema.js └── uri-template.js ├── test ├── adapter.js ├── fixtures │ ├── ast-path-with-reference-sibling.json │ ├── ast-path-with-reference-sibling.yaml │ ├── auth-extensions.json │ ├── auth-extensions.yaml │ ├── auth-multi-consumes.json │ ├── auth-multi-consumes.yaml │ ├── circular-example.json │ ├── circular-example.yaml │ ├── consumes-invalid-type.json │ ├── consumes-invalid-type.yaml │ ├── consumes-multipart-file.json │ ├── consumes-multipart-file.yaml │ ├── data-structure-generation-nullable-member.json │ ├── data-structure-generation-nullable-member.yaml │ ├── data-structure-generation-ref.json │ ├── data-structure-generation-ref.yaml │ ├── data-structure-generation.json │ ├── data-structure-generation.yaml │ ├── external-dereferencing.json │ ├── external-dereferencing.yaml │ ├── headers-type-warning.json │ ├── headers-type-warning.yaml │ ├── invalid-media-type.json │ ├── invalid-media-type.yaml │ ├── invalid-reference.json │ ├── invalid-reference.yaml │ ├── json-body-generation.json │ ├── json-body-generation.yaml │ ├── non-existing-elm-in-sequence.json │ ├── non-existing-elm-in-sequence.yaml │ ├── operation-consumes-invalid-type.json │ ├── operation-consumes-invalid-type.yaml │ ├── operation-extension.json │ ├── operation-extension.yaml │ ├── operation-produces-invalid-type.json │ ├── operation-produces-invalid-type.yaml │ ├── parameter-array-default-warning.json │ ├── parameter-array-default-warning.yaml │ ├── parameters-header-type-warning.json │ ├── parameters-header-type-warning.yaml │ ├── produces-invalid-type.json │ ├── produces-invalid-type.yaml │ ├── request-body-primitive.json │ ├── request-body-primitive.yaml │ ├── string.json │ ├── string.yaml │ ├── x-summary-type.json │ ├── x-summary-type.yaml │ ├── yaml-error.json │ └── yaml-error.yaml ├── generator.js ├── inherit-parameters.js ├── json-schema.js ├── media-type.js ├── parameter.js ├── parser.js ├── schema.js ├── uri-template.js └── with-path.js └── tonic-example.js /.babelrc: -------------------------------------------------------------------------------- 1 | // Generated by Peasant 2 | { 3 | "presets": [["env", {"targets": {"node": "6"}}]], 4 | "plugins": ["transform-runtime"] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: http://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | // Generated by Peasant 2 | { 3 | "extends": "airbnb/base", 4 | "env": { 5 | "mocha": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib 3 | .vagrant 4 | npm-debug.log 5 | package-lock.json 6 | .DS_Store 7 | coverage 8 | .eslintcache 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | .travis.yml 3 | circle.yml 4 | .git* 5 | coverage 6 | .vagrant 7 | .npmignore 8 | .eslintcache 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '10' 5 | - '8' 6 | - '6' 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npm install -g codeclimate-test-reporter 11 | - npm run cover 12 | - codeclimate-test-reporter < coverage/lcov.info 13 | notifications: 14 | email: false 15 | slack: 16 | secure: vduz8OTsQ2hfb4N0Gtx4RK0lVIn4rMNva3OdCrQ7ZHKxmpiQ7oHhtL8gqcbJga+zEqGnnAsvJpdXEXUeN7bX1FZ4m7oexUIW92pcysp9DH1q8boZK79UZ1mcMcOh61ZShUG+YhWZ0qnGyR+s+Ufc6QBG0S/GPrRWCdwx9cobx5sDFc5BLW1QhubBy8jmDGXp0Y291/KRiaiunbKBm9jtFfcxqiAg4E3Jn74c2BekUYOwRhXYi6BxWSiR9/tI5p8Scg9QCt0NrK11245Tz6jt0A1VgV02JIFRCKcvUDt1zPncGN7fre2vktFhMiw2IXSuQxUxZ5Jpgtj249/rSG0MCAUmXm9BGCLrt7wyPiBGPlXUYhoNrUZVW/PMWVmawPFx/TOpOPbGxiH2EMjSS/+2wiMng3Uu5L1aHvGe9nJc8MzZXkSI6zVraJmE483UZX2BHWOGS3yf4Apoxeqylkg0qUQZ3hPJuWlIUffZrF4XJYMdR/2pgWSE6xe/c0g87I3ZSO44Tsga6oWlBYSxvmGMqlaidc55mkBRZEdXoRa5fu/lybt5NghGrLyBQoQdAkz/irWajAULn5cPRu5593eU8qwMHCWWPxrZ16FzNbEZDYc4Ff1yHO7uuqE36ZznmX04IYZ/BDE5wwJER1C3q5VXqEWmROVLKXXhbWwFWa6UQ3I= 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Apiary Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fury Swagger 2.0 Adapter 2 | 3 | This repository has moved location to [API Elements: JS](https://github.com/apiaryio/api-elements.js). 4 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["swagger-zoo"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fury-adapter-swagger", 3 | "version": "0.22.7", 4 | "description": "Swagger 2.0 parser for Fury.js", 5 | "main": "./lib/adapter.js", 6 | "tonicExampleFilename": "tonic-example.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/apiaryio/fury-adapter-swagger.git" 10 | }, 11 | "scripts": { 12 | "test": "peasant test", 13 | "ci": "peasant -s lint test build", 14 | "prepublish": "npm run ci", 15 | "cover": "peasant cover", 16 | "peasant": "peasant", 17 | "lint": "peasant lint" 18 | }, 19 | "dependencies": { 20 | "babel-runtime": "^6.23.0", 21 | "content-type": "^1.0.4", 22 | "js-yaml": "^3.4.2", 23 | "json-schema-faker": "0.5.0-rc16", 24 | "lodash": "^4.15.0", 25 | "media-typer": "^1.0.1", 26 | "swagger-parser": "^6.0.2", 27 | "yaml-js": "^0.2.3", 28 | "z-schema": "^3.16.1" 29 | }, 30 | "peerDependencies": { 31 | "fury": "3.0.0-beta.7" 32 | }, 33 | "devDependencies": { 34 | "chai": "^4.1.2", 35 | "fury": "3.0.0-beta.7", 36 | "glob": "^7.1.2", 37 | "peasant": "1.1.0", 38 | "swagger-zoo": "2.19.2" 39 | }, 40 | "engines": { 41 | "node": ">=6" 42 | }, 43 | "author": "Apiary.io ", 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /src/adapter.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Parser from './parser'; 3 | 4 | export const name = 'swagger'; 5 | 6 | // TODO: Figure out media type for Swagger 2.0 7 | export const mediaTypes = [ 8 | 'application/swagger+json', 9 | 'application/swagger+yaml', 10 | ]; 11 | 12 | export function detect(source) { 13 | return !!(_.isString(source) 14 | ? source.match(/"?swagger"?\s*:\s*["']2\.0["']/g) 15 | : source.swagger === '2.0'); 16 | } 17 | 18 | /* 19 | * Parse Swagger 2.0 into Refract elements 20 | */ 21 | export function parse(options, done) { 22 | const parser = new Parser(options); 23 | parser.parse(done); 24 | } 25 | 26 | export default { 27 | name, mediaTypes, detect, parse, 28 | }; 29 | -------------------------------------------------------------------------------- /src/annotations.js: -------------------------------------------------------------------------------- 1 | // These describe the type of annotations that are produced by this parser 2 | // and assigns a unique code to each one. Downstream applications can use this 3 | // code to group similar types of annotations together. 4 | export default { 5 | CANNOT_PARSE: { 6 | type: 'error', 7 | code: 1, 8 | fragment: 'yaml-parser', 9 | }, 10 | AST_UNAVAILABLE: { 11 | type: 'warning', 12 | code: 2, 13 | fragment: 'yaml-parser', 14 | }, 15 | DATA_LOST: { 16 | type: 'warning', 17 | code: 3, 18 | fragment: 'refract-not-supported', 19 | }, 20 | VALIDATION_ERROR: { 21 | type: 'error', 22 | code: 4, 23 | fragment: 'swagger-validation', 24 | }, 25 | UNCAUGHT_ERROR: { 26 | type: 'error', 27 | code: 5, 28 | fragment: 'uncaught-error', 29 | }, 30 | VALIDATION_WARNING: { 31 | type: 'warning', 32 | code: 6, 33 | fragment: 'swagger-validation', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/ast.js: -------------------------------------------------------------------------------- 1 | // A module for dealing with YAML syntax trees and looking up source map 2 | // location information. 3 | 4 | import _ from 'lodash'; 5 | import yamlAst from 'yaml-js'; 6 | 7 | export default class Ast { 8 | constructor(source) { 9 | this.root = yamlAst.compose(source); 10 | } 11 | 12 | // Look up a position in the original source based on a JSON path, for 13 | // example ['paths', '/test', 'get', 'responses', '200']. Also supported 14 | // is using a string ('paths./test.get') but it does not understand any 15 | // escaping. 16 | getPosition(path) { 17 | const pieces = _.isArray(path) ? [].concat(path) : path.split('.'); 18 | let end; 19 | let node = this.root; 20 | let piece = pieces.shift(); 21 | let start; 22 | 23 | if (!node) { 24 | return null; 25 | } 26 | 27 | while (piece !== undefined) { 28 | let newNode = null; 29 | 30 | if (node.tag === 'tag:yaml.org,2002:map') { 31 | // This is a may / object with key:value pairs. 32 | // eslint-disable-next-line no-restricted-syntax 33 | for (const subNode of node.value) { 34 | if (subNode[0] && subNode[0].value === piece) { 35 | [, newNode] = subNode; 36 | 37 | if (!pieces.length) { 38 | // This is the last item! 39 | start = subNode[0].start_mark; 40 | end = subNode[1].end_mark; 41 | } 42 | break; 43 | } 44 | } 45 | } else if (node.tag === 'tag:yaml.org,2002:seq') { 46 | // This is a sequence, i.e. array. Access it by index. 47 | newNode = node.value[piece]; 48 | 49 | if (!pieces.length) { 50 | // This is the last item! 51 | 52 | if (!newNode && piece > 0 && node.value[piece - 1]) { 53 | // Element in sequence does not exist. It could have been empty 54 | // Let's provide the end of previous element 55 | start = node.value[piece - 1].end_mark; 56 | end = start; 57 | } else { 58 | start = newNode.start_mark; 59 | end = newNode.end_mark; 60 | } 61 | } 62 | } else { 63 | // Unknown piece, which will just return no source map. 64 | return null; 65 | } 66 | 67 | if (newNode) { 68 | node = newNode; 69 | } else { 70 | // We have no other node so return whatever we have. 71 | // Better than nothing init? 72 | return { start, end }; 73 | } 74 | 75 | piece = pieces.shift(); 76 | } 77 | 78 | return { start, end }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/generator.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import querystring from 'querystring'; 3 | import faker from 'json-schema-faker'; 4 | import { dereference } from './json-schema'; 5 | import annotations from './annotations'; 6 | import { inferred } from './link'; 7 | import { isFormURLEncoded, isMultiPartFormData, parseBoundary } from './media-type'; 8 | 9 | faker.option({ 10 | fixedProbabilities: true, 11 | optionalsProbability: 1.0, 12 | useExamplesValue: true, 13 | useDefaultValue: true, 14 | maxItems: 5, 15 | maxLength: 256, 16 | }); 17 | 18 | const schemaIsArrayAndHasItems = schema => schema.type && schema.type === 'array' && schema.items; 19 | const isEmptyArray = value => value && Array.isArray(value) && value.length === 0; 20 | 21 | export function bodyFromSchema(schema, payload, parser, contentType = 'application/json') { 22 | const dereferencedSchema = dereference(schema, schema); 23 | const { Asset } = parser.minim.elements; 24 | let asset = null; 25 | 26 | try { 27 | let body = faker.generate(dereferencedSchema); 28 | 29 | if (isEmptyArray(body) && schemaIsArrayAndHasItems(dereferencedSchema)) { 30 | // Faker failed to generate array schema, pass it `items` and wrap in array ourselves 31 | body = [faker.generate(dereferencedSchema.items)]; 32 | } 33 | 34 | if (typeof body !== 'string') { 35 | if (isFormURLEncoded(contentType)) { 36 | // Form data 37 | // TODO: check for arrays etc. 38 | body = querystring.stringify(body); 39 | } else if (isMultiPartFormData(contentType)) { 40 | const boundary = parseBoundary(contentType); 41 | let content = ''; 42 | 43 | _.forEach(body, (value, key) => { 44 | content += `--${boundary}\r\n`; 45 | content += `Content-Disposition: form-data; name="${key}"\r\n\r\n`; 46 | content += `${value}\r\n`; 47 | }); 48 | 49 | content += `\r\n--${boundary}--\r\n`; 50 | 51 | body = content; 52 | } else { 53 | // JSON 54 | body = JSON.stringify(body, null, 2); 55 | } 56 | } 57 | 58 | asset = new Asset(body); 59 | 60 | asset.classes.push('messageBody'); 61 | asset.contentType = contentType; 62 | 63 | inferred('message-body-generation', asset, parser); 64 | 65 | payload.content.push(asset); 66 | } catch (exception) { 67 | parser.createAnnotation( 68 | annotations.DATA_LOST, parser.path, 69 | `Unable to generate ${contentType} example message body out of JSON Schema`, 70 | ); 71 | } 72 | 73 | return asset; 74 | } 75 | 76 | // Generates body asset from formData parameters. 77 | export function bodyFromFormParameter(param, schema) { 78 | // Preparing throwaway schema. Later we will feed the 'bodyFromSchema' 79 | // with it. 80 | const paramSchema = _.clone(param); 81 | const retSchema = _.clone(schema); 82 | 83 | // If there's example value, we want to force the body generator 84 | // to use it. This is done using 'enum' with a single value. 85 | if (param['x-example'] !== undefined) { 86 | paramSchema.default = param['x-example']; 87 | } 88 | 89 | delete paramSchema.name; 90 | delete paramSchema.in; 91 | delete paramSchema.format; 92 | delete paramSchema.required; 93 | delete paramSchema['x-example']; 94 | delete paramSchema.collectionFormat; 95 | delete paramSchema.allowEmptyValue; // allowEmptyValue is not supported yet 96 | delete paramSchema.items; // arrays are not supported yet 97 | 98 | retSchema.properties[param.name] = paramSchema; 99 | 100 | if (param.required) { 101 | retSchema.required.push(param.name); 102 | } 103 | 104 | return retSchema; 105 | } 106 | 107 | export default { bodyFromSchema, bodyFromFormParameter }; 108 | -------------------------------------------------------------------------------- /src/headers.js: -------------------------------------------------------------------------------- 1 | import { inferred } from './link'; 2 | import annotations from './annotations'; 3 | 4 | export function createHeaders(payload, parser) { 5 | const { HttpHeaders } = parser.minim.elements; 6 | 7 | const headers = new HttpHeaders(); 8 | 9 | // eslint-disable-next-line no-param-reassign 10 | payload.headers = payload.headers || headers; 11 | } 12 | 13 | export function pushHeader(key, value, payload, parser, fragment) { 14 | const { Member: MemberElement } = parser.minim.elements; 15 | let header; 16 | 17 | createHeaders(payload, parser); 18 | 19 | const duplicate = payload.headers.find(member => 20 | member.key.content.toLowerCase() === key.toLowerCase()); 21 | 22 | if (duplicate.length) { 23 | header = duplicate.first; 24 | header.value = value; 25 | } else { 26 | header = new MemberElement(key, value); 27 | } 28 | 29 | if (fragment) { 30 | inferred(fragment, header, parser); 31 | } else { 32 | // eslint-disable-next-line no-underscore-dangle 33 | header._meta = parser.minim.toElement({}); 34 | } 35 | 36 | if (fragment === undefined && parser.generateSourceMap) { 37 | parser.createSourceMap(header, parser.path); 38 | } 39 | 40 | if (!duplicate.length) { 41 | payload.headers.push(header); 42 | } 43 | 44 | return header; 45 | } 46 | 47 | export function pushHeaderObject(key, header, payload, parser) { 48 | let value = ''; 49 | 50 | if (header.type === 'array') { 51 | // TODO: Support collectionFormat once arrays are supported 52 | parser.createAnnotation( 53 | annotations.DATA_LOST, parser.path, 54 | 'Headers of type array are not yet supported', 55 | ); 56 | 57 | return; 58 | } 59 | 60 | const schema = { type: header.type }; 61 | 62 | // Choose the first available option 63 | if (header.enum) { 64 | // TODO: This may lose data if there are multiple enums. 65 | [value] = header.enum; 66 | } 67 | 68 | if (header['x-example']) { 69 | parser.withPath('x-example', () => { 70 | value = parser.convertValueToElement(header['x-example'], schema); 71 | }); 72 | } else if (header.default) { 73 | parser.withPath('default', () => { 74 | value = parser.convertValueToElement(header.default, schema); 75 | }); 76 | } 77 | 78 | const headerElement = pushHeader(key, value, payload, parser); 79 | 80 | if (header.description) { 81 | headerElement.description = header.description; 82 | 83 | if (parser.generateSourceMap) { 84 | parser.createSourceMap(headerElement.meta.get('description'), parser.path.concat(['description'])); 85 | } 86 | } 87 | } 88 | 89 | export default { pushHeader, pushHeaderObject }; 90 | -------------------------------------------------------------------------------- /src/json-schema.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | // Test whether a key is a special Swagger extension. 4 | function isExtension(value, key) { 5 | return _.startsWith(key, 'x-'); 6 | } 7 | 8 | export function parseReference(reference) { 9 | const parts = reference.split('/'); 10 | 11 | if (parts[0] !== '#') { 12 | throw new Error('Schema reference must start with document root (#)'); 13 | } 14 | 15 | if (parts[1] !== 'definitions' || parts.length !== 3) { 16 | throw new Error('Schema reference must be reference to #/definitions'); 17 | } 18 | 19 | const id = parts[2]; 20 | 21 | return id; 22 | } 23 | 24 | /** 25 | * Lookup a reference 26 | * 27 | * Resolves a reference in the given root schema. An optional depth argument 28 | * can be provided to limit resolution to a certain level. For example to 29 | * limit the `#/definitions/User/properties/name` reference lookup to just a 30 | * depth `#/definitions/User`, a depth of 3 can be supplied. 31 | * 32 | * @param reference {string} - Example: #/definitions/User/properties/name 33 | * @param root {object} - The object to resolve the given reference 34 | * @param depth {number} - A limit to resolving the depth 35 | */ 36 | export function lookupReference(reference, root, depth) { 37 | const parts = reference.split('/').reverse(); 38 | 39 | if (parts.pop() !== '#') { 40 | throw new Error('Schema reference must start with document root (#)'); 41 | } 42 | 43 | if (parts.pop() !== 'definitions') { 44 | throw new Error('Schema reference must be reference to #/definitions'); 45 | } 46 | 47 | const id = parts[parts.length - 1]; 48 | let value = root.definitions; 49 | 50 | // ['#', 'definitions'] (2) 51 | let currentDepth = 2; 52 | 53 | while (parts.length > 0 && value !== undefined) { 54 | const key = parts.pop(); 55 | value = value[key]; 56 | currentDepth += 1; 57 | 58 | if (depth && depth === currentDepth) { 59 | break; 60 | } 61 | } 62 | 63 | if (value === undefined) { 64 | throw new Error(`Reference to ${reference} does not exist`); 65 | } 66 | 67 | return { 68 | id, 69 | referenced: value, 70 | }; 71 | } 72 | 73 | function pathHasCircularReference(paths, path, reference) { 74 | const currentPath = (path || []).join('/'); 75 | 76 | // Check for direct circular reference 77 | if (currentPath.startsWith(reference)) { 78 | return true; 79 | } 80 | 81 | // Check for indirect circular Reference 82 | if ((paths || []).find(p => p.startsWith(reference))) { 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | 89 | export function dereference(example, root, paths, path) { 90 | if (example === null || example === undefined) { 91 | return example; 92 | } 93 | 94 | if (example.$ref && _.isString(example.$ref)) { 95 | const refPath = example.$ref.split('/'); 96 | const currentPath = (path || []).join('/'); 97 | 98 | if (path && pathHasCircularReference(paths, path, example.$ref)) { 99 | return null; 100 | } 101 | 102 | const ref = lookupReference(example.$ref, root); 103 | 104 | const newPaths = (paths || []).concat([currentPath]); 105 | return dereference(ref.referenced, root, newPaths, refPath); 106 | } 107 | 108 | if (_.isArray(example)) { 109 | return example.map(value => dereference(value, root, paths, path)); 110 | } 111 | 112 | if (_.isObject(example)) { 113 | const result = {}; 114 | 115 | _.forEach(example, (value, key) => { 116 | result[key] = dereference(value, root, paths, (path || []).concat([key])); 117 | }); 118 | 119 | return result; 120 | } 121 | 122 | return example; 123 | } 124 | 125 | function convertSubSchema(schema, references, swagger) { 126 | if (schema.$ref) { 127 | references.push(schema.$ref); 128 | return { $ref: schema.$ref }; 129 | } 130 | 131 | const recurseConvertSubSchema = s => convertSubSchema(s, references, swagger); 132 | 133 | let actualSchema = _.omit(schema, ['discriminator', 'readOnly', 'xml', 'externalDocs', 'example']); 134 | actualSchema = _.omitBy(actualSchema, isExtension); 135 | actualSchema = _.cloneDeep(actualSchema); 136 | 137 | if (schema.type === 'file') { 138 | // file is not a valid JSON Schema type let's pick string instead 139 | actualSchema.type = 'string'; 140 | } 141 | 142 | if (schema.example) { 143 | actualSchema.examples = [dereference(schema.example, swagger)]; 144 | } 145 | 146 | if (schema['x-nullable']) { 147 | if (actualSchema.type) { 148 | actualSchema.type = [actualSchema.type, 'null']; 149 | } else if (actualSchema.enum === undefined) { 150 | actualSchema.type = 'null'; 151 | } 152 | 153 | if (actualSchema.enum && !actualSchema.enum.includes(null)) { 154 | actualSchema.enum.push(null); 155 | } 156 | } 157 | 158 | if (schema.allOf) { 159 | actualSchema.allOf = schema.allOf.map(recurseConvertSubSchema); 160 | } 161 | 162 | if (schema.anyOf) { 163 | actualSchema.anyOf = schema.anyOf.map(recurseConvertSubSchema); 164 | } 165 | 166 | if (schema.oneOf) { 167 | actualSchema.oneOf = schema.oneOf.map(recurseConvertSubSchema); 168 | } 169 | 170 | if (schema.not) { 171 | actualSchema.not = recurseConvertSubSchema(schema.not); 172 | } 173 | 174 | // Array 175 | 176 | if (schema.items) { 177 | if (Array.isArray(schema.items)) { 178 | actualSchema.items = schema.items.map(recurseConvertSubSchema); 179 | } else { 180 | actualSchema.items = recurseConvertSubSchema(schema.items); 181 | } 182 | } 183 | 184 | if (schema.additionalItems && typeof schema.additionalItems === 'object') { 185 | actualSchema.additionalItems = recurseConvertSubSchema(schema.additionalItems); 186 | } 187 | 188 | // Object 189 | 190 | if (schema.properties) { 191 | Object.keys(schema.properties).forEach((key) => { 192 | actualSchema.properties[key] = recurseConvertSubSchema(schema.properties[key]); 193 | }); 194 | } 195 | 196 | if (schema.patternProperties) { 197 | Object.keys(schema.patternProperties).forEach((key) => { 198 | actualSchema.patternProperties[key] = 199 | recurseConvertSubSchema(schema.patternProperties[key]); 200 | }); 201 | } 202 | 203 | if (schema.additionalProperties && typeof schema.additionalProperties === 'object') { 204 | actualSchema.additionalProperties = recurseConvertSubSchema(schema.additionalProperties); 205 | } 206 | 207 | return actualSchema; 208 | } 209 | 210 | /** Returns true if the given schema contains any references 211 | */ 212 | function checkSchemaHasReferences(schema) { 213 | if (schema.$ref) { 214 | return true; 215 | } 216 | 217 | return Object.values(schema).some((value) => { 218 | if (_.isArray(value)) { 219 | return value.some(checkSchemaHasReferences); 220 | } else if (_.isObject(value)) { 221 | return checkSchemaHasReferences(value); 222 | } 223 | 224 | return false; 225 | }); 226 | } 227 | 228 | /** Traverses the entire schema to find all of the references 229 | * @returns array of each reference that is found in the schema 230 | */ 231 | function findReferences(schema) { 232 | if (schema.$ref) { 233 | return [schema.$ref]; 234 | } 235 | 236 | let references = []; 237 | 238 | if (schema.allOf) { 239 | references = references.concat(...schema.allOf.map(findReferences)); 240 | } 241 | 242 | if (schema.anyOf) { 243 | references = references.concat(...schema.anyOf.map(findReferences)); 244 | } 245 | 246 | if (schema.oneOf) { 247 | references = references.concat(...schema.oneOf.map(findReferences)); 248 | } 249 | 250 | if (schema.not) { 251 | references = references.concat(...findReferences(schema.not)); 252 | } 253 | 254 | // Array 255 | 256 | if (schema.items) { 257 | if (Array.isArray(schema.items)) { 258 | references = references.concat(...schema.items.map(findReferences)); 259 | } else { 260 | references = references.concat(findReferences(schema.items)); 261 | } 262 | } 263 | 264 | if (schema.additionalItems && typeof schema.additionalItems === 'object') { 265 | references = references.concat(findReferences(schema.additionalItems)); 266 | } 267 | 268 | // Object 269 | 270 | if (schema.properties) { 271 | Object.keys(schema.properties).forEach((key) => { 272 | references = references.concat(findReferences(schema.properties[key])); 273 | }); 274 | } 275 | 276 | if (schema.patternProperties) { 277 | Object.keys(schema.patternProperties).forEach((key) => { 278 | references = references.concat(findReferences(schema.patternProperties[key])); 279 | }); 280 | } 281 | 282 | if (schema.additionalProperties && typeof schema.additionalProperties === 'object') { 283 | references = references.concat(findReferences(schema.additionalProperties)); 284 | } 285 | 286 | return references; 287 | } 288 | 289 | /** Convert Swagger schema to JSON Schema 290 | * @param schema - The Swagger schema to convert 291 | * @param root - The document root (this contains the JSON schema definitions) 292 | * @param swagger - The swagger document root (this contains the Swagger schema definitions) 293 | * @param copyDefinitins - Whether to copy the referenced definitions to the resulted schema 294 | */ 295 | export function convertSchema(schema, root, swagger, copyDefinitions = true) { 296 | let references = []; 297 | const result = convertSubSchema(schema, references, swagger); 298 | 299 | if (copyDefinitions) { 300 | if (references.length !== 0) { 301 | result.definitions = {}; 302 | } 303 | 304 | while (references.length !== 0) { 305 | const lookup = lookupReference(references.pop(), root, 3); 306 | 307 | if (result.definitions[lookup.id] === undefined) { 308 | references = references.concat(findReferences(lookup.referenced)); 309 | result.definitions[lookup.id] = lookup.referenced; 310 | } 311 | } 312 | } 313 | 314 | if (result.$ref && copyDefinitions) { 315 | const reference = lookupReference(result.$ref, root); 316 | 317 | if (!checkSchemaHasReferences(result.definitions[reference.id])) { 318 | // Dereference the root reference if possible 319 | return result.definitions[reference.id]; 320 | } 321 | 322 | // Wrap any root reference in allOf because faker will end up in 323 | // loop with root references which is avoided with allOf 324 | return { 325 | allOf: [{ $ref: result.$ref }], 326 | definitions: result.definitions, 327 | }; 328 | } 329 | 330 | return result; 331 | } 332 | 333 | export function convertSchemaDefinitions(definitions) { 334 | const jsonSchemaDefinitions = {}; 335 | 336 | if (definitions) { 337 | _.forEach(definitions, (schema, key) => { 338 | jsonSchemaDefinitions[key] = convertSchema(schema, { definitions }, { definitions }, false); 339 | }); 340 | } 341 | 342 | return jsonSchemaDefinitions; 343 | } 344 | -------------------------------------------------------------------------------- /src/link.js: -------------------------------------------------------------------------------- 1 | export function baseLink(element, parser, relation, options = {}) { 2 | const { String: StringElement, Link } = parser.minim.elements; 3 | 4 | const opts = { 5 | path: options.path || [], 6 | url: options.url || `http://docs.apiary.io/validations/swagger#${(options.fragment || '')}`, 7 | }; 8 | 9 | const href = new StringElement(opts.url); 10 | const link = new Link(); 11 | 12 | if (parser.generateSourceMap) { 13 | parser.createSourceMap(href, opts.path.concat(['url'])); 14 | } 15 | 16 | link.relation = relation; 17 | link.href = href; 18 | 19 | if (options.description) { 20 | link.description = options.description; 21 | 22 | if (parser.generateSourceMap) { 23 | parser.createSourceMap(link.meta.get('description'), opts.path.concat(['description'])); 24 | } 25 | } 26 | 27 | element.links.push(link); 28 | } 29 | 30 | export function origin(fragment, element, parser) { 31 | baseLink(element, parser, 'origin', { 32 | fragment, 33 | }); 34 | } 35 | 36 | export function inferred(fragment, element, parser) { 37 | baseLink(element, parser, 'inferred', { 38 | fragment, 39 | }); 40 | } 41 | 42 | export default { baseLink, origin, inferred }; 43 | -------------------------------------------------------------------------------- /src/media-type.js: -------------------------------------------------------------------------------- 1 | import mediaTyper from 'media-typer'; 2 | import contentTypeModule from 'content-type'; 3 | 4 | export const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded'; 5 | 6 | function parse(contentType) { 7 | const { type } = contentTypeModule.parse(contentType); 8 | return mediaTyper.parse(type); 9 | } 10 | 11 | export function isValidContentType(contentType) { 12 | try { 13 | parse(contentType); 14 | } catch (e) { 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | export function isJsonContentType(contentType) { 21 | try { 22 | const type = parse(contentType); 23 | return type.suffix === 'json' || type.subtype === 'json'; 24 | } catch (e) { 25 | return false; 26 | } 27 | } 28 | 29 | export function isTextContentType(contentType) { 30 | try { 31 | return parse(contentType).type === 'text'; 32 | } catch (e) { 33 | return false; 34 | } 35 | } 36 | 37 | export function isMultiPartFormData(contentType) { 38 | try { 39 | const type = parse(contentType); 40 | return type.type === 'multipart' && type.subtype === 'form-data'; 41 | } catch (e) { 42 | return false; 43 | } 44 | } 45 | 46 | export function isFormURLEncoded(contentType) { 47 | try { 48 | const type = parse(contentType); 49 | return type.type === 'application' && type.subtype === 'x-www-form-urlencoded'; 50 | } catch (e) { 51 | return false; 52 | } 53 | } 54 | 55 | export function hasBoundary(contentType) { 56 | try { 57 | const type = contentTypeModule.parse(contentType); 58 | return type.parameters.boundary !== undefined; 59 | } catch (e) { 60 | return false; 61 | } 62 | } 63 | 64 | export function parseBoundary(contentType) { 65 | const boundary = 'BOUNDARY'; 66 | 67 | try { 68 | const type = contentTypeModule.parse(contentType); 69 | 70 | if (type.parameters.boundary) { 71 | return type.parameters.boundary; 72 | } 73 | } catch (e) { 74 | // Ignore invalid content type 75 | } 76 | 77 | return boundary; 78 | } 79 | -------------------------------------------------------------------------------- /src/uri-template.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | function escapeUriTemplateVariable(variable) { 4 | return encodeURIComponent(variable) 5 | .replace(/[-.!~*'()]/g, c => `%${c.charCodeAt(0).toString(16)}`); 6 | } 7 | 8 | export default function (basePath, href, pathObjectParams = [], queryParams = []) { 9 | const parameterNames = _.chain(pathObjectParams) 10 | .concat(queryParams) 11 | .filter(parameter => parameter.in === 'query') 12 | .uniqBy(parameter => parameter.name) 13 | .map((parameter) => { 14 | const name = escapeUriTemplateVariable(parameter.name); 15 | 16 | if (parameter.collectionFormat === 'multi') { 17 | return `${name}*`; 18 | } 19 | 20 | return name; 21 | }) 22 | .value(); 23 | 24 | if (parameterNames.length > 0) { 25 | const queryString = parameterNames.join(','); 26 | return `${basePath}${href}{?${queryString}}`; 27 | } 28 | 29 | return basePath + href; 30 | } 31 | -------------------------------------------------------------------------------- /test/adapter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | /* eslint-disable no-loop-func */ 3 | /* eslint-disable global-require */ 4 | /* eslint-disable import/no-dynamic-require */ 5 | /* 6 | * Tests for Swagger adapter. 7 | */ 8 | 9 | import fs from 'fs'; 10 | import path from 'path'; 11 | import glob from 'glob'; 12 | import fury from 'fury'; 13 | import swaggerZoo from 'swagger-zoo'; 14 | import { expect } from 'chai'; 15 | import adapter, { detect } from '../src/adapter'; 16 | 17 | fury.adapters = [adapter]; 18 | 19 | function testFixture(description, fixture, generateSourceMap = false) { 20 | it(description, (done) => { 21 | const source = fixture.swagger; 22 | let expected; 23 | 24 | if (generateSourceMap) { 25 | expected = fixture.apiElementsSourceMap; 26 | } else { 27 | expected = fixture.apiElements; 28 | } 29 | 30 | const mediaType = 'application/swagger+yaml'; 31 | 32 | fury.parse({ source, mediaType, generateSourceMap }, (err, output) => { 33 | if (err && !output) { 34 | return done(err); 35 | } 36 | 37 | output.freeze(); 38 | 39 | // Invoke with the env var GENERATE set to regenerate the fixtures. 40 | if (process.env.GENERATE) { 41 | expected = fury.minim.toRefract(output); 42 | 43 | if (generateSourceMap) { 44 | // eslint-disable-next-line no-param-reassign 45 | fixture.apiElementsSourceMap = expected; 46 | } else { 47 | // eslint-disable-next-line no-param-reassign 48 | fixture.apiElements = expected; 49 | } 50 | } 51 | 52 | expect(fury.minim.toRefract(output)).to.deep.equal(expected); 53 | return done(); 54 | }); 55 | }); 56 | } 57 | 58 | describe('Swagger 2.0 adapter', () => { 59 | context('detection', () => { 60 | it('detects JSON', () => { 61 | expect(detect('"swagger": "2.0"')).to.be.true; 62 | }); 63 | 64 | it('detects YAML', () => { 65 | expect(detect('swagger: "2.0"')).to.be.true; 66 | }); 67 | 68 | it('detects object', () => { 69 | expect(detect({ swagger: '2.0' })).to.be.true; 70 | }); 71 | 72 | it('works with single quotes', () => { 73 | expect(detect('swagger: \'2.0\'')).to.be.true; 74 | }); 75 | 76 | it('works with extra spacing', () => { 77 | expect(detect('swagger: \t "2.0"')).to.be.true; 78 | }); 79 | 80 | it('works with JSON Swagger', () => { 81 | expect(detect('{ "swagger" : "2.0" }')).to.be.true; 82 | }); 83 | 84 | it('ignores other data', () => { 85 | expect(detect('{"title": "Not Swagger!"}')).to.be.false; 86 | }); 87 | }); 88 | 89 | context('can parse Swagger object', () => { 90 | const source = { swagger: '2.0', info: { title: 'Test', version: '1.0' } }; 91 | let result; 92 | 93 | before((done) => { 94 | fury.parse({ source }, (err, output) => { 95 | if (err) { 96 | return done(err); 97 | } 98 | 99 | result = output; 100 | return done(); 101 | }); 102 | }); 103 | 104 | it('has parseResult element', () => { 105 | expect(result.element).to.equal('parseResult'); 106 | }); 107 | 108 | it('has API category inside parse result', () => { 109 | const filtered = result.filter(item => 110 | item.element === 'category' && item.classes.contains('api')); 111 | 112 | expect(filtered).to.have.length(1); 113 | expect(filtered.first).to.be.an('object'); 114 | }); 115 | }); 116 | 117 | context('cannot parse invalid Swagger YAML', () => { 118 | const source = 'swagger: "2.0"\nbad: }'; 119 | 120 | it('returns error for bad input yaml', (done) => { 121 | fury.parse({ source }, (err, parseResult) => { 122 | expect(err).to.exist; 123 | expect(parseResult).to.exist; 124 | expect(parseResult.errors.isEmpty).to.be.false; 125 | expect(parseResult.warnings.isEmpty).to.be.true; 126 | done(); 127 | }); 128 | }); 129 | 130 | it('returns error for bad input yaml with source maps', (done) => { 131 | fury.parse({ source, generateSourceMap: true }, (err, parseResult) => { 132 | expect(err).to.exist; 133 | expect(parseResult).to.exist; 134 | expect(parseResult.errors.isEmpty).to.be.false; 135 | expect(parseResult.warnings.isEmpty).to.be.true; 136 | done(); 137 | }); 138 | }); 139 | }); 140 | 141 | describe('can parse fixtures', () => { 142 | const fixtures = swaggerZoo.features(); 143 | fixtures.forEach((fixture) => { 144 | testFixture(`Parses ${fixture.name}`, fixture); 145 | testFixture(`Parses ${fixture.name} with source maps`, fixture, true); 146 | }); 147 | }); 148 | 149 | describe('can parse regression fixtures', () => { 150 | const files = glob.sync(path.join(__dirname, 'fixtures', '*.yaml')); 151 | 152 | files.forEach((file) => { 153 | const name = path.basename(file, path.extname(file)); 154 | 155 | const swagger = fs.readFileSync(file, 'utf-8'); 156 | const apiElementsPath = path.join(__dirname, 'fixtures', `${name}.json`); 157 | 158 | const options = { swagger }; 159 | 160 | Object.defineProperty(options, 'apiElements', { 161 | get() { 162 | return require(apiElementsPath); 163 | }, 164 | 165 | set(value) { 166 | fs.writeFileSync(apiElementsPath, JSON.stringify(value, null, 2)); 167 | return value; 168 | }, 169 | }); 170 | 171 | testFixture(`Parses ${name}`, options); 172 | }); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /test/fixtures/ast-path-with-reference-sibling.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "error" 13 | } 14 | ] 15 | }, 16 | "links": { 17 | "element": "array", 18 | "content": [ 19 | { 20 | "element": "link", 21 | "attributes": { 22 | "relation": { 23 | "element": "string", 24 | "content": "origin" 25 | }, 26 | "href": { 27 | "element": "string", 28 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | "attributes": { 36 | "code": { 37 | "element": "number", 38 | "content": 4 39 | }, 40 | "sourceMap": { 41 | "element": "array", 42 | "content": [ 43 | { 44 | "element": "sourceMap", 45 | "content": [ 46 | { 47 | "element": "array", 48 | "content": [ 49 | { 50 | "element": "number", 51 | "attributes": { 52 | "line": { 53 | "element": "number", 54 | "content": 14 55 | }, 56 | "column": { 57 | "element": "number", 58 | "content": 9 59 | } 60 | }, 61 | "content": 247 62 | }, 63 | { 64 | "element": "number", 65 | "attributes": { 66 | "line": { 67 | "element": "number", 68 | "content": 14 69 | }, 70 | "column": { 71 | "element": "number", 72 | "content": 9 73 | } 74 | }, 75 | "content": 0 76 | } 77 | ] 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | }, 84 | "content": "Expected type string but found type null" 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /test/fixtures/ast-path-with-reference-sibling.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Test 5 | host: petstore.swagger.io 6 | definitions: 7 | Company: 8 | allOf: 9 | - $ref: '#/definitions/User' 10 | properties: 11 | id: 12 | type: string 13 | required: 14 | - id 15 | - 16 | User: 17 | properties: 18 | name: 19 | type: string 20 | required: 21 | - name 22 | -------------------------------------------------------------------------------- /test/fixtures/auth-extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Authentication with extensions" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "category", 30 | "meta": { 31 | "classes": { 32 | "element": "array", 33 | "content": [ 34 | { 35 | "element": "string", 36 | "content": "authSchemes" 37 | } 38 | ] 39 | } 40 | }, 41 | "content": [ 42 | { 43 | "element": "Token Authentication Scheme", 44 | "meta": { 45 | "id": { 46 | "element": "string", 47 | "content": "APIGatewayAuthorizer" 48 | } 49 | }, 50 | "content": [ 51 | { 52 | "element": "member", 53 | "content": { 54 | "key": { 55 | "element": "string", 56 | "content": "httpHeaderName" 57 | }, 58 | "value": { 59 | "element": "string", 60 | "content": "Authorization" 61 | } 62 | } 63 | }, 64 | { 65 | "element": "extension", 66 | "meta": { 67 | "links": { 68 | "element": "array", 69 | "content": [ 70 | { 71 | "element": "link", 72 | "attributes": { 73 | "relation": { 74 | "element": "string", 75 | "content": "profile" 76 | }, 77 | "href": { 78 | "element": "string", 79 | "content": "https://help.apiary.io/profiles/api-elements/vendor-extensions/" 80 | } 81 | } 82 | } 83 | ] 84 | } 85 | }, 86 | "content": { 87 | "x-amazon-apigateway-authtype": "oauth2", 88 | "x-amazon-apigateway-authorizer": { 89 | "type": "token", 90 | "authorizerUri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:account-id:function:function-name/invocations", 91 | "authorizerCredentials": "arn:aws:iam::account-id:role", 92 | "identityValidationExpression": "^x-[a-z]+", 93 | "authorizerResultTtlInSeconds": 60 94 | } 95 | } 96 | } 97 | ] 98 | } 99 | ] 100 | } 101 | ] 102 | } 103 | ] 104 | } -------------------------------------------------------------------------------- /test/fixtures/auth-extensions.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: 1.0.0 4 | title: Authentication with extensions 5 | securityDefinitions: 6 | APIGatewayAuthorizer: 7 | type: apiKey 8 | name: Authorization 9 | in: header 10 | x-amazon-apigateway-authtype: oauth2 11 | x-amazon-apigateway-authorizer: 12 | type: token 13 | authorizerUri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:account-id:function:function-name/invocations 14 | authorizerCredentials: arn:aws:iam::account-id:role 15 | identityValidationExpression: "^x-[a-z]+" 16 | authorizerResultTtlInSeconds: 60 17 | -------------------------------------------------------------------------------- /test/fixtures/auth-multi-consumes.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Authentication with multiple consumes" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "category", 30 | "meta": { 31 | "classes": { 32 | "element": "array", 33 | "content": [ 34 | { 35 | "element": "string", 36 | "content": "authSchemes" 37 | } 38 | ] 39 | } 40 | }, 41 | "content": [ 42 | { 43 | "element": "Token Authentication Scheme", 44 | "meta": { 45 | "id": { 46 | "element": "string", 47 | "content": "Bearer" 48 | } 49 | }, 50 | "content": [ 51 | { 52 | "element": "member", 53 | "content": { 54 | "key": { 55 | "element": "string", 56 | "content": "httpHeaderName" 57 | }, 58 | "value": { 59 | "element": "string", 60 | "content": "Authorization" 61 | } 62 | } 63 | } 64 | ] 65 | } 66 | ] 67 | }, 68 | { 69 | "element": "resource", 70 | "attributes": { 71 | "href": { 72 | "element": "string", 73 | "content": "/query" 74 | } 75 | }, 76 | "content": [ 77 | { 78 | "element": "transition", 79 | "content": [ 80 | { 81 | "element": "httpTransaction", 82 | "attributes": { 83 | "authSchemes": { 84 | "element": "array", 85 | "content": [ 86 | { 87 | "element": "APIKeyHeader" 88 | } 89 | ] 90 | } 91 | }, 92 | "content": [ 93 | { 94 | "element": "httpRequest", 95 | "attributes": { 96 | "method": { 97 | "element": "string", 98 | "content": "POST" 99 | }, 100 | "headers": { 101 | "element": "httpHeaders", 102 | "content": [ 103 | { 104 | "element": "member", 105 | "meta": { 106 | "links": { 107 | "element": "array", 108 | "content": [ 109 | { 110 | "element": "link", 111 | "attributes": { 112 | "relation": { 113 | "element": "string", 114 | "content": "inferred" 115 | }, 116 | "href": { 117 | "element": "string", 118 | "content": "http://docs.apiary.io/validations/swagger#consumes-content-type" 119 | } 120 | } 121 | } 122 | ] 123 | } 124 | }, 125 | "content": { 126 | "key": { 127 | "element": "string", 128 | "content": "Content-Type" 129 | }, 130 | "value": { 131 | "element": "string", 132 | "content": "application/json" 133 | } 134 | } 135 | } 136 | ] 137 | } 138 | } 139 | }, 140 | { 141 | "element": "httpResponse", 142 | "attributes": { 143 | "statusCode": { 144 | "element": "string", 145 | "content": "200" 146 | } 147 | }, 148 | "content": [ 149 | { 150 | "element": "copy", 151 | "content": "Example" 152 | }, 153 | { 154 | "element": "asset", 155 | "meta": { 156 | "classes": { 157 | "element": "array", 158 | "content": [ 159 | { 160 | "element": "string", 161 | "content": "messageBodySchema" 162 | } 163 | ] 164 | } 165 | }, 166 | "attributes": { 167 | "contentType": { 168 | "element": "string", 169 | "content": "application/schema+json" 170 | } 171 | }, 172 | "content": "{\"type\":\"object\"}" 173 | }, 174 | { 175 | "element": "dataStructure", 176 | "content": { 177 | "element": "object" 178 | } 179 | } 180 | ] 181 | } 182 | ] 183 | }, 184 | { 185 | "element": "httpTransaction", 186 | "attributes": { 187 | "authSchemes": { 188 | "element": "array", 189 | "content": [ 190 | { 191 | "element": "APIKeyHeader" 192 | } 193 | ] 194 | } 195 | }, 196 | "content": [ 197 | { 198 | "element": "httpRequest", 199 | "attributes": { 200 | "method": { 201 | "element": "string", 202 | "content": "POST" 203 | }, 204 | "headers": { 205 | "element": "httpHeaders", 206 | "content": [ 207 | { 208 | "element": "member", 209 | "meta": { 210 | "links": { 211 | "element": "array", 212 | "content": [ 213 | { 214 | "element": "link", 215 | "attributes": { 216 | "relation": { 217 | "element": "string", 218 | "content": "inferred" 219 | }, 220 | "href": { 221 | "element": "string", 222 | "content": "http://docs.apiary.io/validations/swagger#consumes-content-type" 223 | } 224 | } 225 | } 226 | ] 227 | } 228 | }, 229 | "content": { 230 | "key": { 231 | "element": "string", 232 | "content": "Content-Type" 233 | }, 234 | "value": { 235 | "element": "string", 236 | "content": "text/plain" 237 | } 238 | } 239 | } 240 | ] 241 | } 242 | } 243 | }, 244 | { 245 | "element": "httpResponse", 246 | "attributes": { 247 | "statusCode": { 248 | "element": "string", 249 | "content": "200" 250 | } 251 | }, 252 | "content": [ 253 | { 254 | "element": "copy", 255 | "content": "Example" 256 | }, 257 | { 258 | "element": "asset", 259 | "meta": { 260 | "classes": { 261 | "element": "array", 262 | "content": [ 263 | { 264 | "element": "string", 265 | "content": "messageBodySchema" 266 | } 267 | ] 268 | } 269 | }, 270 | "attributes": { 271 | "contentType": { 272 | "element": "string", 273 | "content": "application/schema+json" 274 | } 275 | }, 276 | "content": "{\"type\":\"object\"}" 277 | }, 278 | { 279 | "element": "dataStructure", 280 | "content": { 281 | "element": "object" 282 | } 283 | } 284 | ] 285 | } 286 | ] 287 | } 288 | ] 289 | } 290 | ] 291 | } 292 | ] 293 | } 294 | ] 295 | } -------------------------------------------------------------------------------- /test/fixtures/auth-multi-consumes.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: 1.0.0 4 | title: Authentication with multiple consumes 5 | securityDefinitions: 6 | Bearer: 7 | type: apiKey 8 | name: Authorization 9 | in: header 10 | paths: 11 | /query: 12 | post: 13 | consumes: 14 | - application/json 15 | - text/plain 16 | security: 17 | - APIKeyHeader: [] 18 | responses: 19 | '200': 20 | description: Example 21 | schema: 22 | type: object 23 | -------------------------------------------------------------------------------- /test/fixtures/circular-example.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Test circular reference in example 5 | host: petstore.swagger.io 6 | produces: 7 | - application/json 8 | paths: 9 | /: 10 | get: 11 | responses: 12 | 200: 13 | description: Get company info 14 | schema: 15 | $ref: '#/definitions/Company' 16 | 17 | definitions: 18 | Company: 19 | type: object 20 | properties: 21 | id: 22 | type: string 23 | default: ORCL 24 | user: 25 | type: object 26 | properties: 27 | data: 28 | $ref: '#/definitions/User' 29 | default: 30 | id: ORCL 31 | 32 | User: 33 | type: object 34 | properties: 35 | name: 36 | type: string 37 | default: doe 38 | company: 39 | properties: 40 | data: 41 | $ref: '#/definitions/Company' 42 | 43 | -------------------------------------------------------------------------------- /test/fixtures/consumes-invalid-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Consumes JSON with invalid content type" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/test" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | } 50 | }, 51 | "content": [ 52 | { 53 | "element": "asset", 54 | "meta": { 55 | "classes": { 56 | "element": "array", 57 | "content": [ 58 | { 59 | "element": "string", 60 | "content": "messageBodySchema" 61 | } 62 | ] 63 | } 64 | }, 65 | "attributes": { 66 | "contentType": { 67 | "element": "string", 68 | "content": "application/schema+json" 69 | } 70 | }, 71 | "content": "{\"type\":\"object\"}" 72 | }, 73 | { 74 | "element": "dataStructure", 75 | "content": { 76 | "element": "object" 77 | } 78 | } 79 | ] 80 | }, 81 | { 82 | "element": "httpResponse", 83 | "attributes": { 84 | "statusCode": { 85 | "element": "string", 86 | "content": "200" 87 | } 88 | }, 89 | "content": [ 90 | { 91 | "element": "copy", 92 | "content": "My Response" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | ] 103 | }, 104 | { 105 | "element": "annotation", 106 | "meta": { 107 | "classes": { 108 | "element": "array", 109 | "content": [ 110 | { 111 | "element": "string", 112 | "content": "warning" 113 | } 114 | ] 115 | }, 116 | "links": { 117 | "element": "array", 118 | "content": [ 119 | { 120 | "element": "link", 121 | "attributes": { 122 | "relation": { 123 | "element": "string", 124 | "content": "origin" 125 | }, 126 | "href": { 127 | "element": "string", 128 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 129 | } 130 | } 131 | } 132 | ] 133 | } 134 | }, 135 | "attributes": { 136 | "code": { 137 | "element": "number", 138 | "content": 6 139 | }, 140 | "sourceMap": { 141 | "element": "array", 142 | "content": [ 143 | { 144 | "element": "sourceMap", 145 | "content": [ 146 | { 147 | "element": "array", 148 | "content": [ 149 | { 150 | "element": "number", 151 | "attributes": { 152 | "line": { 153 | "element": "number", 154 | "content": 5 155 | }, 156 | "column": { 157 | "element": "number", 158 | "content": 4 159 | } 160 | }, 161 | "content": 101 162 | }, 163 | { 164 | "element": "number", 165 | "attributes": { 166 | "line": { 167 | "element": "number", 168 | "content": 5 169 | }, 170 | "column": { 171 | "element": "number", 172 | "content": 33 173 | } 174 | }, 175 | "content": 29 176 | } 177 | ] 178 | } 179 | ] 180 | } 181 | ] 182 | } 183 | }, 184 | "content": "Invalid content type 'application/hal+json; invalid', invalid parameter format" 185 | } 186 | ] 187 | } -------------------------------------------------------------------------------- /test/fixtures/consumes-invalid-type.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Consumes JSON with invalid content type 4 | version: '1.0' 5 | consumes: 6 | - application/hal+json; invalid 7 | paths: 8 | '/test': 9 | get: 10 | parameters: 11 | - name: name 12 | in: body 13 | required: true 14 | schema: 15 | type: object 16 | responses: 17 | 200: 18 | description: 'My Response' 19 | -------------------------------------------------------------------------------- /test/fixtures/consumes-multipart-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Consumes Multipart Form Data with file" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/test" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "POST" 49 | }, 50 | "headers": { 51 | "element": "httpHeaders", 52 | "content": [ 53 | { 54 | "element": "member", 55 | "meta": { 56 | "links": { 57 | "element": "array", 58 | "content": [ 59 | { 60 | "element": "link", 61 | "attributes": { 62 | "relation": { 63 | "element": "string", 64 | "content": "inferred" 65 | }, 66 | "href": { 67 | "element": "string", 68 | "content": "http://docs.apiary.io/validations/swagger#consumes-content-type" 69 | } 70 | } 71 | } 72 | ] 73 | } 74 | }, 75 | "content": { 76 | "key": { 77 | "element": "string", 78 | "content": "Content-Type" 79 | }, 80 | "value": { 81 | "element": "string", 82 | "content": "multipart/form-data; boundary=BOUNDARY" 83 | } 84 | } 85 | } 86 | ] 87 | } 88 | }, 89 | "content": [ 90 | { 91 | "element": "asset", 92 | "meta": { 93 | "classes": { 94 | "element": "array", 95 | "content": [ 96 | { 97 | "element": "string", 98 | "content": "messageBody" 99 | } 100 | ] 101 | }, 102 | "links": { 103 | "element": "array", 104 | "content": [ 105 | { 106 | "element": "link", 107 | "attributes": { 108 | "relation": { 109 | "element": "string", 110 | "content": "inferred" 111 | }, 112 | "href": { 113 | "element": "string", 114 | "content": "http://docs.apiary.io/validations/swagger#message-body-generation" 115 | } 116 | } 117 | } 118 | ] 119 | } 120 | }, 121 | "attributes": { 122 | "contentType": { 123 | "element": "string", 124 | "content": "multipart/form-data; boundary=BOUNDARY" 125 | } 126 | }, 127 | "content": "--BOUNDARY\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\nvalue\r\n\r\n--BOUNDARY--\r\n" 128 | }, 129 | { 130 | "element": "dataStructure", 131 | "content": { 132 | "element": "object", 133 | "content": [ 134 | { 135 | "element": "member", 136 | "attributes": { 137 | "typeAttributes": { 138 | "element": "array", 139 | "content": [ 140 | { 141 | "element": "string", 142 | "content": "required" 143 | } 144 | ] 145 | } 146 | }, 147 | "content": { 148 | "key": { 149 | "element": "string", 150 | "content": "image" 151 | }, 152 | "value": { 153 | "element": "string", 154 | "content": "value" 155 | } 156 | } 157 | } 158 | ] 159 | } 160 | } 161 | ] 162 | }, 163 | { 164 | "element": "httpResponse", 165 | "attributes": { 166 | "statusCode": { 167 | "element": "string", 168 | "content": "200" 169 | } 170 | }, 171 | "content": [ 172 | { 173 | "element": "copy", 174 | "content": "My Response" 175 | } 176 | ] 177 | } 178 | ] 179 | } 180 | ] 181 | } 182 | ] 183 | } 184 | ] 185 | } 186 | ] 187 | } -------------------------------------------------------------------------------- /test/fixtures/consumes-multipart-file.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Consumes Multipart Form Data with file 4 | version: '1.0' 5 | consumes: 6 | - multipart/form-data 7 | paths: 8 | '/test': 9 | post: 10 | parameters: 11 | - name: image 12 | in: formData 13 | required: true 14 | x-example: value 15 | type: file 16 | responses: 17 | 200: 18 | description: 'My Response' 19 | -------------------------------------------------------------------------------- /test/fixtures/data-structure-generation-nullable-member.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Test API" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "copy", 30 | "content": "Nullable Member" 31 | }, 32 | { 33 | "element": "resource", 34 | "attributes": { 35 | "href": { 36 | "element": "string", 37 | "content": "/test" 38 | } 39 | }, 40 | "content": [ 41 | { 42 | "element": "transition", 43 | "meta": { 44 | "title": { 45 | "element": "string", 46 | "content": "Test endpoint" 47 | } 48 | }, 49 | "content": [ 50 | { 51 | "element": "httpTransaction", 52 | "content": [ 53 | { 54 | "element": "httpRequest", 55 | "attributes": { 56 | "method": { 57 | "element": "string", 58 | "content": "GET" 59 | } 60 | } 61 | }, 62 | { 63 | "element": "httpResponse", 64 | "attributes": { 65 | "statusCode": { 66 | "element": "string", 67 | "content": "200" 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "copy", 73 | "content": "A response" 74 | }, 75 | { 76 | "element": "asset", 77 | "meta": { 78 | "classes": { 79 | "element": "array", 80 | "content": [ 81 | { 82 | "element": "string", 83 | "content": "messageBodySchema" 84 | } 85 | ] 86 | } 87 | }, 88 | "attributes": { 89 | "contentType": { 90 | "element": "string", 91 | "content": "application/schema+json" 92 | } 93 | }, 94 | "content": "{\"type\":\"object\",\"properties\":{\"name\":{\"type\":[\"string\",\"null\"]}}}" 95 | }, 96 | { 97 | "element": "dataStructure", 98 | "content": { 99 | "element": "object", 100 | "content": [ 101 | { 102 | "element": "member", 103 | "attributes": { 104 | "typeAttributes": { 105 | "element": "array", 106 | "content": [ 107 | { 108 | "element": "string", 109 | "content": "optional" 110 | } 111 | ] 112 | } 113 | }, 114 | "content": { 115 | "key": { 116 | "element": "string", 117 | "content": "name" 118 | }, 119 | "value": { 120 | "element": "string", 121 | "attributes": { 122 | "typeAttributes": { 123 | "element": "array", 124 | "content": [ 125 | { 126 | "element": "string", 127 | "content": "nullable" 128 | } 129 | ] 130 | } 131 | }, 132 | "content": null 133 | } 134 | } 135 | } 136 | ] 137 | } 138 | } 139 | ] 140 | } 141 | ] 142 | } 143 | ] 144 | } 145 | ] 146 | } 147 | ] 148 | } 149 | ] 150 | } -------------------------------------------------------------------------------- /test/fixtures/data-structure-generation-nullable-member.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: '1.0' 4 | title: Test API 5 | description: Nullable Member 6 | paths: 7 | /test: 8 | get: 9 | summary: Test endpoint 10 | responses: 11 | 200: 12 | description: A response 13 | schema: 14 | type: object 15 | properties: 16 | name: 17 | type: string 18 | x-nullable: true 19 | -------------------------------------------------------------------------------- /test/fixtures/data-structure-generation-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Data Structure Generation" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/user" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "meta": { 40 | "id": { 41 | "element": "string", 42 | "content": "getResource" 43 | } 44 | }, 45 | "content": [ 46 | { 47 | "element": "copy", 48 | "content": "Get a resource" 49 | }, 50 | { 51 | "element": "httpTransaction", 52 | "content": [ 53 | { 54 | "element": "httpRequest", 55 | "attributes": { 56 | "method": { 57 | "element": "string", 58 | "content": "GET" 59 | } 60 | } 61 | }, 62 | { 63 | "element": "httpResponse", 64 | "attributes": { 65 | "statusCode": { 66 | "element": "string", 67 | "content": "200" 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "copy", 73 | "content": "response description" 74 | }, 75 | { 76 | "element": "asset", 77 | "meta": { 78 | "classes": { 79 | "element": "array", 80 | "content": [ 81 | { 82 | "element": "string", 83 | "content": "messageBodySchema" 84 | } 85 | ] 86 | } 87 | }, 88 | "attributes": { 89 | "contentType": { 90 | "element": "string", 91 | "content": "application/schema+json" 92 | } 93 | }, 94 | "content": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"number\"},\"name\":{\"$ref\":\"#/definitions/User\"}},\"examples\":[{\"id\":123,\"user\":{\"name\":\"Doe\"}}],\"definitions\":{\"User\":{\"type\":\"object\",\"examples\":[{\"name\":\"Doe\"}]}}}" 95 | }, 96 | { 97 | "element": "dataStructure", 98 | "content": { 99 | "element": "object", 100 | "attributes": { 101 | "samples": { 102 | "element": "array", 103 | "content": [ 104 | { 105 | "element": "object", 106 | "content": [ 107 | { 108 | "element": "member", 109 | "content": { 110 | "key": { 111 | "element": "string", 112 | "content": "id" 113 | }, 114 | "value": { 115 | "element": "number", 116 | "content": 123 117 | } 118 | } 119 | }, 120 | { 121 | "element": "member", 122 | "content": { 123 | "key": { 124 | "element": "string", 125 | "content": "user" 126 | }, 127 | "value": { 128 | "element": "object", 129 | "content": [ 130 | { 131 | "element": "member", 132 | "content": { 133 | "key": { 134 | "element": "string", 135 | "content": "name" 136 | }, 137 | "value": { 138 | "element": "string", 139 | "content": "Doe" 140 | } 141 | } 142 | } 143 | ] 144 | } 145 | } 146 | } 147 | ] 148 | } 149 | ] 150 | } 151 | }, 152 | "content": [ 153 | { 154 | "element": "member", 155 | "attributes": { 156 | "typeAttributes": { 157 | "element": "array", 158 | "content": [ 159 | { 160 | "element": "string", 161 | "content": "optional" 162 | } 163 | ] 164 | } 165 | }, 166 | "content": { 167 | "key": { 168 | "element": "string", 169 | "content": "id" 170 | }, 171 | "value": { 172 | "element": "number", 173 | "content": null 174 | } 175 | } 176 | }, 177 | { 178 | "element": "member", 179 | "attributes": { 180 | "typeAttributes": { 181 | "element": "array", 182 | "content": [ 183 | { 184 | "element": "string", 185 | "content": "optional" 186 | } 187 | ] 188 | } 189 | }, 190 | "content": { 191 | "key": { 192 | "element": "string", 193 | "content": "name" 194 | }, 195 | "value": { 196 | "element": "definitions/User", 197 | "content": null 198 | } 199 | } 200 | } 201 | ] 202 | } 203 | } 204 | ] 205 | } 206 | ] 207 | } 208 | ] 209 | } 210 | ] 211 | }, 212 | { 213 | "element": "category", 214 | "meta": { 215 | "classes": { 216 | "element": "array", 217 | "content": [ 218 | { 219 | "element": "string", 220 | "content": "dataStructures" 221 | } 222 | ] 223 | } 224 | }, 225 | "content": [ 226 | { 227 | "element": "dataStructure", 228 | "content": { 229 | "element": "object", 230 | "meta": { 231 | "id": { 232 | "element": "string", 233 | "content": "definitions/User" 234 | } 235 | }, 236 | "content": [ 237 | { 238 | "element": "member", 239 | "content": { 240 | "key": { 241 | "element": "string", 242 | "content": "name" 243 | }, 244 | "value": { 245 | "element": "string", 246 | "content": "Doe" 247 | } 248 | } 249 | } 250 | ] 251 | } 252 | } 253 | ] 254 | } 255 | ] 256 | } 257 | ] 258 | } -------------------------------------------------------------------------------- /test/fixtures/data-structure-generation-ref.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Data Structure Generation 5 | paths: 6 | /user: 7 | get: 8 | description: Get a resource 9 | operationId: getResource 10 | responses: 11 | 200: 12 | description: response description 13 | schema: 14 | type: object 15 | example: 16 | id: 123 17 | user: 18 | $ref: '#/definitions/User/example' 19 | properties: 20 | id: 21 | type: number 22 | name: 23 | $ref: '#/definitions/User' 24 | definitions: 25 | User: 26 | type: object 27 | example: 28 | name: Doe 29 | -------------------------------------------------------------------------------- /test/fixtures/data-structure-generation.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Data Structure Generation" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/user" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "meta": { 40 | "id": { 41 | "element": "string", 42 | "content": "getResource" 43 | } 44 | }, 45 | "content": [ 46 | { 47 | "element": "copy", 48 | "content": "Get a resource" 49 | }, 50 | { 51 | "element": "httpTransaction", 52 | "content": [ 53 | { 54 | "element": "httpRequest", 55 | "attributes": { 56 | "method": { 57 | "element": "string", 58 | "content": "GET" 59 | } 60 | } 61 | }, 62 | { 63 | "element": "httpResponse", 64 | "attributes": { 65 | "statusCode": { 66 | "element": "string", 67 | "content": "200" 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "copy", 73 | "content": "response description" 74 | }, 75 | { 76 | "element": "asset", 77 | "meta": { 78 | "classes": { 79 | "element": "array", 80 | "content": [ 81 | { 82 | "element": "string", 83 | "content": "messageBodySchema" 84 | } 85 | ] 86 | } 87 | }, 88 | "attributes": { 89 | "contentType": { 90 | "element": "string", 91 | "content": "application/schema+json" 92 | } 93 | }, 94 | "content": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"number\"},\"name\":{\"type\":\"string\",\"examples\":[\"doe\"]}},\"examples\":[{\"id\":123,\"name\":\"doe\"}]}" 95 | }, 96 | { 97 | "element": "dataStructure", 98 | "content": { 99 | "element": "object", 100 | "attributes": { 101 | "samples": { 102 | "element": "array", 103 | "content": [ 104 | { 105 | "element": "object", 106 | "content": [ 107 | { 108 | "element": "member", 109 | "content": { 110 | "key": { 111 | "element": "string", 112 | "content": "id" 113 | }, 114 | "value": { 115 | "element": "number", 116 | "content": 123 117 | } 118 | } 119 | }, 120 | { 121 | "element": "member", 122 | "content": { 123 | "key": { 124 | "element": "string", 125 | "content": "name" 126 | }, 127 | "value": { 128 | "element": "string", 129 | "content": "doe" 130 | } 131 | } 132 | } 133 | ] 134 | } 135 | ] 136 | } 137 | }, 138 | "content": [ 139 | { 140 | "element": "member", 141 | "attributes": { 142 | "typeAttributes": { 143 | "element": "array", 144 | "content": [ 145 | { 146 | "element": "string", 147 | "content": "optional" 148 | } 149 | ] 150 | } 151 | }, 152 | "content": { 153 | "key": { 154 | "element": "string", 155 | "content": "id" 156 | }, 157 | "value": { 158 | "element": "number", 159 | "content": null 160 | } 161 | } 162 | }, 163 | { 164 | "element": "member", 165 | "attributes": { 166 | "typeAttributes": { 167 | "element": "array", 168 | "content": [ 169 | { 170 | "element": "string", 171 | "content": "optional" 172 | } 173 | ] 174 | } 175 | }, 176 | "content": { 177 | "key": { 178 | "element": "string", 179 | "content": "name" 180 | }, 181 | "value": { 182 | "element": "string", 183 | "content": "doe" 184 | } 185 | } 186 | } 187 | ] 188 | } 189 | } 190 | ] 191 | } 192 | ] 193 | } 194 | ] 195 | } 196 | ] 197 | } 198 | ] 199 | } 200 | ] 201 | } -------------------------------------------------------------------------------- /test/fixtures/data-structure-generation.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Data Structure Generation 5 | paths: 6 | /user: 7 | get: 8 | description: Get a resource 9 | operationId: getResource 10 | responses: 11 | 200: 12 | description: response description 13 | schema: 14 | type: object 15 | example: 16 | id: 123 17 | name: doe 18 | properties: 19 | id: 20 | type: number 21 | name: 22 | type: string 23 | example: doe 24 | -------------------------------------------------------------------------------- /test/fixtures/external-dereferencing.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Dereferencing a local file" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "v2" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | }, 50 | "headers": { 51 | "element": "httpHeaders", 52 | "content": [ 53 | { 54 | "element": "member", 55 | "content": { 56 | "key": { 57 | "element": "string", 58 | "content": "Accept" 59 | }, 60 | "value": { 61 | "element": "string", 62 | "content": "application/json" 63 | } 64 | } 65 | } 66 | ] 67 | } 68 | } 69 | }, 70 | { 71 | "element": "httpResponse", 72 | "attributes": { 73 | "headers": { 74 | "element": "httpHeaders", 75 | "content": [ 76 | { 77 | "element": "member", 78 | "content": { 79 | "key": { 80 | "element": "string", 81 | "content": "Content-Type" 82 | }, 83 | "value": { 84 | "element": "string", 85 | "content": "application/json" 86 | } 87 | } 88 | } 89 | ] 90 | }, 91 | "statusCode": { 92 | "element": "string", 93 | "content": "200" 94 | } 95 | }, 96 | "content": [ 97 | { 98 | "element": "copy", 99 | "content": "dereference package.json" 100 | }, 101 | { 102 | "element": "asset", 103 | "meta": { 104 | "classes": { 105 | "element": "array", 106 | "content": [ 107 | { 108 | "element": "string", 109 | "content": "messageBody" 110 | } 111 | ] 112 | } 113 | }, 114 | "content": "{\n \"example\": {\n \"$ref\": \"package.json\"\n }\n}" 115 | } 116 | ] 117 | } 118 | ] 119 | } 120 | ] 121 | } 122 | ] 123 | } 124 | ] 125 | } 126 | ] 127 | } -------------------------------------------------------------------------------- /test/fixtures/external-dereferencing.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: Dereferencing a local file 4 | version: v2 5 | paths: 6 | /: 7 | get: 8 | responses: 9 | 200: 10 | description: dereference package.json 11 | examples: 12 | application/json: 13 | example: 14 | $ref: 'package.json' 15 | -------------------------------------------------------------------------------- /test/fixtures/headers-type-warning.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Example Header Values" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/test" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | } 50 | } 51 | }, 52 | { 53 | "element": "httpResponse", 54 | "attributes": { 55 | "headers": { 56 | "element": "httpHeaders", 57 | "content": [ 58 | { 59 | "element": "member", 60 | "content": { 61 | "key": { 62 | "element": "string", 63 | "content": "X-RateLimit" 64 | } 65 | } 66 | }, 67 | { 68 | "element": "member", 69 | "content": { 70 | "key": { 71 | "element": "string", 72 | "content": "X-RateLimit-RetryAfter" 73 | } 74 | } 75 | } 76 | ] 77 | }, 78 | "statusCode": { 79 | "element": "string", 80 | "content": "204" 81 | } 82 | }, 83 | "content": [ 84 | { 85 | "element": "copy", 86 | "content": "Header Value Example" 87 | } 88 | ] 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ] 97 | }, 98 | { 99 | "element": "annotation", 100 | "meta": { 101 | "classes": { 102 | "element": "array", 103 | "content": [ 104 | { 105 | "element": "string", 106 | "content": "warning" 107 | } 108 | ] 109 | }, 110 | "links": { 111 | "element": "array", 112 | "content": [ 113 | { 114 | "element": "link", 115 | "attributes": { 116 | "relation": { 117 | "element": "string", 118 | "content": "origin" 119 | }, 120 | "href": { 121 | "element": "string", 122 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 123 | } 124 | } 125 | } 126 | ] 127 | } 128 | }, 129 | "attributes": { 130 | "code": { 131 | "element": "number", 132 | "content": 6 133 | }, 134 | "sourceMap": { 135 | "element": "array", 136 | "content": [ 137 | { 138 | "element": "sourceMap", 139 | "content": [ 140 | { 141 | "element": "array", 142 | "content": [ 143 | { 144 | "element": "number", 145 | "attributes": { 146 | "line": { 147 | "element": "number", 148 | "content": 13 149 | }, 150 | "column": { 151 | "element": "number", 152 | "content": 14 153 | } 154 | }, 155 | "content": 255 156 | }, 157 | { 158 | "element": "number", 159 | "attributes": { 160 | "line": { 161 | "element": "number", 162 | "content": 13 163 | }, 164 | "column": { 165 | "element": "number", 166 | "content": 27 167 | } 168 | }, 169 | "content": 13 170 | } 171 | ] 172 | } 173 | ] 174 | } 175 | ] 176 | } 177 | }, 178 | "content": "Expected type number but found type string" 179 | }, 180 | { 181 | "element": "annotation", 182 | "meta": { 183 | "classes": { 184 | "element": "array", 185 | "content": [ 186 | { 187 | "element": "string", 188 | "content": "warning" 189 | } 190 | ] 191 | }, 192 | "links": { 193 | "element": "array", 194 | "content": [ 195 | { 196 | "element": "link", 197 | "attributes": { 198 | "relation": { 199 | "element": "string", 200 | "content": "origin" 201 | }, 202 | "href": { 203 | "element": "string", 204 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 205 | } 206 | } 207 | } 208 | ] 209 | } 210 | }, 211 | "attributes": { 212 | "code": { 213 | "element": "number", 214 | "content": 6 215 | }, 216 | "sourceMap": { 217 | "element": "array", 218 | "content": [ 219 | { 220 | "element": "sourceMap", 221 | "content": [ 222 | { 223 | "element": "array", 224 | "content": [ 225 | { 226 | "element": "number", 227 | "attributes": { 228 | "line": { 229 | "element": "number", 230 | "content": 16 231 | }, 232 | "column": { 233 | "element": "number", 234 | "content": 14 235 | } 236 | }, 237 | "content": 346 238 | }, 239 | { 240 | "element": "number", 241 | "attributes": { 242 | "line": { 243 | "element": "number", 244 | "content": 16 245 | }, 246 | "column": { 247 | "element": "number", 248 | "content": 28 249 | } 250 | }, 251 | "content": 14 252 | } 253 | ] 254 | } 255 | ] 256 | } 257 | ] 258 | } 259 | }, 260 | "content": "Expected type number but found type string" 261 | } 262 | ] 263 | } -------------------------------------------------------------------------------- /test/fixtures/headers-type-warning.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Example Header Values 4 | version: '1.0' 5 | paths: 6 | '/test': 7 | get: 8 | responses: 9 | 204: 10 | description: Header Value Example 11 | headers: 12 | X-RateLimit: 13 | type: number 14 | default: five 15 | X-RateLimit-RetryAfter: 16 | type: number 17 | x-example: ten 18 | -------------------------------------------------------------------------------- /test/fixtures/invalid-media-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Test bad media type" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0.0" 25 | }, 26 | "metadata": { 27 | "element": "array", 28 | "content": [ 29 | { 30 | "element": "member", 31 | "meta": { 32 | "classes": { 33 | "element": "array", 34 | "content": [ 35 | { 36 | "element": "string", 37 | "content": "user" 38 | } 39 | ] 40 | } 41 | }, 42 | "content": { 43 | "key": { 44 | "element": "string", 45 | "content": "HOST" 46 | }, 47 | "value": { 48 | "element": "string", 49 | "content": "petstore.swagger.io" 50 | } 51 | } 52 | } 53 | ] 54 | } 55 | }, 56 | "content": [ 57 | { 58 | "element": "resource", 59 | "attributes": { 60 | "href": { 61 | "element": "string", 62 | "content": "/" 63 | } 64 | }, 65 | "content": [ 66 | { 67 | "element": "transition", 68 | "content": [ 69 | { 70 | "element": "httpTransaction", 71 | "content": [ 72 | { 73 | "element": "httpRequest", 74 | "attributes": { 75 | "method": { 76 | "element": "string", 77 | "content": "GET" 78 | } 79 | } 80 | }, 81 | { 82 | "element": "httpResponse", 83 | "attributes": { 84 | "statusCode": { 85 | "element": "string", 86 | "content": "204" 87 | } 88 | }, 89 | "content": [ 90 | { 91 | "element": "copy", 92 | "content": "Get company info" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | ] 103 | }, 104 | { 105 | "element": "annotation", 106 | "meta": { 107 | "classes": { 108 | "element": "array", 109 | "content": [ 110 | { 111 | "element": "string", 112 | "content": "warning" 113 | } 114 | ] 115 | }, 116 | "links": { 117 | "element": "array", 118 | "content": [ 119 | { 120 | "element": "link", 121 | "attributes": { 122 | "relation": { 123 | "element": "string", 124 | "content": "origin" 125 | }, 126 | "href": { 127 | "element": "string", 128 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 129 | } 130 | } 131 | } 132 | ] 133 | } 134 | }, 135 | "attributes": { 136 | "code": { 137 | "element": "number", 138 | "content": 6 139 | }, 140 | "sourceMap": { 141 | "element": "array", 142 | "content": [ 143 | { 144 | "element": "sourceMap", 145 | "content": [ 146 | { 147 | "element": "array", 148 | "content": [ 149 | { 150 | "element": "number", 151 | "attributes": { 152 | "line": { 153 | "element": "number", 154 | "content": 6 155 | }, 156 | "column": { 157 | "element": "number", 158 | "content": 4 159 | } 160 | }, 161 | "content": 107 162 | }, 163 | { 164 | "element": "number", 165 | "attributes": { 166 | "line": { 167 | "element": "number", 168 | "content": 6 169 | }, 170 | "column": { 171 | "element": "number", 172 | "content": 13 173 | } 174 | }, 175 | "content": 9 176 | } 177 | ] 178 | } 179 | ] 180 | } 181 | ] 182 | } 183 | }, 184 | "content": "Invalid content type 'form-data', invalid media type" 185 | } 186 | ] 187 | } -------------------------------------------------------------------------------- /test/fixtures/invalid-media-type.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Test bad media type 5 | host: petstore.swagger.io 6 | consumes: 7 | - form-data 8 | paths: 9 | /: 10 | get: 11 | responses: 12 | 204: 13 | description: Get company info 14 | -------------------------------------------------------------------------------- /test/fixtures/invalid-reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Test References" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/external" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | }, 50 | "headers": { 51 | "element": "httpHeaders", 52 | "content": [ 53 | { 54 | "element": "member", 55 | "meta": { 56 | "links": { 57 | "element": "array", 58 | "content": [ 59 | { 60 | "element": "link", 61 | "attributes": { 62 | "relation": { 63 | "element": "string", 64 | "content": "inferred" 65 | }, 66 | "href": { 67 | "element": "string", 68 | "content": "http://docs.apiary.io/validations/swagger#produces-accept" 69 | } 70 | } 71 | } 72 | ] 73 | } 74 | }, 75 | "content": { 76 | "key": { 77 | "element": "string", 78 | "content": "Accept" 79 | }, 80 | "value": { 81 | "element": "string", 82 | "content": "application/json" 83 | } 84 | } 85 | } 86 | ] 87 | } 88 | } 89 | }, 90 | { 91 | "element": "httpResponse", 92 | "attributes": { 93 | "headers": { 94 | "element": "httpHeaders", 95 | "content": [ 96 | { 97 | "element": "member", 98 | "meta": { 99 | "links": { 100 | "element": "array", 101 | "content": [ 102 | { 103 | "element": "link", 104 | "attributes": { 105 | "relation": { 106 | "element": "string", 107 | "content": "inferred" 108 | }, 109 | "href": { 110 | "element": "string", 111 | "content": "http://docs.apiary.io/validations/swagger#produces-content-type" 112 | } 113 | } 114 | } 115 | ] 116 | } 117 | }, 118 | "content": { 119 | "key": { 120 | "element": "string", 121 | "content": "Content-Type" 122 | }, 123 | "value": { 124 | "element": "string", 125 | "content": "application/json" 126 | } 127 | } 128 | } 129 | ] 130 | }, 131 | "statusCode": { 132 | "element": "string", 133 | "content": "200" 134 | } 135 | }, 136 | "content": [ 137 | { 138 | "element": "copy", 139 | "content": "External Reference" 140 | } 141 | ] 142 | } 143 | ] 144 | } 145 | ] 146 | } 147 | ] 148 | } 149 | ] 150 | }, 151 | { 152 | "element": "annotation", 153 | "meta": { 154 | "classes": { 155 | "element": "array", 156 | "content": [ 157 | { 158 | "element": "string", 159 | "content": "error" 160 | } 161 | ] 162 | }, 163 | "links": { 164 | "element": "array", 165 | "content": [ 166 | { 167 | "element": "link", 168 | "attributes": { 169 | "relation": { 170 | "element": "string", 171 | "content": "origin" 172 | }, 173 | "href": { 174 | "element": "string", 175 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 176 | } 177 | } 178 | } 179 | ] 180 | } 181 | }, 182 | "attributes": { 183 | "code": { 184 | "element": "number", 185 | "content": 4 186 | }, 187 | "sourceMap": { 188 | "element": "array", 189 | "content": [ 190 | { 191 | "element": "sourceMap", 192 | "content": [ 193 | { 194 | "element": "array", 195 | "content": [ 196 | { 197 | "element": "number", 198 | "attributes": { 199 | "line": { 200 | "element": "number", 201 | "content": 12 202 | }, 203 | "column": { 204 | "element": "number", 205 | "content": 10 206 | } 207 | }, 208 | "content": 205 209 | }, 210 | { 211 | "element": "number", 212 | "attributes": { 213 | "line": { 214 | "element": "number", 215 | "content": 14 216 | }, 217 | "column": { 218 | "element": "number", 219 | "content": 0 220 | } 221 | }, 222 | "content": 48 223 | } 224 | ] 225 | } 226 | ] 227 | } 228 | ] 229 | } 230 | }, 231 | "content": "Schema reference must start with document root (#)" 232 | } 233 | ] 234 | } -------------------------------------------------------------------------------- /test/fixtures/invalid-reference.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Test References 5 | produces: 6 | - application/json 7 | paths: 8 | /external: 9 | get: 10 | responses: 11 | 200: 12 | description: External Reference 13 | schema: 14 | $ref: 'https://example.com' 15 | -------------------------------------------------------------------------------- /test/fixtures/json-body-generation.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Data Structure Generation 5 | produces: 6 | - application/json 7 | paths: 8 | /user: 9 | get: 10 | description: Get a resource 11 | operationId: getResource 12 | responses: 13 | 200: 14 | description: response description 15 | schema: 16 | type: object 17 | properties: 18 | id: 19 | type: number 20 | example: 1 21 | name: 22 | type: string 23 | example: doe 24 | -------------------------------------------------------------------------------- /test/fixtures/non-existing-elm-in-sequence.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "error" 13 | } 14 | ] 15 | }, 16 | "links": { 17 | "element": "array", 18 | "content": [ 19 | { 20 | "element": "link", 21 | "attributes": { 22 | "relation": { 23 | "element": "string", 24 | "content": "origin" 25 | }, 26 | "href": { 27 | "element": "string", 28 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | "attributes": { 36 | "code": { 37 | "element": "number", 38 | "content": 4 39 | }, 40 | "sourceMap": { 41 | "element": "array", 42 | "content": [ 43 | { 44 | "element": "sourceMap", 45 | "content": [ 46 | { 47 | "element": "array", 48 | "content": [ 49 | { 50 | "element": "number", 51 | "attributes": { 52 | "line": { 53 | "element": "number", 54 | "content": 11 55 | }, 56 | "column": { 57 | "element": "number", 58 | "content": 9 59 | } 60 | }, 61 | "content": 226 62 | }, 63 | { 64 | "element": "number", 65 | "attributes": { 66 | "line": { 67 | "element": "number", 68 | "content": 11 69 | }, 70 | "column": { 71 | "element": "number", 72 | "content": 9 73 | } 74 | }, 75 | "content": 0 76 | } 77 | ] 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | }, 84 | "content": "Expected type string but found type object" 85 | }, 86 | { 87 | "element": "annotation", 88 | "meta": { 89 | "classes": { 90 | "element": "array", 91 | "content": [ 92 | { 93 | "element": "string", 94 | "content": "error" 95 | } 96 | ] 97 | }, 98 | "links": { 99 | "element": "array", 100 | "content": [ 101 | { 102 | "element": "link", 103 | "attributes": { 104 | "relation": { 105 | "element": "string", 106 | "content": "origin" 107 | }, 108 | "href": { 109 | "element": "string", 110 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 111 | } 112 | } 113 | } 114 | ] 115 | } 116 | }, 117 | "attributes": { 118 | "code": { 119 | "element": "number", 120 | "content": 4 121 | }, 122 | "sourceMap": { 123 | "element": "array", 124 | "content": [ 125 | { 126 | "element": "sourceMap", 127 | "content": [ 128 | { 129 | "element": "array", 130 | "content": [ 131 | { 132 | "element": "number", 133 | "attributes": { 134 | "line": { 135 | "element": "number", 136 | "content": 11 137 | }, 138 | "column": { 139 | "element": "number", 140 | "content": 9 141 | } 142 | }, 143 | "content": 226 144 | }, 145 | { 146 | "element": "number", 147 | "attributes": { 148 | "line": { 149 | "element": "number", 150 | "content": 11 151 | }, 152 | "column": { 153 | "element": "number", 154 | "content": 9 155 | } 156 | }, 157 | "content": 0 158 | } 159 | ] 160 | } 161 | ] 162 | } 163 | ] 164 | } 165 | }, 166 | "content": "Expected type string but found type null" 167 | } 168 | ] 169 | } -------------------------------------------------------------------------------- /test/fixtures/non-existing-elm-in-sequence.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: Test non-existing element in yaml sequence 5 | host: petstore.swagger.io 6 | definitions: 7 | Company: 8 | allOf: 9 | - $ref: '#/definitions/User' 10 | - required: 11 | - id 12 | - 13 | - properties: 14 | id: 15 | type: string 16 | 17 | User: 18 | properties: 19 | name: 20 | type: string 21 | required: 22 | - name 23 | 24 | -------------------------------------------------------------------------------- /test/fixtures/operation-consumes-invalid-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Consumes JSON with invalid content type" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/test" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | } 50 | }, 51 | "content": [ 52 | { 53 | "element": "asset", 54 | "meta": { 55 | "classes": { 56 | "element": "array", 57 | "content": [ 58 | { 59 | "element": "string", 60 | "content": "messageBodySchema" 61 | } 62 | ] 63 | } 64 | }, 65 | "attributes": { 66 | "contentType": { 67 | "element": "string", 68 | "content": "application/schema+json" 69 | } 70 | }, 71 | "content": "{\"type\":\"object\"}" 72 | }, 73 | { 74 | "element": "dataStructure", 75 | "content": { 76 | "element": "object" 77 | } 78 | } 79 | ] 80 | }, 81 | { 82 | "element": "httpResponse", 83 | "attributes": { 84 | "statusCode": { 85 | "element": "string", 86 | "content": "200" 87 | } 88 | }, 89 | "content": [ 90 | { 91 | "element": "copy", 92 | "content": "My Response" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | ] 103 | }, 104 | { 105 | "element": "annotation", 106 | "meta": { 107 | "classes": { 108 | "element": "array", 109 | "content": [ 110 | { 111 | "element": "string", 112 | "content": "warning" 113 | } 114 | ] 115 | }, 116 | "links": { 117 | "element": "array", 118 | "content": [ 119 | { 120 | "element": "link", 121 | "attributes": { 122 | "relation": { 123 | "element": "string", 124 | "content": "origin" 125 | }, 126 | "href": { 127 | "element": "string", 128 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 129 | } 130 | } 131 | } 132 | ] 133 | } 134 | }, 135 | "attributes": { 136 | "code": { 137 | "element": "number", 138 | "content": 6 139 | }, 140 | "sourceMap": { 141 | "element": "array", 142 | "content": [ 143 | { 144 | "element": "sourceMap", 145 | "content": [ 146 | { 147 | "element": "array", 148 | "content": [ 149 | { 150 | "element": "number", 151 | "attributes": { 152 | "line": { 153 | "element": "number", 154 | "content": 8 155 | }, 156 | "column": { 157 | "element": "number", 158 | "content": 10 159 | } 160 | }, 161 | "content": 140 162 | }, 163 | { 164 | "element": "number", 165 | "attributes": { 166 | "line": { 167 | "element": "number", 168 | "content": 8 169 | }, 170 | "column": { 171 | "element": "number", 172 | "content": 39 173 | } 174 | }, 175 | "content": 29 176 | } 177 | ] 178 | } 179 | ] 180 | } 181 | ] 182 | } 183 | }, 184 | "content": "Invalid content type 'application/hal+json; invalid', invalid parameter format" 185 | } 186 | ] 187 | } -------------------------------------------------------------------------------- /test/fixtures/operation-consumes-invalid-type.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Consumes JSON with invalid content type 4 | version: '1.0' 5 | paths: 6 | '/test': 7 | get: 8 | consumes: 9 | - application/hal+json; invalid 10 | parameters: 11 | - name: name 12 | in: body 13 | required: true 14 | schema: 15 | type: object 16 | responses: 17 | 200: 18 | description: 'My Response' 19 | -------------------------------------------------------------------------------- /test/fixtures/operation-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Simple API overview" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "v2" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/path" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "extension", 39 | "meta": { 40 | "links": { 41 | "element": "array", 42 | "content": [ 43 | { 44 | "element": "link", 45 | "attributes": { 46 | "relation": { 47 | "element": "string", 48 | "content": "profile" 49 | }, 50 | "href": { 51 | "element": "string", 52 | "content": "https://help.apiary.io/profiles/api-elements/vendor-extensions/" 53 | } 54 | } 55 | } 56 | ] 57 | } 58 | }, 59 | "content": { 60 | "x-k": null 61 | } 62 | } 63 | ] 64 | } 65 | ] 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /test/fixtures/operation-extension.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: Simple API overview 4 | version: v2 5 | paths: 6 | /path: 7 | x-k: 8 | -------------------------------------------------------------------------------- /test/fixtures/operation-produces-invalid-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Produces JSON with invalid content type" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/test" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | } 50 | } 51 | }, 52 | { 53 | "element": "httpResponse", 54 | "attributes": { 55 | "statusCode": { 56 | "element": "string", 57 | "content": "200" 58 | } 59 | }, 60 | "content": [ 61 | { 62 | "element": "copy", 63 | "content": "My Response" 64 | }, 65 | { 66 | "element": "asset", 67 | "meta": { 68 | "classes": { 69 | "element": "array", 70 | "content": [ 71 | { 72 | "element": "string", 73 | "content": "messageBodySchema" 74 | } 75 | ] 76 | } 77 | }, 78 | "attributes": { 79 | "contentType": { 80 | "element": "string", 81 | "content": "application/schema+json" 82 | } 83 | }, 84 | "content": "{\"type\":\"object\"}" 85 | }, 86 | { 87 | "element": "dataStructure", 88 | "content": { 89 | "element": "object" 90 | } 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | }, 102 | { 103 | "element": "annotation", 104 | "meta": { 105 | "classes": { 106 | "element": "array", 107 | "content": [ 108 | { 109 | "element": "string", 110 | "content": "warning" 111 | } 112 | ] 113 | }, 114 | "links": { 115 | "element": "array", 116 | "content": [ 117 | { 118 | "element": "link", 119 | "attributes": { 120 | "relation": { 121 | "element": "string", 122 | "content": "origin" 123 | }, 124 | "href": { 125 | "element": "string", 126 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 127 | } 128 | } 129 | } 130 | ] 131 | } 132 | }, 133 | "attributes": { 134 | "code": { 135 | "element": "number", 136 | "content": 6 137 | }, 138 | "sourceMap": { 139 | "element": "array", 140 | "content": [ 141 | { 142 | "element": "sourceMap", 143 | "content": [ 144 | { 145 | "element": "array", 146 | "content": [ 147 | { 148 | "element": "number", 149 | "attributes": { 150 | "line": { 151 | "element": "number", 152 | "content": 8 153 | }, 154 | "column": { 155 | "element": "number", 156 | "content": 10 157 | } 158 | }, 159 | "content": 140 160 | }, 161 | { 162 | "element": "number", 163 | "attributes": { 164 | "line": { 165 | "element": "number", 166 | "content": 8 167 | }, 168 | "column": { 169 | "element": "number", 170 | "content": 39 171 | } 172 | }, 173 | "content": 29 174 | } 175 | ] 176 | } 177 | ] 178 | } 179 | ] 180 | } 181 | }, 182 | "content": "Invalid content type 'application/hal+json; invalid', invalid parameter format" 183 | } 184 | ] 185 | } -------------------------------------------------------------------------------- /test/fixtures/operation-produces-invalid-type.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Produces JSON with invalid content type 4 | version: '1.0' 5 | paths: 6 | '/test': 7 | get: 8 | produces: 9 | - application/hal+json; invalid 10 | responses: 11 | 200: 12 | description: 'My Response' 13 | schema: 14 | type: object 15 | -------------------------------------------------------------------------------- /test/fixtures/parameter-array-default-warning.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Parameters" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "attributes": { 40 | "href": { 41 | "element": "string", 42 | "content": "/{?arg}" 43 | }, 44 | "hrefVariables": { 45 | "element": "hrefVariables", 46 | "content": [ 47 | { 48 | "element": "member", 49 | "meta": { 50 | "description": { 51 | "element": "string", 52 | "content": "Query argument" 53 | } 54 | }, 55 | "content": { 56 | "key": { 57 | "element": "string", 58 | "content": "arg" 59 | }, 60 | "value": { 61 | "element": "array", 62 | "content": [ 63 | { 64 | "element": "string", 65 | "content": null 66 | } 67 | ] 68 | } 69 | } 70 | } 71 | ] 72 | } 73 | }, 74 | "content": [ 75 | { 76 | "element": "httpTransaction", 77 | "content": [ 78 | { 79 | "element": "httpRequest", 80 | "attributes": { 81 | "method": { 82 | "element": "string", 83 | "content": "GET" 84 | } 85 | } 86 | }, 87 | { 88 | "element": "httpResponse", 89 | "attributes": { 90 | "statusCode": { 91 | "element": "string", 92 | "content": "200" 93 | } 94 | }, 95 | "content": [ 96 | { 97 | "element": "copy", 98 | "content": "Response" 99 | }, 100 | { 101 | "element": "asset", 102 | "meta": { 103 | "classes": { 104 | "element": "array", 105 | "content": [ 106 | { 107 | "element": "string", 108 | "content": "messageBodySchema" 109 | } 110 | ] 111 | } 112 | }, 113 | "attributes": { 114 | "contentType": { 115 | "element": "string", 116 | "content": "application/schema+json" 117 | } 118 | }, 119 | "content": "{\"type\":\"string\"}" 120 | }, 121 | { 122 | "element": "dataStructure", 123 | "content": { 124 | "element": "string", 125 | "content": null 126 | } 127 | } 128 | ] 129 | } 130 | ] 131 | } 132 | ] 133 | } 134 | ] 135 | } 136 | ] 137 | }, 138 | { 139 | "element": "annotation", 140 | "meta": { 141 | "classes": { 142 | "element": "array", 143 | "content": [ 144 | { 145 | "element": "string", 146 | "content": "warning" 147 | } 148 | ] 149 | }, 150 | "links": { 151 | "element": "array", 152 | "content": [ 153 | { 154 | "element": "link", 155 | "attributes": { 156 | "relation": { 157 | "element": "string", 158 | "content": "origin" 159 | }, 160 | "href": { 161 | "element": "string", 162 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 163 | } 164 | } 165 | } 166 | ] 167 | } 168 | }, 169 | "attributes": { 170 | "code": { 171 | "element": "number", 172 | "content": 6 173 | }, 174 | "sourceMap": { 175 | "element": "array", 176 | "content": [ 177 | { 178 | "element": "sourceMap", 179 | "content": [ 180 | { 181 | "element": "array", 182 | "content": [ 183 | { 184 | "element": "number", 185 | "attributes": { 186 | "line": { 187 | "element": "number", 188 | "content": 14 189 | }, 190 | "column": { 191 | "element": "number", 192 | "content": 10 193 | } 194 | }, 195 | "content": 249 196 | }, 197 | { 198 | "element": "number", 199 | "attributes": { 200 | "line": { 201 | "element": "number", 202 | "content": 14 203 | }, 204 | "column": { 205 | "element": "number", 206 | "content": 20 207 | } 208 | }, 209 | "content": 10 210 | } 211 | ] 212 | } 213 | ] 214 | } 215 | ] 216 | } 217 | }, 218 | "content": "Expected type array but found type string" 219 | } 220 | ] 221 | } -------------------------------------------------------------------------------- /test/fixtures/parameter-array-default-warning.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Parameters 4 | version: '1.0' 5 | paths: 6 | /: 7 | get: 8 | parameters: 9 | - name: arg 10 | in: query 11 | description: Query argument 12 | type: array 13 | items: 14 | type: string 15 | default: A 16 | responses: 17 | 200: 18 | description: Response 19 | schema: 20 | type: string 21 | -------------------------------------------------------------------------------- /test/fixtures/parameters-header-type-warning.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Request Header Parameter Example" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | }, 50 | "headers": { 51 | "element": "httpHeaders", 52 | "content": [ 53 | { 54 | "element": "member", 55 | "content": { 56 | "key": { 57 | "element": "string", 58 | "content": "UserID" 59 | } 60 | } 61 | }, 62 | { 63 | "element": "member", 64 | "content": { 65 | "key": { 66 | "element": "string", 67 | "content": "RequestID" 68 | } 69 | } 70 | } 71 | ] 72 | } 73 | } 74 | }, 75 | { 76 | "element": "httpResponse", 77 | "attributes": { 78 | "statusCode": { 79 | "element": "string", 80 | "content": "204" 81 | } 82 | }, 83 | "content": [ 84 | { 85 | "element": "copy", 86 | "content": "Response" 87 | } 88 | ] 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ] 97 | }, 98 | { 99 | "element": "annotation", 100 | "meta": { 101 | "classes": { 102 | "element": "array", 103 | "content": [ 104 | { 105 | "element": "string", 106 | "content": "warning" 107 | } 108 | ] 109 | }, 110 | "links": { 111 | "element": "array", 112 | "content": [ 113 | { 114 | "element": "link", 115 | "attributes": { 116 | "relation": { 117 | "element": "string", 118 | "content": "origin" 119 | }, 120 | "href": { 121 | "element": "string", 122 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 123 | } 124 | } 125 | } 126 | ] 127 | } 128 | }, 129 | "attributes": { 130 | "code": { 131 | "element": "number", 132 | "content": 6 133 | }, 134 | "sourceMap": { 135 | "element": "array", 136 | "content": [ 137 | { 138 | "element": "sourceMap", 139 | "content": [ 140 | { 141 | "element": "array", 142 | "content": [ 143 | { 144 | "element": "number", 145 | "attributes": { 146 | "line": { 147 | "element": "number", 148 | "content": 11 149 | }, 150 | "column": { 151 | "element": "number", 152 | "content": 8 153 | } 154 | }, 155 | "content": 202 156 | }, 157 | { 158 | "element": "number", 159 | "attributes": { 160 | "line": { 161 | "element": "number", 162 | "content": 11 163 | }, 164 | "column": { 165 | "element": "number", 166 | "content": 23 167 | } 168 | }, 169 | "content": 15 170 | } 171 | ] 172 | } 173 | ] 174 | } 175 | ] 176 | } 177 | }, 178 | "content": "Expected type number but found type string" 179 | }, 180 | { 181 | "element": "annotation", 182 | "meta": { 183 | "classes": { 184 | "element": "array", 185 | "content": [ 186 | { 187 | "element": "string", 188 | "content": "warning" 189 | } 190 | ] 191 | }, 192 | "links": { 193 | "element": "array", 194 | "content": [ 195 | { 196 | "element": "link", 197 | "attributes": { 198 | "relation": { 199 | "element": "string", 200 | "content": "origin" 201 | }, 202 | "href": { 203 | "element": "string", 204 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 205 | } 206 | } 207 | } 208 | ] 209 | } 210 | }, 211 | "attributes": { 212 | "code": { 213 | "element": "number", 214 | "content": 6 215 | }, 216 | "sourceMap": { 217 | "element": "array", 218 | "content": [ 219 | { 220 | "element": "sourceMap", 221 | "content": [ 222 | { 223 | "element": "array", 224 | "content": [ 225 | { 226 | "element": "number", 227 | "attributes": { 228 | "line": { 229 | "element": "number", 230 | "content": 16 231 | }, 232 | "column": { 233 | "element": "number", 234 | "content": 8 235 | } 236 | }, 237 | "content": 313 238 | }, 239 | { 240 | "element": "number", 241 | "attributes": { 242 | "line": { 243 | "element": "number", 244 | "content": 16 245 | }, 246 | "column": { 247 | "element": "number", 248 | "content": 21 249 | } 250 | }, 251 | "content": 13 252 | } 253 | ] 254 | } 255 | ] 256 | } 257 | ] 258 | } 259 | }, 260 | "content": "Expected type number but found type string" 261 | } 262 | ] 263 | } -------------------------------------------------------------------------------- /test/fixtures/parameters-header-type-warning.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Request Header Parameter Example 4 | version: '1.0' 5 | paths: 6 | '/': 7 | parameters: 8 | - name: UserID 9 | in: header 10 | type: number 11 | required: true 12 | x-example: five 13 | - name: RequestID 14 | in: header 15 | type: number 16 | required: true 17 | default: five 18 | get: 19 | responses: 20 | 204: 21 | description: Response 22 | -------------------------------------------------------------------------------- /test/fixtures/produces-invalid-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Produces JSON with invalid content type" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/test" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "content": [ 40 | { 41 | "element": "httpTransaction", 42 | "content": [ 43 | { 44 | "element": "httpRequest", 45 | "attributes": { 46 | "method": { 47 | "element": "string", 48 | "content": "GET" 49 | } 50 | } 51 | }, 52 | { 53 | "element": "httpResponse", 54 | "attributes": { 55 | "statusCode": { 56 | "element": "string", 57 | "content": "200" 58 | } 59 | }, 60 | "content": [ 61 | { 62 | "element": "copy", 63 | "content": "My Response" 64 | }, 65 | { 66 | "element": "asset", 67 | "meta": { 68 | "classes": { 69 | "element": "array", 70 | "content": [ 71 | { 72 | "element": "string", 73 | "content": "messageBodySchema" 74 | } 75 | ] 76 | } 77 | }, 78 | "attributes": { 79 | "contentType": { 80 | "element": "string", 81 | "content": "application/schema+json" 82 | } 83 | }, 84 | "content": "{\"type\":\"object\"}" 85 | }, 86 | { 87 | "element": "dataStructure", 88 | "content": { 89 | "element": "object" 90 | } 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | }, 102 | { 103 | "element": "annotation", 104 | "meta": { 105 | "classes": { 106 | "element": "array", 107 | "content": [ 108 | { 109 | "element": "string", 110 | "content": "warning" 111 | } 112 | ] 113 | }, 114 | "links": { 115 | "element": "array", 116 | "content": [ 117 | { 118 | "element": "link", 119 | "attributes": { 120 | "relation": { 121 | "element": "string", 122 | "content": "origin" 123 | }, 124 | "href": { 125 | "element": "string", 126 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 127 | } 128 | } 129 | } 130 | ] 131 | } 132 | }, 133 | "attributes": { 134 | "code": { 135 | "element": "number", 136 | "content": 6 137 | }, 138 | "sourceMap": { 139 | "element": "array", 140 | "content": [ 141 | { 142 | "element": "sourceMap", 143 | "content": [ 144 | { 145 | "element": "array", 146 | "content": [ 147 | { 148 | "element": "number", 149 | "attributes": { 150 | "line": { 151 | "element": "number", 152 | "content": 5 153 | }, 154 | "column": { 155 | "element": "number", 156 | "content": 4 157 | } 158 | }, 159 | "content": 101 160 | }, 161 | { 162 | "element": "number", 163 | "attributes": { 164 | "line": { 165 | "element": "number", 166 | "content": 5 167 | }, 168 | "column": { 169 | "element": "number", 170 | "content": 33 171 | } 172 | }, 173 | "content": 29 174 | } 175 | ] 176 | } 177 | ] 178 | } 179 | ] 180 | } 181 | }, 182 | "content": "Invalid content type 'application/hal+json; invalid', invalid parameter format" 183 | } 184 | ] 185 | } -------------------------------------------------------------------------------- /test/fixtures/produces-invalid-type.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Produces JSON with invalid content type 4 | version: '1.0' 5 | produces: 6 | - application/hal+json; invalid 7 | paths: 8 | '/test': 9 | get: 10 | responses: 11 | 200: 12 | description: 'My Response' 13 | schema: 14 | type: object 15 | -------------------------------------------------------------------------------- /test/fixtures/request-body-primitive.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Primitive Body 4 | version: 1.0.0 5 | consumes: 6 | - text/plain 7 | paths: 8 | /string: 9 | post: 10 | summary: Example Operation Using String Body 11 | parameters: 12 | - in: body 13 | name: body 14 | required: true 15 | schema: 16 | type: string 17 | example: Http Request Body 18 | responses: 19 | 204: 20 | description: Success 21 | /number: 22 | post: 23 | summary: Example Operation Using Number Body 24 | parameters: 25 | - in: body 26 | name: body 27 | required: true 28 | schema: 29 | type: number 30 | example: 1.0 31 | responses: 32 | 204: 33 | description: Success 34 | /boolean: 35 | post: 36 | summary: Example Operation Using Boolean Body 37 | parameters: 38 | - in: body 39 | name: body 40 | required: true 41 | schema: 42 | type: boolean 43 | example: true 44 | responses: 45 | 204: 46 | description: Success 47 | -------------------------------------------------------------------------------- /test/fixtures/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "error" 13 | } 14 | ] 15 | }, 16 | "links": { 17 | "element": "array", 18 | "content": [ 19 | { 20 | "element": "link", 21 | "attributes": { 22 | "relation": { 23 | "element": "string", 24 | "content": "origin" 25 | }, 26 | "href": { 27 | "element": "string", 28 | "content": "http://docs.apiary.io/validations/swagger#yaml-parser" 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | "attributes": { 36 | "code": { 37 | "element": "number", 38 | "content": 1 39 | } 40 | }, 41 | "content": "Swagger document is not an object" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /test/fixtures/string.yaml: -------------------------------------------------------------------------------- 1 | Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/x-summary-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Resource summary as incorrect type coerces to string" 19 | } 20 | }, 21 | "attributes": { 22 | "version": { 23 | "element": "string", 24 | "content": "1.0" 25 | } 26 | }, 27 | "content": [ 28 | { 29 | "element": "resource", 30 | "meta": { 31 | "title": { 32 | "element": "string", 33 | "content": "true" 34 | } 35 | }, 36 | "attributes": { 37 | "href": { 38 | "element": "string", 39 | "content": "/boolean" 40 | } 41 | }, 42 | "content": [ 43 | { 44 | "element": "transition", 45 | "content": [ 46 | { 47 | "element": "httpTransaction", 48 | "content": [ 49 | { 50 | "element": "httpRequest", 51 | "attributes": { 52 | "method": { 53 | "element": "string", 54 | "content": "GET" 55 | } 56 | } 57 | }, 58 | { 59 | "element": "httpResponse", 60 | "attributes": { 61 | "statusCode": { 62 | "element": "string", 63 | "content": "200" 64 | } 65 | }, 66 | "content": [ 67 | { 68 | "element": "copy", 69 | "content": "My Response" 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | ] 78 | }, 79 | { 80 | "element": "resource", 81 | "meta": { 82 | "title": { 83 | "element": "string", 84 | "content": "1" 85 | } 86 | }, 87 | "attributes": { 88 | "href": { 89 | "element": "string", 90 | "content": "/number" 91 | } 92 | }, 93 | "content": [ 94 | { 95 | "element": "transition", 96 | "content": [ 97 | { 98 | "element": "httpTransaction", 99 | "content": [ 100 | { 101 | "element": "httpRequest", 102 | "attributes": { 103 | "method": { 104 | "element": "string", 105 | "content": "GET" 106 | } 107 | } 108 | }, 109 | { 110 | "element": "httpResponse", 111 | "attributes": { 112 | "statusCode": { 113 | "element": "string", 114 | "content": "200" 115 | } 116 | }, 117 | "content": [ 118 | { 119 | "element": "copy", 120 | "content": "My Response" 121 | } 122 | ] 123 | } 124 | ] 125 | } 126 | ] 127 | } 128 | ] 129 | } 130 | ] 131 | } 132 | ] 133 | } -------------------------------------------------------------------------------- /test/fixtures/x-summary-type.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Resource summary as incorrect type coerces to string 4 | version: '1.0' 5 | paths: 6 | '/boolean': 7 | x-summary: true 8 | get: 9 | responses: 10 | 200: 11 | description: 'My Response' 12 | '/number': 13 | x-summary: 1.0 14 | get: 15 | responses: 16 | 200: 17 | description: 'My Response' 18 | -------------------------------------------------------------------------------- /test/fixtures/yaml-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "error" 13 | } 14 | ] 15 | }, 16 | "links": { 17 | "element": "array", 18 | "content": [ 19 | { 20 | "element": "link", 21 | "attributes": { 22 | "relation": { 23 | "element": "string", 24 | "content": "origin" 25 | }, 26 | "href": { 27 | "element": "string", 28 | "content": "http://docs.apiary.io/validations/swagger#swagger-validation" 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | "attributes": { 36 | "code": { 37 | "element": "number", 38 | "content": 4 39 | } 40 | }, 41 | "content": "Additional properties not allowed: invalid" 42 | }, 43 | { 44 | "element": "annotation", 45 | "meta": { 46 | "classes": { 47 | "element": "array", 48 | "content": [ 49 | { 50 | "element": "string", 51 | "content": "warning" 52 | } 53 | ] 54 | }, 55 | "links": { 56 | "element": "array", 57 | "content": [ 58 | { 59 | "element": "link", 60 | "attributes": { 61 | "relation": { 62 | "element": "string", 63 | "content": "origin" 64 | }, 65 | "href": { 66 | "element": "string", 67 | "content": "http://docs.apiary.io/validations/swagger#yaml-parser" 68 | } 69 | } 70 | } 71 | ] 72 | } 73 | }, 74 | "attributes": { 75 | "code": { 76 | "element": "number", 77 | "content": 2 78 | }, 79 | "sourceMap": { 80 | "element": "array", 81 | "content": [ 82 | { 83 | "element": "sourceMap", 84 | "content": [ 85 | { 86 | "element": "array", 87 | "content": [ 88 | { 89 | "element": "number", 90 | "content": 94 91 | }, 92 | { 93 | "element": "number", 94 | "content": 1 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | }, 103 | "content": "YAML Syntax Error: expected , but found " 104 | } 105 | ] 106 | } -------------------------------------------------------------------------------- /test/fixtures/yaml-error.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: Simple API overview 4 | version: v2 5 | invalid: 6 | title: {x: true} description: {y: false} 7 | -------------------------------------------------------------------------------- /test/generator.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { Fury } from 'fury'; 4 | import { bodyFromSchema } from '../src/generator'; 5 | 6 | const { minim } = new Fury(); 7 | 8 | describe('bodyFromSchema', () => { 9 | const parser = { minim }; 10 | 11 | it('can generate a JSON body', () => { 12 | const schema = { 13 | type: 'array', 14 | }; 15 | 16 | const payload = { content: [] }; 17 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 18 | const body = JSON.parse(asset.content); 19 | 20 | expect(body).to.be.an('array'); 21 | }); 22 | 23 | it('can generate a JSON object for date format', () => { 24 | const schema = { 25 | type: 'string', 26 | format: 'date', 27 | }; 28 | 29 | const payload = { content: [] }; 30 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 31 | 32 | expect(asset.content).to.match(/^(19|20)[0-9]{2}-[0-1][0-9]-[0-3][0-9]$/); 33 | }); 34 | 35 | it('can generate a JSON object for date format', () => { 36 | const schema = { 37 | type: 'string', 38 | format: 'date-time', 39 | }; 40 | 41 | const payload = { content: [] }; 42 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 43 | 44 | expect(asset.content).to.match(/^(19|20)[0-9]{2}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]/); 45 | }); 46 | 47 | it('limits a strings min/max length to 256', () => { 48 | const schema = { 49 | type: 'string', 50 | minLength: 1000, 51 | maxLength: 2000, 52 | }; 53 | 54 | const payload = { content: [] }; 55 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 56 | 57 | expect(asset.content).to.be.a('string'); 58 | expect(asset.content.length).to.equal(256); 59 | }); 60 | 61 | it('limits an array min/max items to 5', () => { 62 | const schema = { 63 | type: 'array', 64 | items: { 65 | type: 'string', 66 | }, 67 | minItems: 10, 68 | maxItems: 20, 69 | }; 70 | 71 | const payload = { content: [] }; 72 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 73 | const body = JSON.parse(asset.content); 74 | 75 | expect(body).to.be.an('array'); 76 | expect(body.length).to.equal(5); 77 | }); 78 | 79 | it('can generate a structure with references', () => { 80 | const schema = { 81 | type: 'array', 82 | items: { 83 | $ref: '#/definitions/User', 84 | }, 85 | minItems: 1, 86 | maxItems: 1, 87 | definitions: { 88 | User: { 89 | type: 'object', 90 | properties: { 91 | name: { type: 'string', default: 'doe' }, 92 | }, 93 | }, 94 | }, 95 | }; 96 | 97 | const payload = { content: [] }; 98 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 99 | const body = JSON.parse(asset.content); 100 | 101 | expect(body).to.deep.equal([{ name: 'doe' }]); 102 | }); 103 | 104 | describe('multipart/form-data', () => { 105 | it('can generate multipart form with specified boundary', () => { 106 | const schema = { 107 | type: 'object', 108 | properties: { 109 | example: { 110 | type: 'string', 111 | enum: ['Hello'], 112 | }, 113 | }, 114 | required: ['example'], 115 | }; 116 | 117 | const payload = { content: [] }; 118 | const asset = bodyFromSchema(schema, payload, parser, 'multipart/form-data; boundary=boundy'); 119 | 120 | expect(asset.content).to.be.a('string'); 121 | expect(asset.content).to.equal('--boundy\r\nContent-Disposition: form-data; name="example"\r\n\r\nHello\r\n\r\n--boundy--\r\n'); 122 | }); 123 | 124 | it('can generate multipart form with multiple parts', () => { 125 | const schema = { 126 | type: 'object', 127 | properties: { 128 | example1: { 129 | type: 'string', 130 | enum: ['Hello'], 131 | }, 132 | example2: { 133 | type: 'string', 134 | enum: ['Hello'], 135 | }, 136 | }, 137 | required: ['example1', 'example2'], 138 | }; 139 | 140 | const payload = { content: [] }; 141 | const asset = bodyFromSchema(schema, payload, parser, 'multipart/form-data; boundary=boundy'); 142 | 143 | expect(asset.content).to.be.a('string'); 144 | expect(asset.content).to 145 | .equal('--boundy\r\nContent-Disposition: form-data; name="example1"\r\n\r\nHello\r\n' + 146 | '--boundy\r\nContent-Disposition: form-data; name="example2"\r\n\r\nHello\r\n' + 147 | '\r\n--boundy--\r\n'); 148 | }); 149 | }); 150 | 151 | describe('application/json', () => { 152 | const generate = (schema) => { 153 | const payload = { content: [] }; 154 | const asset = bodyFromSchema(schema, payload, parser, 'application/json'); 155 | return JSON.parse(asset.content); 156 | }; 157 | 158 | describe('array type', () => { 159 | it('can generate an array without any items', () => { 160 | const schema = { 161 | type: 'array', 162 | }; 163 | 164 | const body = generate(schema); 165 | 166 | expect(body).to.deep.equal([]); 167 | }); 168 | 169 | it('can generate an array with items', () => { 170 | const schema = { 171 | type: 'array', 172 | items: { 173 | type: 'string', 174 | examples: ['doe'], 175 | }, 176 | }; 177 | 178 | const body = generate(schema); 179 | 180 | expect(body).to.deep.equal(['doe']); 181 | }); 182 | }); 183 | 184 | describe('object type', () => { 185 | it('can generate an object without any properties', () => { 186 | const schema = { 187 | type: 'object', 188 | }; 189 | 190 | const body = generate(schema); 191 | 192 | expect(body).to.deep.equal({}); 193 | }); 194 | 195 | it('can generate a JSON object from optional properties', () => { 196 | const schema = { 197 | type: 'object', 198 | properties: { 199 | name: { 200 | type: 'string', 201 | examples: ['doe'], 202 | }, 203 | }, 204 | }; 205 | 206 | const body = generate(schema); 207 | 208 | expect(body).to.deep.equal({ name: 'doe' }); 209 | }); 210 | }); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /test/media-type.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | // Allows chai `expect(true).to.be.true;` 3 | 4 | import { expect } from 'chai'; 5 | import { isTextContentType, isMultiPartFormData, hasBoundary, parseBoundary } from '../src/media-type'; 6 | 7 | describe('#isTextContentType', () => { 8 | it('does not detect non-text content type', () => { 9 | expect(isTextContentType('application/json')).to.be.false; 10 | }); 11 | 12 | it('does not detect invalid content type', () => { 13 | expect(isTextContentType('')).to.be.false; 14 | }); 15 | 16 | it('detects plain text', () => { 17 | expect(isTextContentType('text/plain')).to.be.true; 18 | }); 19 | 20 | it('detects html text', () => { 21 | expect(isTextContentType('text/html')).to.be.true; 22 | }); 23 | 24 | it('detects text content type with version', () => { 25 | expect(isTextContentType('text/plain; version=1')).to.be.true; 26 | }); 27 | }); 28 | 29 | describe('#isMultiPartFormData', () => { 30 | it('does not detect non multipart form content type', () => { 31 | expect(isMultiPartFormData('application/json')).to.be.false; 32 | }); 33 | 34 | it('does not detect invalid content type', () => { 35 | expect(isMultiPartFormData('')).to.be.false; 36 | }); 37 | 38 | it('detects multipart/form-data', () => { 39 | expect(isMultiPartFormData('multipart/form-data')).to.be.true; 40 | }); 41 | 42 | it('detects text multipart/form-data with version', () => { 43 | expect(isMultiPartFormData('multipart/form-data; BOUNDARY=ABC')).to.be.true; 44 | }); 45 | }); 46 | 47 | describe('#hasBoundary', () => { 48 | it('returns false when the given content type does not have a boundary', () => { 49 | expect(hasBoundary('multipart/form-data')).to.be.false; 50 | }); 51 | 52 | it('returns true when the given content type has a boundary', () => { 53 | expect(hasBoundary('multipart/form-data; BOUNDARY=ABC')).to.be.true; 54 | }); 55 | }); 56 | 57 | describe('#parseBoundary', () => { 58 | it('defaults to BOUNDARY when no boundary is provided', () => { 59 | expect(parseBoundary('multipart/form-data')).to.equal('BOUNDARY'); 60 | }); 61 | 62 | it('parses the boundary from a content type', () => { 63 | expect(parseBoundary('multipart/form-data; BOUNDARY=ABC')).to.equal('ABC'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/parameter.js: -------------------------------------------------------------------------------- 1 | // Chai uses unused expressions for expect 2 | /* eslint-disable no-unused-expressions */ 3 | 4 | import { expect } from 'chai'; 5 | import { Fury } from 'fury'; 6 | import Parser from '../src/parser'; 7 | 8 | const { minim } = new Fury(); 9 | const { Annotation } = minim.elements; 10 | 11 | describe('Parameter to Member converter', () => { 12 | it('can convert a parameter to a member with x-example', () => { 13 | const parser = new Parser({ minim, source: '' }); 14 | const parameter = { 15 | in: 'query', 16 | name: 'tags', 17 | type: 'string', 18 | 'x-example': 'hello', 19 | }; 20 | const member = parser.convertParameterToMember(parameter); 21 | 22 | expect(member.value).to.be.instanceof(minim.elements.String); 23 | expect(member.value.toValue()).to.equal('hello'); 24 | }); 25 | 26 | it('can convert a parameter to a member with array x-example', () => { 27 | const parser = new Parser({ minim, source: '' }); 28 | const parameter = { 29 | in: 'query', 30 | name: 'tags', 31 | type: 'array', 32 | 'x-example': ['one', 'two'], 33 | }; 34 | const member = parser.convertParameterToMember(parameter); 35 | 36 | expect(member.value).to.be.instanceof(minim.elements.Array); 37 | expect(member.value.toValue()).to.deep.equal(['one', 'two']); 38 | }); 39 | 40 | it('can convert a parameter to a member with array x-example and items', () => { 41 | const parser = new Parser({ minim, source: '' }); 42 | const parameter = { 43 | in: 'query', 44 | name: 'tags', 45 | type: 'array', 46 | items: { 47 | type: 'string', 48 | }, 49 | 'x-example': ['one', 'two'], 50 | }; 51 | const member = parser.convertParameterToMember(parameter); 52 | 53 | expect(member.value).to.be.instanceof(minim.elements.Array); 54 | expect(member.value.toValue()).to.deep.equal(['one', 'two']); 55 | }); 56 | 57 | it('can convert a parameter to a member with array empty items', () => { 58 | const parser = new Parser({ minim, source: '' }); 59 | const parameter = { 60 | in: 'query', 61 | name: 'tags', 62 | type: 'array', 63 | items: { 64 | }, 65 | }; 66 | const member = parser.convertParameterToMember(parameter); 67 | 68 | expect(member.value).to.be.instanceof(minim.elements.Array); 69 | expect(member.value.toValue()).to.deep.equal([]); 70 | }); 71 | 72 | it('can convert a parameter to a member with array x-example and items but with string example', () => { 73 | const parser = new Parser({ minim, source: '' }); 74 | const parameter = { 75 | in: 'query', 76 | name: 'tags', 77 | type: 'array', 78 | items: { 79 | type: 'string', 80 | }, 81 | 'x-example': "['one', 'two']", 82 | }; 83 | 84 | parser.result = new minim.elements.ParseResult(); 85 | 86 | const member = parser.convertParameterToMember(parameter); 87 | 88 | expect(member.value).to.be.instanceof(minim.elements.Array); 89 | 90 | expect(member.value.length).to.equal(1); 91 | expect(member.value.get(0)).to.be.instanceof(minim.elements.String); 92 | expect(member.value.get(0).toValue()).to.be.null; 93 | 94 | expect(parser.result.toValue()).to.deep.equal(['Expected type array but found type string']); 95 | }); 96 | 97 | it('can convert a parameter with enum values to a member with enumerations', () => { 98 | const parser = new Parser({ minim, source: '' }); 99 | const parameter = { 100 | in: 'query', 101 | name: 'order', 102 | type: 'string', 103 | enum: ['ascending', 'descending'], 104 | }; 105 | const member = parser.convertParameterToMember(parameter); 106 | 107 | expect(member.value).to.be.instanceof(minim.elements.Element); 108 | const enumerations = member.value.attributes.get('enumerations'); 109 | 110 | expect(enumerations).to.be.instanceof(minim.elements.Array); 111 | expect(enumerations.toValue()).to.deep.equal(['ascending', 'descending']); 112 | }); 113 | 114 | it('can convert a parameter with array enum values to a member with enumerations', () => { 115 | const parser = new Parser({ minim, source: '' }); 116 | const parameter = { 117 | in: 'query', 118 | name: 'tags', 119 | type: 'array', 120 | items: { 121 | type: 'string', 122 | }, 123 | enum: [ 124 | ['hello'], 125 | ], 126 | }; 127 | const member = parser.convertParameterToMember(parameter); 128 | 129 | expect(member.value).to.be.instanceof(minim.elements.Element); 130 | const enumerations = member.value.attributes.get('enumerations'); 131 | 132 | expect(enumerations).to.be.instanceof(minim.elements.Array); 133 | expect(enumerations.toValue()).to.deep.equal([['hello']]); 134 | }); 135 | 136 | it('creates a warning when example does not match parameter type', () => { 137 | const parser = new Parser({ minim, source: '' }); 138 | parser.result = new minim.elements.ParseResult(); 139 | parser.convertParameterToMember({ 140 | type: 'string', 141 | 'x-example': 5, 142 | }); 143 | 144 | expect(parser.result.get(0)).to.be.instanceof(Annotation); 145 | expect(parser.result.toValue()).to.deep.equal(['Expected type string but found type integer']); 146 | }); 147 | 148 | it('creates a warning when default does not match parameter type', () => { 149 | const parser = new Parser({ minim, source: '' }); 150 | parser.result = new minim.elements.ParseResult(); 151 | parser.convertParameterToMember({ 152 | type: 'string', 153 | default: 5, 154 | }); 155 | 156 | expect(parser.result.get(0)).to.be.instanceof(Annotation); 157 | expect(parser.result.toValue()).to.deep.equal(['Expected type string but found type integer']); 158 | }); 159 | 160 | it('creates a warning when enum type does not match parameter type', () => { 161 | const parser = new Parser({ minim, source: '' }); 162 | parser.result = new minim.elements.ParseResult(); 163 | parser.convertParameterToMember({ 164 | type: 'string', 165 | enum: [5], 166 | }); 167 | 168 | expect(parser.result.get(0)).to.be.instanceof(Annotation); 169 | expect(parser.result.toValue()).to.deep.equal(['Expected type string but found type integer']); 170 | }); 171 | 172 | it('discards invalid parameter enumerations', () => { 173 | const parser = new Parser({ minim, source: '' }); 174 | parser.result = new minim.elements.ParseResult(); 175 | 176 | const parameter = { 177 | in: 'query', 178 | name: 'tags', 179 | type: 'array', 180 | items: { 181 | type: 'string', 182 | enum: ['red'], 183 | }, 184 | enum: ['red'], 185 | }; 186 | const member = parser.convertParameterToMember(parameter); 187 | 188 | expect(member.value).to.be.instanceof(minim.elements.Array); 189 | expect(member.value.get(0)).to.be.instanceof(minim.elements.Enum); 190 | expect(member.value.get(0).enumerations.toValue()).to.deep.equal(['red']); 191 | 192 | expect(parser.result.warnings.get(0)).to.be.instanceof(Annotation); 193 | expect(parser.result.warnings.toValue()).to.deep.equal(['Expected type array but found type string']); 194 | }); 195 | 196 | it('creates a warning when example does not match items parameter type', () => { 197 | const parser = new Parser({ minim, source: '' }); 198 | parser.result = new minim.elements.ParseResult(); 199 | parser.convertParameterToMember({ 200 | type: 'array', 201 | items: { 202 | type: 'string', 203 | }, 204 | 'x-example': [5], 205 | }); 206 | 207 | expect(parser.result.get(0)).to.be.instanceof(Annotation); 208 | expect(parser.result.toValue()).to.deep.equal(['Expected type string but found type integer']); 209 | }); 210 | 211 | it('coerces an integer value that does not match string parameter type', () => { 212 | const parser = new Parser({ minim, source: '' }); 213 | parser.result = new minim.elements.ParseResult(); 214 | const parameter = parser.convertParameterToElement({ 215 | type: 'string', 216 | 'x-example': 5, 217 | }); 218 | 219 | expect(parser.result.get(0)).to.be.instanceof(Annotation); 220 | expect(parameter.toValue()).to.equal('5'); 221 | }); 222 | 223 | it('coerces an boolean value that does not match string parameter type', () => { 224 | const parser = new Parser({ minim, source: '' }); 225 | parser.result = new minim.elements.ParseResult(); 226 | const parameter = parser.convertParameterToElement({ 227 | type: 'string', 228 | 'x-example': true, 229 | }); 230 | 231 | expect(parser.result.get(0)).to.be.instanceof(Annotation); 232 | expect(parameter.toValue()).to.equal('true'); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /test/parser.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import fury from 'fury'; 3 | import Parser from '../src/parser'; 4 | 5 | describe('Parser', () => { 6 | let parser; 7 | 8 | before(() => { 9 | parser = new Parser({ minim: fury.minim }); 10 | parser.swagger = { 11 | consumes: [], 12 | produces: [], 13 | }; 14 | }); 15 | 16 | context('content types', () => { 17 | it('gathers null response type when no examples or produces', () => { 18 | const methodValue = {}; 19 | const examples = {}; 20 | const contentTypes = parser.gatherResponseContentTypes(methodValue, examples); 21 | 22 | expect(contentTypes).to.deep.equal([null]); 23 | }); 24 | 25 | it('gathers all example response content types', () => { 26 | const methodValue = {}; 27 | const examples = { 28 | 'application/json': '', 29 | 'application/hal+json': '', 30 | 'application/xml': '', 31 | }; 32 | const contentTypes = parser.gatherResponseContentTypes(methodValue, examples); 33 | 34 | expect(contentTypes).to.deep.equal([ 35 | 'application/json', 36 | 'application/hal+json', 37 | 'application/xml', 38 | ]); 39 | }); 40 | 41 | it('gathers first JSON produces without examples', () => { 42 | const methodValue = { 43 | produces: [ 44 | 'text/plain', 45 | 'application/json', 46 | 'application/hal+json', 47 | ], 48 | }; 49 | const examples = {}; 50 | const contentTypes = parser.gatherResponseContentTypes(methodValue, examples); 51 | 52 | expect(contentTypes).to.deep.equal(['application/json']); 53 | }); 54 | 55 | it('gathers only example response content types with produces', () => { 56 | const methodValue = { 57 | produces: [ 58 | 'application/json', 59 | 'application/problem+json', 60 | ], 61 | }; 62 | const examples = { 63 | 'application/vnd.error+json': '', 64 | }; 65 | const contentTypes = parser.gatherResponseContentTypes(methodValue, examples); 66 | 67 | expect(contentTypes).to.deep.equal(['application/vnd.error+json']); 68 | }); 69 | 70 | it('rejects invalid content types when gathering response content types', () => { 71 | const methodValue = {}; 72 | const examples = { '!!!': '' }; 73 | const contentTypes = parser.gatherResponseContentTypes(methodValue, examples); 74 | 75 | expect(contentTypes).to.deep.equal([null]); 76 | }); 77 | }); 78 | 79 | context('schema', () => { 80 | it('can push schema onto HTTP message payload', () => { 81 | const schema = { type: 'object' }; 82 | const payload = new fury.minim.elements.HttpResponse(); 83 | parser.pushAssets(schema, payload); 84 | 85 | expect(payload.messageBodySchema.toValue()).to.deep.equal('{"type":"object"}'); 86 | }); 87 | 88 | it('strips extensions from schema', () => { 89 | const schema = { type: 'object', 'x-test': true }; 90 | const payload = new fury.minim.elements.HttpResponse(); 91 | parser.pushAssets(schema, payload); 92 | 93 | expect(payload.messageBodySchema.toValue()).to.deep.equal('{"type":"object"}'); 94 | }); 95 | 96 | it('adds null to type when x-nullable is provided', () => { 97 | const schema = { type: 'object', 'x-nullable': true }; 98 | const payload = new fury.minim.elements.HttpResponse(); 99 | parser.pushAssets(schema, payload); 100 | 101 | expect(payload.messageBodySchema.toValue()).to.deep.equal('{"type":["object","null"]}'); 102 | }); 103 | 104 | it('sets null as type when x-nullable is provided without type', () => { 105 | const schema = { 'x-nullable': true }; 106 | const payload = new fury.minim.elements.HttpResponse(); 107 | parser.pushAssets(schema, payload); 108 | 109 | expect(payload.messageBodySchema.toValue()).to.deep.equal('{"type":"null"}'); 110 | }); 111 | 112 | it('adds null value to enum when x-nullable is provided', () => { 113 | const schema = { 'x-nullable': true, enum: ['north', 'south'] }; 114 | const payload = new fury.minim.elements.HttpResponse(); 115 | parser.pushAssets(schema, payload); 116 | 117 | expect(payload.messageBodySchema.toValue()).to.deep.equal('{"enum":["north","south",null]}'); 118 | }); 119 | 120 | it('does not add null value to enum when x-nullable is provided and null in enum', () => { 121 | const schema = { 'x-nullable': true, enum: ['north', 'south', null] }; 122 | const payload = new fury.minim.elements.HttpResponse(); 123 | parser.pushAssets(schema, payload); 124 | 125 | expect(payload.messageBodySchema.toValue()).to.deep.equal('{"enum":["north","south",null]}'); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/uri-template.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import buildUriTemplate from '../src/uri-template'; 4 | 5 | describe('URI Template Handler', () => { 6 | context('when there are path object parameters', () => { 7 | context('when the path object parameters are not query parameters', () => { 8 | const basePath = '/api'; 9 | const href = '/pet/findByTags'; 10 | const pathObjectParams = [ 11 | { 12 | in: 'path', 13 | description: 'Path parameter from path object', 14 | name: 'fromPath', 15 | required: true, 16 | type: 'string', 17 | }, 18 | ]; 19 | const queryParams = [ 20 | { 21 | in: 'query', 22 | description: 'Tags to filter by', 23 | name: 'tags', 24 | required: true, 25 | type: 'string', 26 | }, 27 | { 28 | in: 'query', 29 | description: 'For tests. Unknown type of query parameter.', 30 | name: 'unknown', 31 | required: true, 32 | type: 'unknown', 33 | }, 34 | ]; 35 | 36 | it('returns the correct URI', () => { 37 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 38 | expect(hrefForResource).to.equal('/api/pet/findByTags{?tags,unknown}'); 39 | }); 40 | }); 41 | 42 | context('when there are no query parameters but have one path object parameter', () => { 43 | const basePath = '/api'; 44 | const href = '/pet/{id}'; 45 | const pathObjectParams = [ 46 | { 47 | in: 'path', 48 | description: 'Pet\'s identifier', 49 | name: 'id', 50 | required: true, 51 | type: 'number', 52 | }, 53 | ]; 54 | const queryParams = []; 55 | 56 | it('returns the correct URI', () => { 57 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 58 | expect(hrefForResource).to.equal('/api/pet/{id}'); 59 | }); 60 | }); 61 | 62 | context('when there are query parameters defined', () => { 63 | const basePath = '/api'; 64 | const href = '/pet/findByTags'; 65 | const pathObjectParams = [ 66 | { 67 | in: 'query', 68 | description: 'Query parameter from path object', 69 | name: 'fromPath', 70 | required: true, 71 | type: 'string', 72 | }, 73 | ]; 74 | const queryParams = [ 75 | { 76 | in: 'query', 77 | description: 'Tags to filter by', 78 | name: 'tags', 79 | required: true, 80 | type: 'string', 81 | }, 82 | { 83 | in: 'query', 84 | description: 'For tests. Unknown type of query parameter.', 85 | name: 'unknown', 86 | required: true, 87 | type: 'unknown', 88 | }, 89 | ]; 90 | 91 | it('returns the correct URI', () => { 92 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 93 | expect(hrefForResource).to.equal('/api/pet/findByTags{?fromPath,tags,unknown}'); 94 | }); 95 | }); 96 | 97 | context('when there are parameters with reserved characters', () => { 98 | const basePath = '/my-api'; 99 | const href = '/pet/{unique%2did}'; 100 | const queryParams = [ 101 | { 102 | in: 'query', 103 | description: 'Tags to filter by', 104 | name: 'tag-names[]', 105 | required: true, 106 | type: 'string', 107 | }, 108 | ]; 109 | 110 | it('returns the correct URI', () => { 111 | const hrefForResource = buildUriTemplate(basePath, href, [], queryParams); 112 | expect(hrefForResource).to.equal('/my-api/pet/{unique%2did}{?tag%2dnames%5B%5D}'); 113 | }); 114 | }); 115 | 116 | context('when there is a conflict in parameter names', () => { 117 | const basePath = '/api'; 118 | const href = '/pet/findByTags'; 119 | const pathObjectParams = [ 120 | { 121 | in: 'query', 122 | description: 'Tags to filter by', 123 | name: 'tags', 124 | required: true, 125 | type: 'string', 126 | }, 127 | ]; 128 | const queryParams = [ 129 | { 130 | in: 'query', 131 | description: 'Tags to filter by', 132 | name: 'tags', 133 | required: true, 134 | type: 'string', 135 | }, 136 | ]; 137 | 138 | it('only adds one to the query parameters', () => { 139 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 140 | expect(hrefForResource).to.equal('/api/pet/findByTags{?tags}'); 141 | }); 142 | }); 143 | 144 | context('when there are no query parameters defined', () => { 145 | const basePath = '/api'; 146 | const href = '/pet/findByTags'; 147 | const pathObjectParams = [ 148 | { 149 | in: 'query', 150 | description: 'Query parameter from path object', 151 | name: 'fromPath', 152 | required: true, 153 | type: 'string', 154 | }, 155 | ]; 156 | const queryParams = []; 157 | 158 | it('returns the correct URI', () => { 159 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 160 | expect(hrefForResource).to.equal('/api/pet/findByTags{?fromPath}'); 161 | }); 162 | }); 163 | }); 164 | 165 | context('when there are query parameters but no path object parameters', () => { 166 | const basePath = '/api'; 167 | const href = '/pet/findByTags'; 168 | const pathObjectParams = []; 169 | const queryParams = [ 170 | { 171 | in: 'query', 172 | description: 'Tags to filter by', 173 | name: 'tags', 174 | required: true, 175 | type: 'string', 176 | }, 177 | { 178 | in: 'query', 179 | description: 'For tests. Unknown type of query parameter.', 180 | name: 'unknown', 181 | required: true, 182 | type: 'unknown', 183 | }, 184 | ]; 185 | 186 | it('returns the correct URI', () => { 187 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 188 | expect(hrefForResource).to.equal('/api/pet/findByTags{?tags,unknown}'); 189 | }); 190 | }); 191 | 192 | context('when there are no query or path object parameters', () => { 193 | const basePath = '/api'; 194 | const href = '/pet/findByTags'; 195 | const pathObjectParams = []; 196 | const queryParams = []; 197 | 198 | it('returns the correct URI', () => { 199 | const hrefForResource = buildUriTemplate(basePath, href, pathObjectParams, queryParams); 200 | expect(hrefForResource).to.equal('/api/pet/findByTags'); 201 | }); 202 | }); 203 | 204 | describe('array parameters with collectionFormat', () => { 205 | it('returns a template with default format', () => { 206 | const parameter = { 207 | in: 'query', 208 | name: 'tags', 209 | type: 'array', 210 | }; 211 | 212 | const hrefForResource = buildUriTemplate('', '/example', [parameter]); 213 | expect(hrefForResource).to.equal('/example{?tags}'); 214 | }); 215 | 216 | it('returns a template with csv format', () => { 217 | const parameter = { 218 | in: 'query', 219 | name: 'tags', 220 | type: 'array', 221 | collectionFormat: 'csv', 222 | }; 223 | 224 | const hrefForResource = buildUriTemplate('', '/example', [parameter]); 225 | expect(hrefForResource).to.equal('/example{?tags}'); 226 | }); 227 | 228 | it('returns an exploded template with multi format', () => { 229 | const parameter = { 230 | in: 'query', 231 | name: 'tags', 232 | type: 'array', 233 | collectionFormat: 'multi', 234 | }; 235 | 236 | const hrefForResource = buildUriTemplate('', '/example', [parameter]); 237 | expect(hrefForResource).to.equal('/example{?tags*}'); 238 | }); 239 | }); 240 | }); 241 | -------------------------------------------------------------------------------- /test/with-path.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { expect } from 'chai'; 3 | import Parser from '../src/parser'; 4 | 5 | describe('Test parser.withPath() ', () => { 6 | let parser; 7 | const path = ['paths', '/', 'get']; 8 | 9 | beforeEach(() => { 10 | parser = new Parser({}); 11 | parser.path = _.clone(path); 12 | 13 | expect(parser.path).to.deep.equal(path); 14 | }); 15 | 16 | afterEach(() => { 17 | expect(parser.path).to.deep.equal(path); 18 | }); 19 | 20 | context('invoke with', () => { 21 | it('should work with no path', () => { 22 | parser.withPath((_path) => { 23 | expect(_path).to.deep.equal(path); 24 | }); 25 | }); 26 | 27 | it('should add segment', () => { 28 | parser.withPath('parameters', (_path) => { 29 | expect(_path).to.deep.equal(_.concat(path, 'parameters')); 30 | }); 31 | }); 32 | 33 | it('double dot should iterate in level up', () => { 34 | parser.withPath('..', (_path) => { 35 | expect(_path).to.deep.equal(_.take(path, 2)); 36 | }); 37 | }); 38 | 39 | it('double dot and segment should switch to sibling path', () => { 40 | parser.withPath('..', 'put', (_path) => { 41 | expect(_path).to.deep.equal(['paths', '/', 'put']); 42 | }); 43 | }); 44 | 45 | it('multiple `..` should not crash and switch into root', () => { 46 | parser.withPath('..', '..', '..', '..', '..', (_path) => { 47 | expect(_path).to.deep.equal([]); 48 | }); 49 | }); 50 | 51 | it('multiple `..` followed by segment shold switch into segment', () => { 52 | parser.withPath('..', '..', '..', '..', 'root', (_path) => { 53 | expect(_path).to.deep.equal(['root']); 54 | }); 55 | }); 56 | 57 | it('should work with `.` path', () => { 58 | parser.withPath('.', (_path) => { 59 | expect(_path).to.deep.equal(path); 60 | }); 61 | }); 62 | 63 | it('should work with `.` path and aditional segment', () => { 64 | parser.withPath('.', 'schema', (_path) => { 65 | expect(_path).to.deep.equal(_.concat(path, 'schema')); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /tonic-example.js: -------------------------------------------------------------------------------- 1 | // Test out the Swagger adapter in Fury to convert Swagger 2.0 documents 2 | // into Refract elements. 3 | const source = ` 4 | swagger: '2.0' 5 | info: 6 | version: '1.0.0' 7 | title: Swagger Test 8 | description: Minimal Swagger test example 9 | host: api.example.com 10 | schemes: 11 | - https 12 | paths: 13 | /test: 14 | get: 15 | responses: 16 | 200: 17 | description: I am a description 18 | examples: 19 | 'application/json': 20 | status: ok 21 | `; 22 | 23 | const fury = require('fury'); 24 | fury.use(require('fury-adapter-swagger')); 25 | 26 | fury.parse({source}, (err, result) => { 27 | if (err) { console.log(err) } 28 | if (result) { 29 | // Print out the refract to make overview and copy/paste easy 30 | console.log(JSON.stringify(result.toRefract(), null, 2)); 31 | // Output the js objects so you can inspect each element 32 | console.log(result.toRefract()); 33 | } 34 | }); 35 | --------------------------------------------------------------------------------