diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..a49d274d52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ + +npm-debug.log \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000000..ae855597f0 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,22 @@ +{ + "bitwise": true, + "browser": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "esnext": true, + "immed": true, + "indent": 2, + "latedef": true, + "laxcomma": true, + "newcap": true, + "noarg": true, + "node": true, + "quotmark": "single", + "regexp": true, + "smarttabs": true, + "strict": true, + "trailing": true, + "undef": true, + "unused": true +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..43469d85b8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Apigee Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..b39462cb54 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +The project provides various tools for integrating and interacting with Swagger. This project is in its infancy but +what is within the repository should be fully tested and reusable. Please visit the [issue tracker][project-issues] to +see what issues we are aware of and what features/enhancements we are working on. + +## Supported Swagger Versions + +* [1.2][swagger-docs-v1_2] + +## Features + +* Schema validation: For the file(s) supported by the Swagger specification, ensure they pass structural validation +based on the [JSON Schema][json-schema] associated with that version of the specification + +## Installation + +swagger-tools is distributed via [NPM][npm] so installation is the usual: `npm install swagger-tools --save` + +## Usage + +The swagger-tools module currently exposes one property: `v1_2`. This is a reference to an object that has the +following structure: + +* `docsUrl`: This is a link to the Swagger documentation for the corresponding specification version +* `schemasUrl`: This is a link to the Swagger JSON Schema files for the corresponding specification version +* `verison`: This is the Swagger specification version +* `schemas`: This is an object where the keys are the Swagger JSON Schema file names and the object is the loaded schema +contents +* `validate`: This is a function used to validate a Swagger document, as a JavaScript object, against a Swagger schema +file + +Here is an example showing how to use both versions of the `validate` function *(For more details, the sources are +documented)*: + +```javascript +var spec = require('swagger-tools').v1_2; +var petJson = require('./samples/1.2/pet.json'); +var rlJson = require('./samples/1.2/resource-listing.json'); +var petResults = spec.validate(petJson); // The default schema used is 'apiDeclaration.json' +var rlResults = spec.validate(rlJson, 'resourceListing.json'); +``` + +## Contributing + +This project uses [Gulp][gulp] for building so `npm install -g gulp` once you clone this project. Running `gulp` in the +project root will lint check the source code and run the unit tests. + +[gulp]: http://gulpjs.com/ +[json-schema]: http://json-schema.org/ +[npm]: https://www.npmjs.org/ +[project-issues]: https://github.com/apigee/swagger-tools/issues +[swagger]: https://helloreverb.com/developers/swagger +[swagger-docs-v1_2]: https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000000..6c52ed6246 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Apigee Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var gulp = require('gulp'); +var jshint = require('gulp-jshint'); +var mocha = require('gulp-mocha'); + +gulp.task('lint', function () { + return gulp.src([ + './index.js', + './lib/*.js', + './test/*.js', + './gulpfile.js' + ]) + .pipe(jshint()) + .pipe(jshint.reporter('jshint-stylish')) + .pipe(jshint.reporter('fail')); +}); + +gulp.task('test', function () { + return gulp.src('test/test-*.js') + .pipe(mocha({reporter: 'list'})); +}); + +gulp.task('default', ['lint', 'test']); diff --git a/index.js b/index.js new file mode 100644 index 0000000000..66c43f1083 --- /dev/null +++ b/index.js @@ -0,0 +1,205 @@ +/* + * Copyright 2014 Apigee Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// Module dependencies +var _ = require('lodash'); +var fs = require('fs'); +var jjv = require('jjv'); +var jjve = require('jjve'); + +var validatorDefaults = { + useDefault: false, + useCoerce: false, + checkRequired: true, + removeAdditional: false +}; + +var throwUnsupportedVersion = function (version) { + throw new Error(version + ' is an unsupported Swagger specification version'); +}; + +/** + * Creates a new Swagger specification object. + * + * @param {string} version - The Swagger version + * @param {object} [options] - The specification options (Currently used to pass validator options) + * @param {boolean} [options.useDefault=false] - If true it modifies the object to have the default values for missing + * non-required fields + * @param {boolean} [options.useCoerce=false] - If true it enables type coercion where defined + * @param {boolean} [options.checkRequired=true] - If true it reports missing required properties, otherwise it allows + * missing required properties + * @param {boolean} [options.removeAdditional=false] - If true it removes all attributes of an object which are not + * matched by the schema's specification + * @constructor + */ +var Specification = function Specification (version, options) { + var docsUrl; + var schemasUrl; + + options = _.defaults(options || {}, validatorDefaults); + + switch (version) { + case '1.2': + docsUrl = 'https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md'; + schemasUrl = 'https://github.com/wordnik/swagger-spec/tree/master/schemas/v1.2'; + + break; + default: + throwUnsupportedVersion(version); + } + + this.docsUrl = docsUrl; + this.options = options; + this.schemasUrl = schemasUrl; + this.version = version; + + // Load the schema files + this.schemas = {}; + + fs.readdirSync('./schemas/' + version) + .filter(function (name) { + return name.match(/^(.*)\.json$/); + }) + .forEach(function (name) { + this.schemas[name] = require('./schemas/' + version + '/' + name); + }.bind(this)); + + // Create the validators + this.validators = {}; + + switch (version) { + case '1.2': + Object.keys(this.schemas).forEach(function (schemaName) { + var validator = jjv(); + var toCompile = []; + + // Disable the 'uri' format checker as it's got issues: https://github.com/acornejo/jjv/issues/24 + validator.addFormat('uri', function() { + return true; + }); + + // Since some schemas depend on others, bring them in appropriately + switch (schemaName) { + case 'apiDeclaration.json': + toCompile = [ + 'dataTypeBase.json', + 'modelsObject.json', + 'oauth2GrantType.json', + 'authorizationObject.json', + 'parameterObject.json', + 'operationObject.json' + ]; + + break; + case 'authorizationObject.json': + toCompile.push('oauth2GrantType.json'); + + break; + case 'modelsObject.json': + toCompile.push('dataTypeBase.json'); + + break; + case 'operationObject.json': + toCompile = [ + 'dataTypeBase.json', + 'authorizationObject.json', + 'oauth2GrantType.json', + 'parameterObject.json' + ]; + + break; + + case 'parameterObject.json': + toCompile.push('dataTypeBase.json'); + + break; + + case 'resourceListing.json': + toCompile = [ + 'resourceObject.json', + 'infoObject.json', + 'oauth2GrantType.json', + 'authorizationObject.json' + ]; + + break; + } + + toCompile.push(schemaName); + + toCompile.forEach(function (schemaName) { + this.schemas[schemaName].id = schemaName; + + validator.addSchema(schemaName, this.schemas[schemaName]); + }.bind(this)); + + validator.je = jjve(validator); + + this.validators[schemaName] = validator; + }.bind(this)); + + break; + } +}; + +/** + * Returns the result of the validation of the Swagger document against its schema. + * + * @param {object} data - The object representing the Swagger document/fragment + * @param {string} [schemaName='apiDeclaration.json'] - The schema name to use to validate the document/fragment + * + * @returns undefined if validation passes or an array of error objects + */ +Specification.prototype.validate = function (data, schemaName) { + if (_.isUndefined(data)) { + throw new Error('data is required'); + } else if (!_.isObject(data)) { + throw new TypeError('data must be an object'); + } + + var schema; + var validator; + var result; + + switch (this.version) { + case '1.2': + // Default to 'apiDeclaration.json' + schemaName = schemaName || 'apiDeclaration.json'; + + schema = this.schemas[schemaName]; + + break; + default: + throwUnsupportedVersion(this.version); + } + + if (!schema) { + throw new Error('dataSchema is not valid. Valid schema names: ' + Object.keys(this.schemas).join(', ')); + } + + validator = this.validators[schemaName]; + result = validator.validate(schema, data); + + if (result) { + return validator.je(schema, data, result); + } else { + return undefined; + } +}; + +var v1_2 = module.exports.v1_2 = new Specification('1.2'); // jshint ignore:line diff --git a/package.json b/package.json new file mode 100644 index 0000000000..78d04c8fba --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "swagger-tools", + "version": "0.0.0", + "description": "Various tools for using and integrating with Swagger.", + "main": "index.js", + "scripts": { + "test": "gulp test" + }, + "author": { + "name": "Jeremy Whitlock", + "email": "jwhitlock@apache.org", + "url": "https://github.com/whitlockjc" + }, + "bugs": { + "url": "https://github.com/apigee/connect-swagger/issues" + }, + "homepage": "https://github.com/apigee/connect-swagger", + "licenses": [ + { + "type": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/apigee/connect-swagger.git" + }, + "keywords": [ + "connect", + "swagger" + ], + "devDependencies": { + "gulp": "^3.8.0", + "gulp-jshint": "^1.6.2", + "gulp-mocha": "^0.4.1", + "jshint-stylish": "^0.2.0" + }, + "dependencies": { + "jjv": "^1.0.0", + "jjve": "^0.2.1", + "lodash": "^2.4.1" + } +} diff --git a/samples/1.2/pet.json b/samples/1.2/pet.json new file mode 100644 index 0000000000..eefaca0749 --- /dev/null +++ b/samples/1.2/pet.json @@ -0,0 +1,444 @@ +{ + "apiVersion": "1.0.0", + "apis": [ + { + "operations": [ + { + "authorizations": {}, + "method": "GET", + "nickname": "getPetById", + "notes": "Returns a pet based on ID", + "parameters": [ + { + "allowMultiple":false, + "description": "ID of pet that needs to be fetched", + "format": "int64", + "maximum": "100000.0", + "minimum": "1.0", + "name": "petId", + "paramType": "path", + "required":true, + "type": "integer" + } + ], + "responseMessages": [ + { + "code":400, + "message": "Invalid ID supplied" + }, + { + "code":404, + "message": "Pet not found" + } + ], + "summary": "Find pet by ID", + "type": "Pet" + }, + { + "authorizations": { + "oauth2": [ + { + "description": "modify pets in your account", + "scope": "write:pets" + } + ] + }, + "method": "DELETE", + "nickname": "deletePet", + "notes": "", + "parameters": [ + { + "allowMultiple":false, + "description": "Pet id to delete", + "name": "petId", + "paramType": "path", + "required":true, + "type": "string" + } + ], + "responseMessages": [ + { + "code":400, + "message": "Invalid pet value" + } + ], + "summary": "Deletes a pet", + "type": "void" + }, + { + "authorizations": { + "oauth2": [ + { + "description": "modify pets in your account", + "scope": "write:pets" + } + ] + }, + "consumes": [ + "application/json", + "application/xml" + ], + "items": { + "$ref": "Pet" + }, + "method": "PATCH", + "nickname": "partialUpdate", + "notes": "", + "parameters": [ + { + "allowMultiple":false, + "description": "ID of pet that needs to be fetched", + "name": "petId", + "paramType": "path", + "required":true, + "type": "string" + }, + { + "allowMultiple":false, + "description": "Pet object that needs to be added to the store", + "name": "body", + "paramType": "body", + "required":true, + "type": "Pet" + } + ], + "produces": [ + "application/json", + "application/xml" + ], + "responseMessages": [ + { + "code":400, + "message": "Invalid tag value" + } + ], + "summary": "partial updates to a pet", + "type": "array" + }, + { + "authorizations": { + "oauth2": [ + { + "description": "modify pets in your account", + "scope": "write:pets" + } + ] + }, + "consumes": [ + "application/x-www-form-urlencoded" + ], + "method": "POST", + "nickname": "updatePetWithForm", + "notes": "", + "parameters": [ + { + "allowMultiple":false, + "description": "ID of pet that needs to be updated", + "name": "petId", + "paramType": "path", + "required":true, + "type": "string" + }, + { + "allowMultiple":false, + "description": "Updated name of the pet", + "name": "name", + "paramType": "form", + "required":false, + "type": "string" + }, + { + "allowMultiple":false, + "description": "Updated status of the pet", + "name": "status", + "paramType": "form", + "required":false, + "type": "string" + } + ], + "responseMessages": [ + { + "code":405, + "message": "Invalid input" + } + ], + "summary": "Updates a pet in the store with form data", + "type": "void" + } + ], + "path": "/pet/{petId}" + }, + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "modify pets in your account", + "scope": "write:pets" + }, + { + "description": "read your pets", + "scope": "read:pets" + } + ] + }, + "consumes": [ + "multipart/form-data" + ], + "method": "POST", + "nickname": "uploadFile", + "notes": "", + "parameters": [ + { + "allowMultiple":false, + "description": "Additional data to pass to server", + "name": "additionalMetadata", + "paramType": "form", + "required":false, + "type": "string" + }, + { + "allowMultiple":false, + "description": "file to upload", + "name": "file", + "paramType": "form", + "required":false, + "type": "File" + } + ], + "summary": "uploads an image", + "type": "void" + } + ], + "path": "/pet/uploadImage" + }, + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "modify pets in your account", + "scope": "write:pets" + } + ] + }, + "consumes": [ + "application/json", + "application/xml" + ], + "method": "POST", + "nickname": "addPet", + "notes": "", + "parameters": [ + { + "allowMultiple":false, + "description": "Pet object that needs to be added to the store", + "name": "body", + "paramType": "body", + "required":true, + "type": "Pet" + } + ], + "responseMessages": [ + { + "code":405, + "message": "Invalid input" + } + ], + "summary": "Add a new pet to the store", + "type": "void" + }, + { + "authorizations": { + + }, + "method": "PUT", + "nickname": "updatePet", + "notes": "", + "parameters": [ + { + "allowMultiple":false, + "description": "Pet object that needs to be updated in the store", + "name": "body", + "paramType": "body", + "required":true, + "type": "Pet" + } + ], + "responseMessages": [ + { + "code":400, + "message": "Invalid ID supplied" + }, + { + "code":404, + "message": "Pet not found" + }, + { + "code":405, + "message": "Validation exception" + } + ], + "summary": "Update an existing pet", + "type": "void" + } + ], + "path": "/pet" + }, + { + "operations": [ + { + "authorizations": { + + }, + "items": { + "$ref": "Pet" + }, + "method": "GET", + "nickname": "findPetsByStatus", + "notes": "Multiple status values can be provided with comma seperated strings", + "parameters": [ + { + "allowMultiple":true, + "defaultValue": "available", + "description": "Status values that need to be considered for filter", + "enum": [ + "available", + "pending", + "sold" + ], + "name": "status", + "paramType": "query", + "required":true, + "type": "string" + } + ], + "responseMessages": [ + { + "code":400, + "message": "Invalid status value" + } + ], + "summary": "Finds Pets by status", + "type": "array" + } + ], + "path": "/pet/findByStatus" + }, + { + "operations": [ + { + "authorizations": { + + }, + "deprecated": "true", + "items": { + "$ref": "Pet" + }, + "method": "GET", + "nickname": "findPetsByTags", + "notes": "Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.", + "parameters": [ + { + "allowMultiple":true, + "description": "Tags to filter by", + "name": "tags", + "paramType": "query", + "required":true, + "type": "string" + } + ], + "responseMessages": [ + { + "code":400, + "message": "Invalid tag value" + } + ], + "summary": "Finds Pets by tags", + "type": "array" + } + ], + "path": "/pet/findByTags" + } + ], + "basePath": "http://petstore.swagger.wordnik.com/api", + "models": { + "Category": { + "id": "Category", + "properties": { + "id": { + "format": "int64", + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "Pet": { + "id": "Pet", + "properties": { + "category": { + "$ref": "Category" + }, + "id": { + "description": "unique identifier for the pet", + "format": "int64", + "maximum": "100.0", + "minimum": "0.0", + "type": "integer" + }, + "name": { + "type": "string" + }, + "photoUrls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ], + "type": "string" + }, + "tags": { + "items": { + "$ref": "Tag" + }, + "type": "array" + } + }, + "required": [ + "id", + "name" + ] + }, + "Tag": { + "id": "Tag", + "properties": { + "id": { + "format": "int64", + "type": "integer" + }, + "name": { + "type": "string" + } + } + } + }, + "produces": [ + "application/json", + "application/xml", + "text/plain", + "text/html" + ], + "resourcePath": "/pet", + "swaggerVersion": "1.2" +} \ No newline at end of file diff --git a/samples/1.2/resource-listing.json b/samples/1.2/resource-listing.json new file mode 100644 index 0000000000..7956d48b50 --- /dev/null +++ b/samples/1.2/resource-listing.json @@ -0,0 +1,60 @@ +{ + "apiVersion": "1.0.0", + "apis": [ + { + "description": "Operations about pets", + "path": "/pet" + }, + { + "description": "Operations about user", + "path": "/user" + }, + { + "description": "Operations about store", + "path": "/store" + } + ], + "authorizations": { + "oauth2": { + "grantTypes": { + "authorization_code": { + "tokenEndpoint": { + "tokenName": "auth_code", + "url": "http://petstore.swagger.wordnik.com/oauth/token" + }, + "tokenRequestEndpoint": { + "clientIdName": "client_id", + "clientSecretName": "client_secret", + "url": "http://petstore.swagger.wordnik.com/oauth/requestToken" + } + }, + "implicit": { + "loginEndpoint": { + "url": "http://petstore.swagger.wordnik.com/oauth/dialog" + }, + "tokenName": "access_token" + } + }, + "scopes": [ + { + "description": "Modify pets in your account", + "scope": "write:pets" + }, + { + "description": "Read your pets", + "scope": "read:pets" + } + ], + "type": "oauth2" + } + }, + "info": { + "contact": "apiteam@wordnik.com", + "description": "This is a sample server Petstore server. You can find out more about Swagger \n at http://swagger.wordnik.com or on irc.freenode.net, #swagger. For this sample,\n you can use the api key \"special-key\" to test the authorization filters", + "license": "Apache 2.0", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html", + "termsOfServiceUrl": "http://helloreverb.com/terms/", + "title": "Swagger Sample App" + }, + "swaggerVersion": "1.2" +} diff --git a/samples/1.2/store.json b/samples/1.2/store.json new file mode 100644 index 0000000000..81eb7044ca --- /dev/null +++ b/samples/1.2/store.json @@ -0,0 +1,147 @@ +{ + "apiVersion": "1.0.0", + "apis": [ + { + "operations": [ + { + "authorizations": {}, + "method": "GET", + "nickname": "getOrderById", + "notes": "For valid response try integer IDs with value <= 5. Anything above 5 or nonintegers will generate API errors", + "parameters": [ + { + "allowMultiple": false, + "description": "ID of pet that needs to be fetched", + "name": "orderId", + "paramType": "path", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid ID supplied" + }, + { + "code": 404, + "message": "Order not found" + } + ], + "summary": "Find purchase order by ID", + "type": "Order" + }, + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "DELETE", + "nickname": "deleteOrder", + "notes": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "parameters": [ + { + "allowMultiple": false, + "description": "ID of the order that needs to be deleted", + "name": "orderId", + "paramType": "path", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid ID supplied" + }, + { + "code": 404, + "message": "Order not found" + } + ], + "summary": "Delete purchase order by ID", + "type": "void" + } + ], + "path": "/store/order/{orderId}" + }, + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "POST", + "nickname": "placeOrder", + "notes": "", + "parameters": [ + { + "allowMultiple": false, + "description": "order placed for purchasing the pet", + "name": "body", + "paramType": "body", + "required": true, + "type": "Order" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid order" + } + ], + "summary": "Place an order for a pet", + "type": "void" + } + ], + "path": "/store/order" + } + ], + "basePath": "http://petstore.swagger.wordnik.com/api", + "models": { + "Order": { + "id": "Order", + "properties": { + "id": { + "format": "int64", + "type": "integer" + }, + "petId": { + "format": "int64", + "type": "integer" + }, + "quantity": { + "format": "int32", + "type": "integer" + }, + "shipDate": { + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Order Status", + "enum": [ + "placed", + " approved", + " delivered" + ], + "type": "string" + } + } + } + }, + "produces": [ + "application/json" + ], + "resourcePath": "/store", + "swaggerVersion": "1.2" +} diff --git a/samples/1.2/user.json b/samples/1.2/user.json new file mode 100644 index 0000000000..ee9d63ee9e --- /dev/null +++ b/samples/1.2/user.json @@ -0,0 +1,308 @@ +{ + "apiVersion": "1.0.0", + "apis": [ + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "PUT", + "nickname": "updateUser", + "notes": "This can only be done by the logged in user.", + "parameters": [ + { + "allowMultiple": false, + "description": "name that need to be deleted", + "name": "username", + "paramType": "path", + "required": true, + "type": "string" + }, + { + "allowMultiple": false, + "description": "Updated user object", + "name": "body", + "paramType": "body", + "required": true, + "type": "User" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username supplied" + }, + { + "code": 404, + "message": "User not found" + } + ], + "summary": "Updated user", + "type": "void" + }, + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "DELETE", + "nickname": "deleteUser", + "notes": "This can only be done by the logged in user.", + "parameters": [ + { + "allowMultiple": false, + "description": "The name that needs to be deleted", + "name": "username", + "paramType": "path", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username supplied" + }, + { + "code": 404, + "message": "User not found" + } + ], + "summary": "Delete user", + "type": "void" + }, + { + "authorizations": {}, + "method": "GET", + "nickname": "getUserByName", + "notes": "", + "parameters": [ + { + "allowMultiple": false, + "description": "The name that needs to be fetched. Use user1 for testing.", + "name": "username", + "paramType": "path", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username supplied" + }, + { + "code": 404, + "message": "User not found" + } + ], + "summary": "Get user by user name", + "type": "User" + } + ], + "path": "/user/{username}" + }, + { + "operations": [ + { + "authorizations": {}, + "method": "GET", + "nickname": "loginUser", + "notes": "", + "parameters": [ + { + "allowMultiple": false, + "description": "The user name for login", + "name": "username", + "paramType": "query", + "required": true, + "type": "string" + }, + { + "allowMultiple": false, + "description": "The password for login in clear text", + "name": "password", + "paramType": "query", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username and password combination" + } + ], + "summary": "Logs user into the system", + "type": "string" + } + ], + "path": "/user/login" + }, + { + "operations": [ + { + "authorizations": {}, + "method": "GET", + "nickname": "logoutUser", + "notes": "", + "parameters": [], + "summary": "Logs out current logged in user session", + "type": "void" + } + ], + "path": "/user/logout" + }, + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "POST", + "nickname": "createUser", + "notes": "This can only be done by the logged in user.", + "parameters": [ + { + "allowMultiple": false, + "description": "Created user object", + "name": "body", + "paramType": "body", + "required": true, + "type": "User" + } + ], + "summary": "Create user", + "type": "void" + } + ], + "path": "/user" + }, + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "POST", + "nickname": "createUsersWithArrayInput", + "notes": "", + "parameters": [ + { + "allowMultiple": false, + "description": "List of user object", + "items": { + "$ref": "User" + }, + "name": "body", + "paramType": "body", + "required": true, + "type": "array" + } + ], + "summary": "Creates list of users with given input array", + "type": "void" + } + ], + "path": "/user/createWithArray" + }, + { + "operations": [ + { + "authorizations": { + "oauth2": [ + { + "description": "anything", + "scope": "test:anything" + } + ] + }, + "method": "POST", + "nickname": "createUsersWithListInput", + "notes": "", + "parameters": [ + { + "allowMultiple": false, + "description": "List of user object", + "items": { + "$ref": "User" + }, + "name": "body", + "paramType": "body", + "required": true, + "type": "array" + } + ], + "summary": "Creates list of users with given list input", + "type": "void" + } + ], + "path": "/user/createWithList" + } + ], + "basePath": "http://petstore.swagger.wordnik.com/api", + "models": { + "User": { + "id": "User", + "properties": { + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "id": { + "format": "int64", + "type": "integer" + }, + "lastName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "description": "User Status", + "enum": [ + "1-registered", + "2-active", + "3-closed" + ], + "format": "int32", + "type": "integer" + }, + "username": { + "type": "string" + } + } + } + }, + "produces": [ + "application/json" + ], + "resourcePath": "/user", + "swaggerVersion": "1.2" +} diff --git a/schemas/1.2/README.md b/schemas/1.2/README.md new file mode 100644 index 0000000000..4fe03f08a9 --- /dev/null +++ b/schemas/1.2/README.md @@ -0,0 +1,3 @@ +This folder contains the Swagger 1.2 specification schema files maintained here: + +https://github.com/wordnik/swagger-spec/tree/master/schemas/v1.2 diff --git a/schemas/1.2/apiDeclaration.json b/schemas/1.2/apiDeclaration.json new file mode 100644 index 0000000000..897b37274f --- /dev/null +++ b/schemas/1.2/apiDeclaration.json @@ -0,0 +1,60 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/apiDeclaration.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ "swaggerVersion", "basePath", "apis" ], + "properties": { + "swaggerVersion": { "enum": [ "1.2" ] }, + "apiVersion": { "type": "string" }, + "basePath": { + "type": "string", + "format": "uri", + "pattern": "^https?://" + }, + "resourcePath": { + "type": "string", + "format": "uri", + "pattern": "^/" + }, + "apis": { + "type": "array", + "items": { "$ref": "#/definitions/apiObject" } + }, + "models": { + "type": "object", + "additionalProperties": { + "$ref": "modelsObject.json#" + } + }, + "produces": { "$ref": "#/definitions/mimeTypeArray" }, + "consumes": { "$ref": "#/definitions/mimeTypeArray" }, + "authorizations": { "$ref": "authorizationObject.json#" } + }, + "additionalProperties": false, + "definitions": { + "apiObject": { + "type": "object", + "required": [ "path", "operations" ], + "properties": { + "path": { + "type": "string", + "format": "uri-template", + "pattern": "^/" + }, + "description": { "type": "string" }, + "operations": { + "type": "array", + "items": { "$ref": "operationObject.json#" } + } + }, + "additionalProperties": false + }, + "mimeTypeArray": { + "type": "array", + "items": { + "type": "string", + "format": "mime-type" + } + } + } +} diff --git a/schemas/1.2/authorizationObject.json b/schemas/1.2/authorizationObject.json new file mode 100644 index 0000000000..2319b30e2e --- /dev/null +++ b/schemas/1.2/authorizationObject.json @@ -0,0 +1,59 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/authorizationObject.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/basicAuth" + }, + { + "$ref": "#/definitions/apiKey" + }, + { + "$ref": "#/definitions/oauth2" + } + ] + }, + "definitions": { + "basicAuth": { + "required": [ "type" ], + "properties": { + "type": { "enum": [ "basicAuth" ] } + }, + "additionalProperties": false + }, + "apiKey": { + "required": [ "type", "passAs", "keyname" ], + "properties": { + "type": { "enum": [ "apiKey" ] }, + "passAs": { "enum": [ "header", "query" ] }, + "keyname": { "type": "string" } + }, + "additionalProperties": false + }, + "oauth2": { + "type": "object", + "required": [ "type", "grantTypes" ], + "properties": { + "type": { "enum": [ "oauth2" ] }, + "scopes": { + "type": "array", + "items": { "$ref": "#/definitions/oauth2Scope" } + }, + "grantTypes": { "$ref": "oauth2GrantType.json#" } + }, + "additionalProperties": false + }, + "oauth2Scope": { + "type": "object", + "required": [ "scope" ], + "properties": { + "scope": { "type": "string" }, + "description": { "type": "string" } + }, + "additionalProperties": false + } + } +} + diff --git a/schemas/1.2/dataType.json b/schemas/1.2/dataType.json new file mode 100644 index 0000000000..d2d647d263 --- /dev/null +++ b/schemas/1.2/dataType.json @@ -0,0 +1,132 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/dataType.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Data type as described by the specification (version 1.2)", + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/refType" }, + { "$ref": "#/definitions/voidType" }, + { "$ref": "#/definitions/primitiveType" }, + { "$ref": "#/definitions/modelType" }, + { "$ref": "#/definitions/arrayType" } + ], + "definitions": { + "refType": { + "required": [ "$ref" ], + "properties": { + "$ref": { "type": "string" } + }, + "additionalProperties": false + }, + "voidType": { + "enum": [ { "type": "void" } ] + }, + "modelType": { + "required": [ "type" ], + "properties": { + "type": { + "type": "string", + "not": { + "enum": [ "boolean", "integer", "number", "string", "array" ] + } + } + }, + "additionalProperties": false + }, + "primitiveType": { + "required": [ "type" ], + "properties": { + "type": { + "enum": [ "boolean", "integer", "number", "string" ] + }, + "format": { "type": "string" }, + "defaultValue": { + "not": { "type": [ "array", "object", "null" ] } + }, + "enum": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + }, + "minimum": { "type": "string" }, + "maximum": { "type": "string" } + }, + "additionalProperties": false, + "dependencies": { + "format": { + "oneOf": [ + { + "properties": { + "type": { "enum": [ "integer" ] }, + "format": { "enum": [ "int32", "int64" ] } + } + }, + { + "properties": { + "type": { "enum": [ "number" ] }, + "format": { "enum": [ "float", "double" ] } + } + }, + { + "properties": { + "type": { "enum": [ "string" ] }, + "format": { + "enum": [ "byte", "date", "date-time" ] + } + } + } + ] + }, + "enum": { + "properties": { + "type": { "enum": [ "string" ] } + } + }, + "minimum": { + "properties": { + "type": { "enum": [ "integer", "number" ] } + } + }, + "maximum": { + "properties": { + "type": { "enum": [ "integer", "number" ] } + } + } + } + }, + "arrayType": { + "required": [ "type", "items" ], + "properties": { + "type": { "enum": [ "array" ] }, + "items": { + "type": "array", + "items": { "$ref": "#/definitions/itemsObject" } + }, + "uniqueItems": { "type": "boolean" } + }, + "additionalProperties": false + }, + "itemsObject": { + "oneOf": [ + { + "$ref": "#/definitions/refType" + }, + { + "allOf": [ + { + "$ref": "#/definitions/primitiveType" + }, + { + "properties": { + "type": {}, + "format": {} + }, + "additionalProperties": false + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/schemas/1.2/dataTypeBase.json b/schemas/1.2/dataTypeBase.json new file mode 100644 index 0000000000..4043b08203 --- /dev/null +++ b/schemas/1.2/dataTypeBase.json @@ -0,0 +1,81 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/dataTypeBase.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Data type fields (section 4.3.3)", + "type": "object", + "oneOf": [ + { "required": [ "type" ] }, + { "required": [ "$ref" ] } + ], + "properties": { + "type": { "type": "string" }, + "$ref": { "type": "string" }, + "format": { "type": "string" }, + "defaultValue": { + "not": { "type": [ "array", "object", "null" ] } + }, + "enum": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "minItems": 1 + }, + "minimum": { "type": "string" }, + "maximum": { "type": "string" }, + "items": { "$ref": "#/definitions/itemsObject" }, + "uniqueItems": { "type": "boolean" } + }, + "dependencies": { + "format": { + "oneOf": [ + { + "properties": { + "type": { "enum": [ "integer" ] }, + "format": { "enum": [ "int32", "int64" ] } + } + }, + { + "properties": { + "type": { "enum": [ "number" ] }, + "format": { "enum": [ "float", "double" ] } + } + }, + { + "properties": { + "type": { "enum": [ "string" ] }, + "format": { + "enum": [ "byte", "date", "date-time" ] + } + } + } + ] + } + }, + "definitions": { + "itemsObject": { + "oneOf": [ + { + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { "type": "string" } + }, + "additionalProperties": false + }, + { + "allOf": [ + { "$ref": "#" }, + { + "required": [ "type" ], + "properties": { + "type": {}, + "format": {} + }, + "additionalProperties": false + } + ] + } + ] + } + } +} diff --git a/schemas/1.2/infoObject.json b/schemas/1.2/infoObject.json new file mode 100644 index 0000000000..1fd0f17247 --- /dev/null +++ b/schemas/1.2/infoObject.json @@ -0,0 +1,16 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/infoObject.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "info object (section 5.1.3)", + "type": "object", + "required": [ "title", "description" ], + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "termsOfServiceUrl": { "type": "string", "format": "uri" }, + "contact": { "type": "string", "format": "email" }, + "license": { "type": "string" }, + "licenseUrl": { "type": "string", "format": "uri" } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/1.2/modelsObject.json b/schemas/1.2/modelsObject.json new file mode 100644 index 0000000000..6cfeaf8fc7 --- /dev/null +++ b/schemas/1.2/modelsObject.json @@ -0,0 +1,36 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/modelsObject.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ "id", "properties" ], + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#/definitions/propertyObject" } + }, + "subTypes": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "discriminator": { "type": "string" } + }, + "dependencies": { + "subTypes": [ "discriminator" ] + }, + "definitions": { + "propertyObject": { + "allOf": [ + { + "not": { "$ref": "#" } + }, + { + "$ref": "dataTypeBase.json#" + } + ] + } + } +} + diff --git a/schemas/1.2/oauth2GrantType.json b/schemas/1.2/oauth2GrantType.json new file mode 100644 index 0000000000..ef4589a0aa --- /dev/null +++ b/schemas/1.2/oauth2GrantType.json @@ -0,0 +1,57 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/oauth2GrantType.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "minProperties": 1, + "properties": { + "implicit": { "$ref": "#/definitions/implicit" }, + "authorization_code": { "$ref": "#/definitions/authorizationCode" } + }, + "definitions": { + "implicit": { + "type": "object", + "required": [ "loginEndpoint" ], + "properties": { + "loginEndpoint": { "$ref": "#/definitions/loginEndpoint" }, + "tokenName": { "type": "string" } + }, + "additionalProperties": false + }, + "authorizationCode": { + "type": "object", + "required": [ "tokenEndpoint", "tokenRequestEndpoint" ], + "properties": { + "tokenEndpoint": { "$ref": "#/definitions/tokenEndpoint" }, + "tokenRequestEndpoint": { "$ref": "#/definitions/tokenRequestEndpoint" } + }, + "additionalProperties": false + }, + "loginEndpoint": { + "type": "object", + "required": [ "url" ], + "properties": { + "url": { "type": "string", "format": "uri" } + }, + "additionalProperties": false + }, + "tokenEndpoint": { + "type": "object", + "required": [ "url" ], + "properties": { + "url": { "type": "string", "format": "uri" }, + "tokenName": { "type": "string" } + }, + "additionalProperties": false + }, + "tokenRequestEndpoint": { + "type": "object", + "required": [ "url" ], + "properties": { + "url": { "type": "string", "format": "uri" }, + "clientIdName": { "type": "string" }, + "clientSecretName": { "type": "string" } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/schemas/1.2/operationObject.json b/schemas/1.2/operationObject.json new file mode 100644 index 0000000000..2166b3c473 --- /dev/null +++ b/schemas/1.2/operationObject.json @@ -0,0 +1,64 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/operationObject.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "allOf": [ + { "$ref": "dataTypeBase.json#" }, + { + "required": [ "method", "nickname", "parameters" ], + "properties": { + "method": { "enum": [ "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" ] }, + "summary": { "type": "string", "maxLength": 120 }, + "notes": { "type": "string" }, + "nickname": { + "type": "string", + "pattern": "^[a-zA-Z0-9_]+$" + }, + "authorizations": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "authorizationObject.json#/definitions/oauth2Scope" + } + } + }, + "parameters": { + "type": "array", + "items": { "$ref": "parameterObject.json#" } + }, + "responseMessages": { + "type": "array", + "items": { "$ref": "#/definitions/responseMessageObject"} + }, + "produces": { "$ref": "#/definitions/mimeTypeArray" }, + "consumes": { "$ref": "#/definitions/mimeTypeArray" }, + "deprecated": { "enum": [ "true", "false" ] } + } + } + ], + "definitions": { + "responseMessageObject": { + "type": "object", + "required": [ "code", "message" ], + "properties": { + "code": { "$ref": "#/definitions/rfc2616section10" }, + "message": { "type": "string" }, + "responseModel": { "type": "string" } + } + }, + "rfc2616section10": { + "type": "integer", + "minimum": 100, + "maximum": 600, + "exclusiveMaximum": true + }, + "mimeTypeArray": { + "type": "array", + "items": { + "type": "string", + "format": "mime-type" + } + } + } +} diff --git a/schemas/1.2/parameterObject.json b/schemas/1.2/parameterObject.json new file mode 100644 index 0000000000..af9ec52d6c --- /dev/null +++ b/schemas/1.2/parameterObject.json @@ -0,0 +1,37 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/parameterObject.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "allOf": [ + { "$ref": "dataTypeBase.json#" }, + { + "required": [ "paramType", "name" ], + "properties": { + "paramType": { + "enum": [ "path", "query", "body", "header", "form" ] + }, + "name": { "type": "string" }, + "description": { "type": "string" }, + "required": { "type": "boolean" }, + "allowMultiple": { "type": "boolean" } + } + }, + { + "description": "type File requires special paramType and consumes", + "oneOf": [ + { + "properties": { + "type": { "not": { "enum": [ "File" ] } } + } + }, + { + "properties": { + "type": { "enum": [ "File" ] }, + "paramType": { "enum": [ "form" ] }, + "consumes": { "enum": [ "multipart/form-data" ] } + } + } + ] + } + ] +} diff --git a/schemas/1.2/resourceListing.json b/schemas/1.2/resourceListing.json new file mode 100644 index 0000000000..1e140d749c --- /dev/null +++ b/schemas/1.2/resourceListing.json @@ -0,0 +1,16 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/resourceListing.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ "swaggerVersion", "apis" ], + "properties": { + "swaggerVersion": { "enum": [ "1.2" ] }, + "apis": { + "type": "array", + "items": { "$ref": "resourceObject.json#" } + }, + "apiVersion": { "type": "string" }, + "info": { "$ref": "infoObject.json#" }, + "authorizations": { "$ref": "authorizationObject.json#" } + } +} diff --git a/schemas/1.2/resourceObject.json b/schemas/1.2/resourceObject.json new file mode 100644 index 0000000000..f3f485598e --- /dev/null +++ b/schemas/1.2/resourceObject.json @@ -0,0 +1,11 @@ +{ + "id": "http://wordnik.github.io/schemas/v1.2/resourceObject.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ "path" ], + "properties": { + "path": { "type": "string", "format": "uri" }, + "description": { "type": "string" } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/test/test-v1_2.js b/test/test-v1_2.js new file mode 100644 index 0000000000..6f546ce246 --- /dev/null +++ b/test/test-v1_2.js @@ -0,0 +1,167 @@ +/* global describe, it */ + +/* + * Copyright 2014 Apigee Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// Module requirements +var _ = require('lodash'); +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var spec = require('../').v1_2; // jshint ignore:line + +var allSchemaFiles = [ + 'apiDeclaration.json', + 'authorizationObject.json', + 'dataType.json', + 'dataTypeBase.json', + 'infoObject.json', + 'modelsObject.json', + 'oauth2GrantType.json', + 'operationObject.json', + 'parameterObject.json', + 'resourceListing.json', + 'resourceObject.json' +]; +var allSampleFiles = {}; + +// Load the sample files from disk +fs.readdirSync(path.join(__dirname, '..', 'samples', '1.2')) + .filter(function (name) { + return name.match(/^(.*)\.json$/); + }) + .forEach(function (name) { + allSampleFiles[name] = require('../samples/1.2/' + name); + }); + +describe('swagger-tools v1.2 Specification', function () { + describe('metadata', function () { + it('should have proper docsUrl, options, schemasUrl and verison properties', function () { + assert.deepEqual(spec.options, { + useDefault: false, + useCoerce: false, + checkRequired: true, + removeAdditional: false + }); + assert.strictEqual(spec.docsUrl, 'https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md'); + assert.strictEqual(spec.schemasUrl, 'https://github.com/wordnik/swagger-spec/tree/master/schemas/v1.2'); + assert.strictEqual(spec.version, '1.2'); + }); + }); + + describe('schemas', function () { + it('should contain all schema files', function () { + assert.deepEqual(Object.keys(spec.schemas), allSchemaFiles); + }); + + it('should contain the proper content for each schema file', function () { + Object.keys(spec.schemas).forEach(function (schemaName) { + var schema = spec.schemas[schemaName]; + + assert.ok(schema.id.substring(schema.id.lastIndexOf('/') + 1), schemaName + '#'); + }); + }); + }); + + // Test validators + describe('validators', function () { + it('should contain all validators', function () { + assert.deepEqual(Object.keys(spec.validators), allSchemaFiles); + }); + }); + + describe('#validate', function () { + it('should return true for valid JSON files', function () { + Object.keys(allSampleFiles).forEach(function (name) { + var result; + + switch (name) { + case 'pet.json': + case 'store.json': + case 'user.json': + result = spec.validate(allSampleFiles[name]); + + break; + case 'resource-listing.json': + result = spec.validate(allSampleFiles[name], 'resourceListing.json'); + + break; + default: + throw new Error('Unexpected sample file: ' + name); + } + + assert.ok(_.isUndefined(result)); + }); + }); + + it('should return false for invalid JSON files', function () { + var petJson = _.cloneDeep(allSampleFiles['pet.json']); + var petErrors = [ + { + code: 'VALIDATION_FAILED', + message: 'Validation error: enum', + data: 'body', + path: '$.apis[1].operations[0].parameters[1].paramType' + } + ]; + var rlJson = _.cloneDeep(allSampleFiles['resource-listing.json']); + var rlErrors = [ + { + code: 'VALIDATION_OBJECT_REQUIRED', + message: 'Missing required property: apis', + path: '$.apis' + } + ]; + var storeJson = _.cloneDeep(allSampleFiles['store.json']); + var storeErrors = [ + { + code: 'VALIDATION_INVALID_TYPE', + message: 'Invalid type: boolean should be string', + data: false, + path: '$.models.Order.description' + } + ]; + var userJson = _.cloneDeep(allSampleFiles['user.json']); + var userErrors = [ + { + code: 'VALIDATION_ADDITIONAL_PROPERTIES', + message: 'Additional properties not allowed: extra', + data: 'value', + path: '$.apis[0].operations[0].authorizations.oauth2[0].extra' + } + ]; + + // Wrong enum value + petJson.apis[1].operations[0].parameters[1].paramType = 'body'; + + // Missing required + delete rlJson.apis; + + // Wrong type + storeJson.models.Order.description = false; + + // Extra property + userJson.apis[0].operations[0].authorizations.oauth2[0].extra = 'value'; + + assert.deepEqual(spec.validate(petJson), petErrors); + assert.deepEqual(spec.validate(rlJson, 'resourceListing.json'), rlErrors); + assert.deepEqual(spec.validate(storeJson), storeErrors); + assert.deepEqual(spec.validate(userJson), userErrors); + }); + }); +});