├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── NOTICE.txt ├── README.md ├── aws-api-import.cmd ├── aws-api-import.sh ├── docker-entrypoint.sh ├── pom.xml ├── src └── com │ └── amazonaws │ └── service │ └── apigateway │ └── importer │ ├── ApiImporterMain.java │ ├── RamlApiFileImporter.java │ ├── RamlApiImporter.java │ ├── SwaggerApiFileImporter.java │ ├── SwaggerApiImporter.java │ ├── config │ ├── ApiImporterDefaultModule.java │ └── AwsConfig.java │ ├── impl │ ├── ApiGatewayRamlFileImporter.java │ ├── ApiGatewaySwaggerFileImporter.java │ ├── SchemaTransformer.java │ └── sdk │ │ ├── ApiGatewaySdkApiImporter.java │ │ ├── ApiGatewaySdkRamlApiImporter.java │ │ └── ApiGatewaySdkSwaggerApiImporter.java │ └── util │ └── PatchUtils.java └── tst ├── com └── amazonaws │ └── service │ └── apigateway │ └── importer │ ├── ApiImporterMainTest.java │ ├── config │ ├── RamlApiImporterTestModule.java │ └── SwaggerApiImporterTestModule.java │ ├── impl │ ├── ApiGatewayRamlFileImporterTest.java │ ├── ApiGatewaySwaggerFileImporterTest.java │ ├── LambdaMatcher.java │ └── sdk │ │ ├── ApiGatewaySdkApiImporterTest.java │ │ └── ApiGatewaySdkSwaggerApiImporterTest.java │ └── integration │ └── LargeApiIntegrationTest.java └── resources ├── raml ├── apigateway.json ├── apigateway.raml ├── example.json └── example.raml └── swagger ├── apigateway.json ├── basic.json ├── large.json ├── petstore-expanded.json ├── petstore-minimal.json ├── petstore-simple.json ├── petstore-with-external-docs.json ├── petstore.json ├── test.json ├── uber.json └── uber.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | aws-apigateway-swagger-importer.iml 4 | build/ 5 | target/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3-jdk-8 2 | 3 | LABEL Description="Tools to work with Amazon API Gateway, Swagger, and RAML" 4 | 5 | COPY docker-entrypoint.sh / 6 | COPY pom.xml /app/ 7 | COPY src /app/src/ 8 | COPY tst /app/tst/ 9 | 10 | WORKDIR /app 11 | 12 | RUN mvn assembly:assembly \ 13 | && mv target/aws-apigateway-*-jar-with-dependencies.jar /aws-apigateway-importer.jar \ 14 | && mvn clean 15 | 16 | VOLUME ["/root/.aws"] 17 | VOLUME ["/data"] 18 | 19 | WORKDIR /data 20 | 21 | ENTRYPOINT ["/docker-entrypoint.sh"] 22 | CMD ["--help"] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Amazon API Gateway Swagger Importer 2 | Copyright 2013-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon API Gateway Importer 2 | 3 | The **Amazon API Gateway Importer** lets you create or update [Amazon API Gateway][service-page] APIs from a Swagger or RAML API representation. 4 | 5 | To learn more about API Gateway, please see the [service documentation][service-docs] or the [API documentation][api-docs]. 6 | 7 | [service-page]: http://aws.amazon.com/api-gateway/ 8 | [service-docs]: http://docs.aws.amazon.com/apigateway/latest/developerguide/ 9 | [api-docs]: http://docs.aws.amazon.com/apigateway/api-reference 10 | 11 | [![Build Status](https://api.travis-ci.org/awslabs/aws-apigateway-importer.svg?branch=master)](https://travis-ci.org/awslabs/aws-apigateway-importer) 12 | 13 | #### Updates 14 | 15 | April 5, 2016: Swagger/OpenAPI import is now generally available in the API Gateway [REST API][api], the AWS [CLI][cli] and all AWS [SDKs][sdks]. You can also import and export Swagger definitions using the API Gateway [console][console]. This release addresses many of the open issues and feedback in this repository. 16 | 17 | Customers are encouraged to migrate their workflow to the standard AWS tools. aws-apigateway-importer will receive minimal support from the API Gateway team going forward. Pull requests will be periodically reviewed. Customers using RAML definitions should continue to use aws-apigateway-importer for the time being. 18 | 19 | Thanks for all of your feedback and contributions to this tool. Any feedback or issues going forward should be directed to the official API Gateway [forums][forums]. - @rpgreen 20 | 21 | [sdks]: https://aws.amazon.com/tools 22 | [cli]: http://docs.aws.amazon.com/cli/latest/reference/apigateway/put-rest-api.html 23 | [forums]: https://forums.aws.amazon.com/forum.jspa?forumID=199 24 | [api]: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html 25 | [console]: https://console.aws.amazon.com/apigateway/home 26 | 27 | ## Usage 28 | 29 | ### Prerequisites 30 | 31 | #### Credentials 32 | This tool requires AWS credentials to be configured in at least one of the locations specified by the [default credential provider chain](http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/credentials.html). 33 | 34 | It will look for configured credentials in environment variables, Java system properties, [AWS SDK/CLI](http://aws.amazon.com/cli) profile credentials, and EC2 instance profile credentials. 35 | 36 | #### Build 37 | 38 | Build with `mvn assembly:assembly` 39 | 40 | ### Import a new API 41 | 42 | ```sh 43 | ./aws-api-import.sh --create path/to/swagger.json 44 | 45 | ./aws-api-import.sh -c path/to/api.raml 46 | ``` 47 | 48 | ### Update an existing API and deploy it to a stage 49 | 50 | ```sh 51 | ./aws-api-import.sh --update API_ID --deploy STAGE_NAME path/to/swagger.yaml 52 | 53 | ./aws-api-import.sh --update API_ID --deploy STAGE_NAME --raml-config path/to/config.json path/to/api.raml 54 | ``` 55 | 56 | For Windows environments replace `./aws-api-import.sh` with `./aws-api-import.cmd` in the examples. 57 | 58 | ### API Gateway Extension Example 59 | 60 | You can fully define an API Gateway API in Swagger using the `x-amazon-apigateway-auth` and `x-amazon-apigateway-integration` extensions, 61 | or in RAML using an external configuration file. 62 | 63 | Defined on an Operation: 64 | 65 | ```json 66 | 67 | "x-amazon-apigateway-auth" : { 68 | "type" : "aws_iam" 69 | }, 70 | "x-amazon-apigateway-integration" : { 71 | "type" : "aws", 72 | "uri" : "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:MY_ACCT_ID:function:helloWorld/invocations", 73 | "httpMethod" : "POST", 74 | "credentials" : "arn:aws:iam::MY_ACCT_ID:role/lambda_exec_role", 75 | "requestTemplates" : { 76 | "application/json" : "json request template 2", 77 | "application/xml" : "xml request template 2" 78 | }, 79 | "requestParameters" : { 80 | "integration.request.path.integrationPathParam" : "method.request.querystring.latitude", 81 | "integration.request.querystring.integrationQueryParam" : "method.request.querystring.longitude" 82 | }, 83 | "cacheNamespace" : "cache-namespace", 84 | "cacheKeyParameters" : [], 85 | "responses" : { 86 | "2\\d{2}" : { 87 | "statusCode" : "200", 88 | "responseParameters" : { 89 | "method.response.header.test-method-response-header" : "integration.response.header.integrationResponseHeaderParam1" 90 | }, 91 | "responseTemplates" : { 92 | "application/json" : "json 200 response template", 93 | "application/xml" : "xml 200 response template" 94 | } 95 | }, 96 | "default" : { 97 | "statusCode" : "400", 98 | "responseParameters" : { 99 | "method.response.header.test-method-response-header" : "'static value'" 100 | }, 101 | "responseTemplates" : { 102 | "application/json" : "json 400 response template", 103 | "application/xml" : "xml 400 response template" 104 | } 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | ## Testing 111 | 112 | ```sh 113 | mvn test 114 | ``` 115 | -------------------------------------------------------------------------------- /aws-api-import.cmd: -------------------------------------------------------------------------------- 1 | java -jar %~dp0target/aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.jar %* 2 | -------------------------------------------------------------------------------- /aws-api-import.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | root=$(dirname $(perl -MCwd=abs_path -e 'print abs_path(shift)' $0)) 3 | 4 | java -jar $root/target/aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.jar "$@" -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | java -jar /aws-apigateway-importer.jar "$@" 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.amazonaws 6 | aws-apigateway-importer 7 | 1.0.3-SNAPSHOT 8 | 9 | 10 | org.sonatype.oss 11 | oss-parent 12 | 7 13 | 14 | 15 | 16 | 17 | 18 | com.amazonaws 19 | aws-apigateway-sdk-java 20 | 1.1.1 21 | 22 | 23 | 24 | com.amazonaws 25 | aws-hal-client-java 26 | 1.3.0 27 | 28 | 29 | 30 | com.amazonaws 31 | aws-java-sdk-core 32 | [1.8.6,1.10.77) 33 | 34 | 35 | 36 | org.glassfish 37 | javax.json 38 | 1.0-b06 39 | 40 | 41 | 42 | de.weltraumschaf.commons 43 | jcommander 44 | 2.0.0 45 | 46 | 47 | 48 | com.google.inject 49 | guice 50 | 4.0 51 | 52 | 53 | 54 | log4j 55 | log4j 56 | 1.2.14 57 | 58 | 59 | 60 | io.swagger 61 | swagger-parser 62 | 1.0.17 63 | 64 | 65 | 66 | io.swagger 67 | swagger-compat-spec-parser 68 | 1.0.17 69 | 70 | 71 | 72 | io.swagger 73 | swagger-core 74 | 1.5.8 75 | 76 | 77 | 78 | org.raml 79 | raml-parser 80 | 0.8.11 81 | 82 | 83 | 84 | org.apache.commons 85 | commons-io 86 | 1.3.2 87 | 88 | 89 | 90 | org.apache.commons 91 | commons-lang3 92 | 3.4 93 | 94 | 95 | 96 | com.google.guava 97 | guava 98 | 18.0 99 | 100 | 101 | 102 | com.amazonaws 103 | aws-java-sdk 104 | 1.9.6 105 | 106 | 107 | 108 | org.apache.httpcomponents 109 | httpclient 110 | 4.3.3 111 | 112 | 113 | 114 | 115 | junit 116 | junit 117 | 4.10 118 | test 119 | 120 | 121 | 122 | org.mockito 123 | mockito-all 124 | 2.0.2-beta 125 | test 126 | 127 | 128 | 129 | com.fasterxml.jackson.core 130 | jackson-annotations 131 | 2.5.0 132 | 133 | 134 | 135 | com.fasterxml.jackson.core 136 | jackson-core 137 | 2.5.0 138 | 139 | 140 | 141 | 142 | 143 | 147 | 148 | release-sign-artifacts 149 | 150 | 151 | performRelease 152 | true 153 | 154 | 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-gpg-plugin 160 | 161 | 162 | sign-artifacts 163 | verify 164 | 165 | sign 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | package 177 | 178 | ${basedir}/src 179 | ${basedir}/tst 180 | 181 | 182 | 183 | ${project.basedir} 184 | 185 | LICENSE 186 | NOTICE.txt 187 | README.md 188 | 189 | 190 | 191 | 192 | 193 | 194 | ${basedir}/tst/resources 195 | 196 | 197 | 198 | 199 | 200 | org.apache.maven.plugins 201 | maven-compiler-plugin 202 | 2.5.1 203 | 204 | 1.8 205 | 1.8 206 | 207 | 208 | 209 | 210 | org.apache.maven.plugins 211 | maven-source-plugin 212 | 2.2.1 213 | 214 | 215 | attach-sources 216 | 217 | jar 218 | 219 | 220 | true 221 | 222 | 223 | 224 | 225 | 226 | 227 | org.apache.maven.plugins 228 | maven-javadoc-plugin 229 | 2.9.1 230 | 231 | 232 | attach-javadocs 233 | 234 | jar 235 | 236 | 237 | 238 | 239 | 240 | 241 | org.apache.maven.plugins 242 | maven-release-plugin 243 | 2.4.2 244 | 245 | clean verify install 246 | false 247 | true 248 | 249 | 250 | 251 | 252 | maven-assembly-plugin 253 | 254 | 255 | jar-with-dependencies 256 | 257 | 258 | 259 | com.amazonaws.service.apigateway.importer.ApiImporterMain 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | Apache License, Version 2.0 270 | https://aws.amazon.com/apache2.0 271 | repo 272 | 273 | 274 | 275 | 276 | scm:git:git@github.com:awslabs/aws-apigateway-importer.git 277 | scm:git:git@github.com:awslabs/aws-apigateway-importer.git 278 | scm:git:git@github.com:awslabs/aws-apigateway-importer.git 279 | HEAD 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/ApiImporterMain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer; 16 | 17 | import com.amazonaws.auth.AWSCredentialsProvider; 18 | import com.amazonaws.auth.AWSCredentialsProviderChain; 19 | import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; 20 | import com.amazonaws.auth.InstanceProfileCredentialsProvider; 21 | import com.amazonaws.auth.SystemPropertiesCredentialsProvider; 22 | import com.amazonaws.auth.profile.ProfileCredentialsProvider; 23 | import com.amazonaws.service.apigateway.importer.config.ApiImporterDefaultModule; 24 | import com.amazonaws.service.apigateway.importer.config.AwsConfig; 25 | import com.amazonaws.service.apigateway.importer.impl.ApiGatewayRamlFileImporter; 26 | import com.amazonaws.service.apigateway.importer.impl.ApiGatewaySwaggerFileImporter; 27 | import com.amazonaws.util.json.JSONException; 28 | import com.amazonaws.util.json.JSONObject; 29 | import com.amazonaws.util.json.JSONTokener; 30 | import com.beust.jcommander.JCommander; 31 | import com.beust.jcommander.Parameter; 32 | import com.google.inject.Guice; 33 | import com.google.inject.Injector; 34 | import org.apache.commons.io.FilenameUtils; 35 | import org.apache.commons.lang3.StringUtils; 36 | import org.apache.commons.logging.Log; 37 | import org.apache.commons.logging.LogFactory; 38 | import org.apache.log4j.ConsoleAppender; 39 | import org.apache.log4j.Level; 40 | import org.apache.log4j.Logger; 41 | import org.apache.log4j.PatternLayout; 42 | 43 | import java.io.File; 44 | import java.io.FileReader; 45 | import java.util.List; 46 | 47 | public class ApiImporterMain { 48 | private static final Log LOG = LogFactory.getLog(ApiImporterMain.class); 49 | private static final String CMD_NAME = "aws-api-import"; 50 | 51 | @Parameter(names = {"--update", "-u"}, description = "API ID to import swagger into an existing API") 52 | private String apiId; 53 | 54 | @Parameter(names = {"--create", "-c"}, description = "Create a new API") 55 | private boolean createNew; 56 | 57 | @Parameter(description = "Path to API definition file to import") 58 | private List files; 59 | 60 | @Parameter(names = {"--deploy", "-d"}, description = "Stage used to deploy the API (optional)") 61 | private String deploymentLabel; 62 | 63 | @Parameter(names = {"--test", "-t"}, description = "Delete the API after import (create only)") 64 | private boolean cleanup = false; 65 | 66 | @Parameter(names = {"--region", "-r"}, description = "AWS region to use (optional)") 67 | private String region; 68 | 69 | @Parameter(names = {"--profile", "-p"}, description = "AWS CLI profile to use (optional)") 70 | private String profile = "default"; 71 | 72 | @Parameter(names = {"--raml-config"}, description = "RAML file for API Gateway metadata (optional)") 73 | private String configFile; 74 | 75 | @Parameter(names = "--help", help = true) 76 | private boolean help; 77 | 78 | public static void main(String[] args) { 79 | bootstrap(); 80 | ApiImporterMain main = new ApiImporterMain(); 81 | JCommander jCommander = new JCommander(main, args); 82 | jCommander.setProgramName(CMD_NAME); 83 | main.execute(jCommander); 84 | } 85 | 86 | static void bootstrap() { 87 | Logger root = Logger.getRootLogger(); 88 | root.setLevel(Level.INFO); 89 | root.addAppender(new ConsoleAppender(new PatternLayout("%d %p - %m%n"))); 90 | } 91 | 92 | public void execute(JCommander jCommander) { 93 | if (help) { 94 | jCommander.usage(); 95 | return; 96 | } 97 | 98 | if (!validateArgs()) { 99 | jCommander.usage(); 100 | System.exit(1); 101 | } 102 | 103 | // use default AWS credentials provider chain 104 | AWSCredentialsProvider credentialsProvider = new AWSCredentialsProviderChain( 105 | new EnvironmentVariableCredentialsProvider(), 106 | new SystemPropertiesCredentialsProvider(), 107 | new ProfileCredentialsProvider(profile), 108 | new InstanceProfileCredentialsProvider()); 109 | 110 | // if region parameter is not specified, attempt to load configured region from profile 111 | if (StringUtils.isBlank(region)) { 112 | AwsConfig config = new AwsConfig(profile); 113 | try { 114 | config.load(); 115 | } catch (Throwable t) { 116 | LOG.error("Could not load region from profile and no region parameter specified. " + 117 | "Please run 'aws configure' or specify region with '--region' parameter."); 118 | System.exit(1); 119 | } 120 | region = config.getRegion(); 121 | } 122 | 123 | try { 124 | Injector injector = Guice.createInjector(new ApiImporterDefaultModule(credentialsProvider, region)); 125 | 126 | String fileName = files.get(0); 127 | 128 | if (FilenameUtils.getExtension(fileName).equals("raml")) { 129 | final JSONObject configData; 130 | 131 | RamlApiFileImporter importer = injector.getInstance(ApiGatewayRamlFileImporter.class); 132 | 133 | try { 134 | configData = configFile == null ? null : new JSONObject(new JSONTokener(new FileReader(configFile))); 135 | } catch (JSONException e) { 136 | LOG.info("Unable to parse configuration file: " + e); 137 | System.exit(1); 138 | return; 139 | } 140 | 141 | importRaml(fileName, configData, importer); 142 | } else { 143 | SwaggerApiFileImporter importer = injector.getInstance(ApiGatewaySwaggerFileImporter.class); 144 | 145 | importSwagger(fileName, importer); 146 | } 147 | } catch (Throwable t) { 148 | LOG.error("Error importing API definition", t); 149 | System.exit(1); 150 | } 151 | } 152 | 153 | private void importSwagger(String fileName, SwaggerApiFileImporter importer) { 154 | if (createNew) { 155 | apiId = importer.importApi(fileName); 156 | 157 | if (cleanup) { 158 | importer.deleteApi(apiId); 159 | } 160 | } else { 161 | importer.updateApi(apiId, fileName); 162 | } 163 | 164 | if (!StringUtils.isBlank(deploymentLabel)) { 165 | importer.deploy(apiId, deploymentLabel); 166 | } 167 | } 168 | 169 | private void importRaml(String fileName, JSONObject configData, RamlApiFileImporter importer) { 170 | if (createNew) { 171 | apiId = importer.importApi(fileName, configData); 172 | 173 | if (cleanup) { 174 | importer.deleteApi(apiId); 175 | } 176 | } else { 177 | importer.updateApi(apiId, fileName, configData); 178 | } 179 | 180 | if (!StringUtils.isBlank(deploymentLabel)) { 181 | importer.deploy(apiId, deploymentLabel); 182 | } 183 | } 184 | 185 | private boolean validateArgs() { 186 | if ((apiId == null && !createNew) || files == null || files.isEmpty()) { 187 | return false; 188 | } 189 | 190 | if (cleanup && apiId != null) { 191 | LOG.error("Test mode is not supported when updating an API"); 192 | return false; 193 | } 194 | 195 | final String fileName = files.get(0); 196 | 197 | if (!new File(fileName).exists()) { 198 | LOG.error(String.format("Could not load file '%s'", fileName)); 199 | return false; 200 | } 201 | 202 | return true; 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/RamlApiFileImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer; 16 | 17 | import com.amazonaws.util.json.JSONObject; 18 | 19 | public interface RamlApiFileImporter { 20 | String importApi(String filePath, JSONObject config); 21 | void updateApi(String apiId, String filePath, JSONObject config); 22 | void deploy(String apiId, String deploymentStage); 23 | void deleteApi(String apiId); 24 | } 25 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/RamlApiImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer; 16 | 17 | import com.amazonaws.util.json.JSONObject; 18 | import org.raml.model.Raml; 19 | 20 | public interface RamlApiImporter { 21 | String createApi(Raml raml, String name, JSONObject config); 22 | void updateApi(String apiId, Raml raml, JSONObject config); 23 | void deploy(String apiId, String deploymentStage); 24 | void deleteApi(String apiId); 25 | } 26 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/SwaggerApiFileImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer; 16 | 17 | public interface SwaggerApiFileImporter { 18 | String importApi(String filePath); 19 | void updateApi(String apiId, String filePath); 20 | void deploy(String apiId, String deploymentStage); 21 | void deleteApi(String apiId); 22 | } 23 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/SwaggerApiImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer; 16 | 17 | import io.swagger.models.Swagger; 18 | 19 | public interface SwaggerApiImporter { 20 | String createApi(Swagger swagger, String name); 21 | void updateApi(String apiId, Swagger swagger); 22 | void deploy(String apiId, String deploymentStage); 23 | void deleteApi(String apiId); 24 | } 25 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/config/ApiImporterDefaultModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.config; 16 | 17 | import com.amazonaws.AmazonServiceException; 18 | import com.amazonaws.ClientConfiguration; 19 | import com.amazonaws.auth.AWSCredentialsProvider; 20 | import com.amazonaws.retry.PredefinedRetryPolicies; 21 | import com.amazonaws.retry.RetryPolicy; 22 | import com.amazonaws.retry.RetryUtils; 23 | import com.amazonaws.service.apigateway.importer.ApiImporterMain; 24 | import com.amazonaws.service.apigateway.importer.RamlApiImporter; 25 | import com.amazonaws.service.apigateway.importer.SwaggerApiImporter; 26 | import com.amazonaws.service.apigateway.importer.impl.sdk.ApiGatewaySdkRamlApiImporter; 27 | import com.amazonaws.service.apigateway.importer.impl.sdk.ApiGatewaySdkSwaggerApiImporter; 28 | import com.amazonaws.services.apigateway.AmazonApiGateway; 29 | import com.amazonaws.services.apigateway.model.ApiGateway; 30 | import com.google.inject.AbstractModule; 31 | import com.google.inject.Provides; 32 | import com.google.inject.name.Named; 33 | import com.google.inject.name.Names; 34 | import org.apache.commons.logging.Log; 35 | import org.apache.commons.logging.LogFactory; 36 | 37 | import java.util.Random; 38 | 39 | public class ApiImporterDefaultModule extends AbstractModule { 40 | private static final Log LOG = LogFactory.getLog(ApiImporterMain.class); 41 | private static final String USER_AGENT = "AmazonApiGatewaySwaggerImporter/1.0"; 42 | 43 | private final AWSCredentialsProvider awsCredentialsProvider; 44 | 45 | private String region; 46 | 47 | public ApiImporterDefaultModule(AWSCredentialsProvider awsCredentialsProvider, String region) { 48 | this.awsCredentialsProvider = awsCredentialsProvider; 49 | this.region = region; 50 | 51 | LOG.info("Using API Gateway endpoint " + getEndpoint(region)); 52 | } 53 | 54 | @Override 55 | protected void configure() { 56 | bind(SwaggerApiImporter.class).to(ApiGatewaySdkSwaggerApiImporter.class); 57 | bind(RamlApiImporter.class).to(ApiGatewaySdkRamlApiImporter.class); 58 | bind(String.class).annotatedWith(Names.named("region")).toInstance(region); 59 | } 60 | 61 | @Provides 62 | protected AWSCredentialsProvider provideCredentialsProvider() { 63 | return awsCredentialsProvider; 64 | } 65 | 66 | @Provides 67 | protected ApiGateway provideAmazonApiGateway(AWSCredentialsProvider credsProvider, 68 | RetryPolicy.BackoffStrategy backoffStrategy, 69 | @Named("region") String region) { 70 | 71 | final RetryPolicy retrypolicy = new RetryPolicy(PredefinedRetryPolicies.DEFAULT_RETRY_CONDITION, backoffStrategy, 5, true); 72 | 73 | final ClientConfiguration clientConfig = new ClientConfiguration().withUserAgent(USER_AGENT).withRetryPolicy(retrypolicy); 74 | 75 | return new AmazonApiGateway(getEndpoint(region)).with(credsProvider).with(clientConfig).getApiGateway(); 76 | } 77 | 78 | protected String getEndpoint(String region) { 79 | return String.format("https://apigateway.%s.amazonaws.com", region); 80 | } 81 | 82 | /* 83 | * Override the default SDK exponential backoff implementation 84 | * See {@link PredefinedRetryPolicies#DEFAULT_BACKOFF_STRATEGY 85 | */ 86 | @Provides 87 | protected RetryPolicy.BackoffStrategy provideBackoffStrategy() { 88 | 89 | // tune these parameters to handle throttling errors 90 | final int maxBackoffInMilliseconds = 50 * 1000; // maximum exponential back-off time before retrying a request 91 | final int throttlingScaleFactor = 800; // base sleep time for throttling exceptions 92 | final int maxRetriesBeforeBackoff = 10; // log2(maxBackoffInMilliseconds/throttlingScaleFactor) 93 | 94 | final int baseScaleFactor = 600; // base sleep time for general exceptions 95 | final int throttlingScaleFactorRandomRange = throttlingScaleFactor / 4; 96 | 97 | final Random random = new Random(); 98 | 99 | return (originalRequest, exception, retriesAttempted) -> { 100 | 101 | LOG.debug("Caught error from service. Retry attempt: " + retriesAttempted, exception); 102 | 103 | if (retriesAttempted < 0) return 0; 104 | if (retriesAttempted > maxRetriesBeforeBackoff) return maxBackoffInMilliseconds; 105 | 106 | int scaleFactor; 107 | if (exception instanceof AmazonServiceException 108 | && RetryUtils.isThrottlingException((AmazonServiceException) exception)) { 109 | scaleFactor = throttlingScaleFactor + random.nextInt(throttlingScaleFactorRandomRange); 110 | } else { 111 | scaleFactor = baseScaleFactor; 112 | } 113 | 114 | long delay = (1L << retriesAttempted) * scaleFactor; 115 | delay = Math.min(delay, maxBackoffInMilliseconds); 116 | 117 | LOG.info("Client backing off for " + delay + "ms"); 118 | 119 | return delay; 120 | }; 121 | } 122 | } -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/config/AwsConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.config; 16 | 17 | import org.apache.commons.logging.Log; 18 | import org.apache.commons.logging.LogFactory; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.File; 22 | import java.io.FileReader; 23 | import java.util.Optional; 24 | import java.util.regex.*; 25 | 26 | public class AwsConfig { 27 | private static final Log LOG = LogFactory.getLog(AwsConfig.class); 28 | public static final String DEFAULT_REGION = "us-east-1"; 29 | 30 | private String region; 31 | private String profile; 32 | 33 | public AwsConfig(String profile) { 34 | this.profile = profile; 35 | } 36 | 37 | public String getRegion() { 38 | return region; 39 | } 40 | 41 | public String getProfile() { 42 | return profile; 43 | } 44 | 45 | public void load() { 46 | Optional region = loadRegion(); 47 | 48 | if (region.isPresent()) { 49 | this.region = region.get(); 50 | } else { 51 | this.region = DEFAULT_REGION; 52 | LOG.warn("Could not load region configuration. Please ensure AWS CLI is " + 53 | "configured via 'aws configure'. Will use default region of " + this.region); 54 | } 55 | } 56 | 57 | private Optional loadRegion() { 58 | String file = System.getProperty("user.home") + "/.aws/config"; 59 | 60 | boolean foundProfile = false; 61 | try (BufferedReader br = new BufferedReader(new FileReader(new File(file)))) { 62 | String line; 63 | String region; 64 | Pattern regionPat = Pattern.compile("[a-z]{2}+-[a-z]{2,}+-[0-9]"); 65 | Matcher regionMat; 66 | Integer eqPos; 67 | 68 | while ((line = br.readLine()) != null) { 69 | 70 | if (line.startsWith("[") && line.contains(this.profile)) { 71 | foundProfile = true; 72 | } 73 | 74 | if (foundProfile && line.startsWith("region")) { 75 | eqPos = line.indexOf("="); 76 | region = line.substring(eqPos + 1, line.length()).trim(); 77 | regionMat = regionPat.matcher(region); 78 | if (! regionMat.matches()) { 79 | LOG.error("Region does not match '[a-z]{2}+-[a-z]{2,}+-[0-9]': " + region); 80 | throw new RuntimeException("Region does not match '[a-z]{2}+-[a-z]{2,}+-[0-9]'" + region); 81 | } 82 | return Optional.of(region); 83 | } 84 | } 85 | } catch (Throwable t) { 86 | throw new RuntimeException("Could not load configuration. Please run 'aws configure'"); 87 | } 88 | 89 | return Optional.empty(); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/impl/ApiGatewayRamlFileImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl; 16 | 17 | import com.amazonaws.service.apigateway.importer.RamlApiFileImporter; 18 | import com.amazonaws.service.apigateway.importer.RamlApiImporter; 19 | import com.amazonaws.util.json.JSONObject; 20 | import com.google.inject.Inject; 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.raml.model.Raml; 24 | import org.raml.parser.visitor.RamlDocumentBuilder; 25 | 26 | import java.io.File; 27 | 28 | import static java.lang.String.format; 29 | 30 | public class ApiGatewayRamlFileImporter implements RamlApiFileImporter { 31 | private static final Log LOG = LogFactory.getLog(ApiGatewayRamlFileImporter.class); 32 | 33 | private final RamlDocumentBuilder builder; 34 | private final RamlApiImporter client; 35 | 36 | @Inject 37 | public ApiGatewayRamlFileImporter(RamlDocumentBuilder builder, RamlApiImporter client) { 38 | this.builder = builder; 39 | this.client = client; 40 | } 41 | 42 | @Override 43 | public String importApi(String filePath, JSONObject config) { 44 | LOG.info(format("Attempting to create API from RAML definition. " + 45 | "RAML file: %s", filePath)); 46 | 47 | final Raml raml = parse(filePath); 48 | 49 | return client.createApi(raml, new File(filePath).getName(), config); 50 | } 51 | 52 | @Override 53 | public void updateApi(String apiId, String filePath, JSONObject config) { 54 | LOG.info(format("Attempting to update API from RAML definition. " + 55 | "API identifier: %s RAML file: %s", apiId, filePath)); 56 | 57 | final Raml raml = parse(filePath); 58 | 59 | client.updateApi(apiId, raml, config); 60 | } 61 | 62 | @Override 63 | public void deploy(String apiId, String deploymentStage) { 64 | client.deploy(apiId, deploymentStage); 65 | } 66 | 67 | @Override 68 | public void deleteApi(String apiId) { 69 | client.deleteApi(apiId); 70 | } 71 | 72 | private Raml parse(String filePath) { 73 | final Raml raml = builder.build(filePath); 74 | 75 | // TODO: Error handling. 76 | 77 | return raml; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/impl/ApiGatewaySwaggerFileImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl; 16 | 17 | import com.amazonaws.service.apigateway.importer.SwaggerApiFileImporter; 18 | import com.amazonaws.service.apigateway.importer.SwaggerApiImporter; 19 | import com.google.inject.Inject; 20 | import io.swagger.models.Swagger; 21 | import io.swagger.parser.SwaggerParser; 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | 25 | import java.io.File; 26 | 27 | import static java.lang.String.format; 28 | 29 | public class ApiGatewaySwaggerFileImporter implements SwaggerApiFileImporter { 30 | private static final Log LOG = LogFactory.getLog(ApiGatewaySwaggerFileImporter.class); 31 | 32 | private final SwaggerParser parser; 33 | private final SwaggerApiImporter client; 34 | 35 | @Inject 36 | public ApiGatewaySwaggerFileImporter(SwaggerParser parser, SwaggerApiImporter client) { 37 | this.parser = parser; 38 | this.client = client; 39 | } 40 | 41 | @Override 42 | public String importApi(String filePath) { 43 | LOG.info(format("Attempting to create API from Swagger definition. " + 44 | "Swagger file: %s", filePath)); 45 | 46 | final Swagger swagger = parse(filePath); 47 | 48 | return client.createApi(swagger, new File(filePath).getName()); 49 | } 50 | 51 | @Override 52 | public void updateApi(String apiId, String filePath) { 53 | LOG.info(format("Attempting to update API from Swagger definition. " + 54 | "API identifier: %s Swagger file: %s", apiId, filePath)); 55 | 56 | final Swagger swagger = parse(filePath); 57 | 58 | client.updateApi(apiId, swagger); 59 | } 60 | 61 | @Override 62 | public void deploy(String apiId, String deploymentStage) { 63 | client.deploy(apiId, deploymentStage); 64 | } 65 | 66 | @Override 67 | public void deleteApi(String apiId) { 68 | client.deleteApi(apiId); 69 | } 70 | 71 | private Swagger parse(String filePath) { 72 | final Swagger swagger = parser.read(filePath); 73 | 74 | if (swagger != null && swagger.getPaths() != null) { 75 | LOG.info("Parsed Swagger with " + swagger.getPaths().size() + " paths"); 76 | } 77 | 78 | return swagger; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/impl/SchemaTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl; 16 | 17 | import com.fasterxml.jackson.core.JsonProcessingException; 18 | import com.fasterxml.jackson.databind.JsonNode; 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 21 | import com.fasterxml.jackson.databind.node.ObjectNode; 22 | import com.fasterxml.jackson.databind.node.TextNode; 23 | import com.github.fge.jsonschema.core.exceptions.ProcessingException; 24 | import com.github.fge.jsonschema.core.report.ProcessingReport; 25 | import com.github.fge.jsonschema.main.JsonSchemaFactory; 26 | import org.apache.log4j.Logger; 27 | 28 | import java.io.IOException; 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | 32 | /** 33 | * Deserializes and transforms schema schemas into normalized form 34 | * 35 | * @author rpgreen 36 | */ 37 | public class SchemaTransformer { 38 | protected final static Logger LOG = Logger.getLogger(SchemaTransformer.class); 39 | 40 | /** 41 | * Get a schema schema in "flattened" form whereby all dependent references are resolved 42 | * and included as inline schema definitions 43 | * 44 | * @return the json-schema string in flattened form 45 | */ 46 | public String flatten(String model, String models) { 47 | return getFlattened(deserialize(model), deserialize(models)); 48 | } 49 | 50 | private void buildSchemaReferenceMap(JsonNode model, JsonNode models, Map modelMap) { 51 | Map refs = new HashMap<>(); 52 | findReferences(model, refs); 53 | 54 | for (JsonNode ref : refs.keySet()) { 55 | String canonicalRef = ref.textValue(); 56 | 57 | String schemaName = getSchemaName(canonicalRef); 58 | 59 | JsonNode subSchema = getSchema(schemaName, models); 60 | 61 | // replace reference values with inline definitions 62 | replaceRef((ObjectNode) refs.get(ref), schemaName); 63 | 64 | buildSchemaReferenceMap(subSchema, models, modelMap); 65 | 66 | modelMap.put(schemaName, serializeExisting(subSchema)); 67 | } 68 | } 69 | 70 | private JsonNode getSchema(String schemaName, JsonNode models) { 71 | return models.findPath(schemaName); 72 | } 73 | 74 | private String getFlattened(JsonNode model, JsonNode models) { 75 | HashMap schemaMap = new HashMap<>(); 76 | 77 | buildSchemaReferenceMap(model, models, schemaMap); 78 | 79 | replaceRefs(model, schemaMap); 80 | 81 | if (LOG.isTraceEnabled()) { 82 | try { 83 | LOG.trace("Flattened schema to: " + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(model)); 84 | } catch (JsonProcessingException ignored){} 85 | } 86 | 87 | String flattened = serializeExisting(model); 88 | 89 | validate(model); 90 | 91 | return flattened; 92 | } 93 | 94 | private void validate(JsonNode rootNode) { 95 | final JsonSchemaFactory factory; 96 | try { 97 | factory = JsonSchemaFactory.byDefault(); 98 | factory.getJsonSchema(rootNode); 99 | } catch (ProcessingException e) { 100 | throw new IllegalStateException("Invalid schema json was generated", e); 101 | } catch (ExceptionInInitializerError | NoClassDefFoundError e) { 102 | return; // this should only happen from test code. JsonSchemaFactory not easily mocked 103 | } 104 | 105 | ProcessingReport report = factory.getSyntaxValidator().validateSchema(rootNode); 106 | if (!report.isSuccess()) { 107 | throw new IllegalStateException("Invalid schema json was generated" + report.iterator().next().getMessage()); 108 | } 109 | } 110 | 111 | /* 112 | * Add schema references as inline definitions to the root schema 113 | */ 114 | private void replaceRefs(JsonNode root, HashMap schemaMap) { 115 | 116 | ObjectNode definitionsNode = new ObjectNode(JsonNodeFactory.instance); 117 | 118 | for (Map.Entry entry : schemaMap.entrySet()) { 119 | JsonNode schemaNode = deserialize(entry.getValue()); 120 | definitionsNode.set(entry.getKey(), schemaNode); 121 | } 122 | 123 | ((ObjectNode)root).set("definitions", definitionsNode); 124 | } 125 | 126 | /* 127 | * Replace a reference node with an inline reference 128 | */ 129 | private void replaceRef(ObjectNode parent, String schemaName) { 130 | parent.set("$ref", new TextNode("#/definitions/" + schemaName)); 131 | } 132 | 133 | /* 134 | * Find all reference node in the schema tree. Build a map of the reference node to its parent 135 | */ 136 | private void findReferences(JsonNode node, Map refNodes) { 137 | JsonNode refNode = node.path("$ref"); 138 | if (!refNode.isMissingNode()) { 139 | refNodes.put(refNode, node); 140 | } 141 | 142 | for (JsonNode child : node) { 143 | findReferences(child, refNodes); 144 | } 145 | } 146 | 147 | /* 148 | * Attempt to serialize an existing schema 149 | * If this fails something is seriously wrong, because this schema has already been saved by the control plane 150 | */ 151 | JsonNode deserialize(String schemaText) { 152 | try { 153 | ObjectMapper mapper = new ObjectMapper(); 154 | return mapper.readTree(schemaText); 155 | } catch (IOException e) { 156 | throw new IllegalStateException("Invalid schema found. Could not deserialize schema: " + schemaText, e); 157 | } 158 | } 159 | 160 | /* 161 | * Attempt to serialize an existing schema 162 | * If this fails something is seriously wrong, because this schema has already been saved by the control plane 163 | */ 164 | private String serializeExisting(JsonNode root) { 165 | try { 166 | return new ObjectMapper().writeValueAsString(root); 167 | } catch (JsonProcessingException e) { 168 | throw new IllegalStateException("Could not serialize generated schema json", e); 169 | } 170 | } 171 | 172 | public static String getSchemaName(String refVal) { 173 | String schemaName; 174 | try { 175 | schemaName = refVal.substring(refVal.lastIndexOf("/") + 1, 176 | refVal.length()); 177 | } catch (Throwable t) { 178 | throw new IllegalStateException("Invalid reference found: " + refVal, t); 179 | } 180 | 181 | return schemaName; 182 | } 183 | 184 | public static String getRestApiId(String refVal) { 185 | String apiId; 186 | try { 187 | apiId = refVal.substring(refVal.indexOf("restapis/"), 188 | refVal.length()).split("/")[1]; 189 | } catch (Throwable t) { 190 | throw new IllegalStateException("Invalid reference found: " + refVal, t); 191 | } 192 | 193 | return apiId; 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/impl/sdk/ApiGatewaySdkApiImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl.sdk; 16 | 17 | import com.amazonaws.services.apigateway.model.ApiGateway; 18 | import com.amazonaws.services.apigateway.model.CreateDeploymentInput; 19 | import com.amazonaws.services.apigateway.model.CreateModelInput; 20 | import com.amazonaws.services.apigateway.model.CreateResourceInput; 21 | import com.amazonaws.services.apigateway.model.CreateRestApiInput; 22 | import com.amazonaws.services.apigateway.model.Model; 23 | import com.amazonaws.services.apigateway.model.Models; 24 | import com.amazonaws.services.apigateway.model.NotFoundException; 25 | import com.amazonaws.services.apigateway.model.Resource; 26 | import com.amazonaws.services.apigateway.model.Resources; 27 | import com.amazonaws.services.apigateway.model.RestApi; 28 | import com.google.common.util.concurrent.RateLimiter; 29 | import com.google.inject.Inject; 30 | import org.apache.commons.lang3.StringUtils; 31 | import org.apache.commons.logging.Log; 32 | import org.apache.commons.logging.LogFactory; 33 | 34 | import java.util.ArrayList; 35 | import java.util.HashSet; 36 | import java.util.List; 37 | import java.util.Optional; 38 | import java.util.Set; 39 | import java.util.stream.Stream; 40 | 41 | import static com.amazonaws.service.apigateway.importer.util.PatchUtils.createPatchDocument; 42 | import static com.amazonaws.service.apigateway.importer.util.PatchUtils.createReplaceOperation; 43 | 44 | public class ApiGatewaySdkApiImporter { 45 | 46 | private static final Log LOG = LogFactory.getLog(ApiGatewaySdkApiImporter.class); 47 | 48 | @Inject 49 | protected ApiGateway apiGateway; 50 | 51 | // keep track of the models created/updated from the definition file. Any orphaned models left in the API will be deleted 52 | protected HashSet processedModels = new HashSet<>(); 53 | 54 | public void deleteApi(String apiId) { 55 | deleteApi(apiGateway.getRestApiById(apiId)); 56 | } 57 | 58 | public void deploy(String apiId, String deploymentStage) { 59 | LOG.info(String.format("Creating deployment for API %s and stage %s", apiId, deploymentStage)); 60 | 61 | CreateDeploymentInput input = new CreateDeploymentInput(); 62 | input.setStageName(deploymentStage); 63 | 64 | apiGateway.getRestApiById(apiId).createDeployment(input); 65 | } 66 | 67 | protected RestApi createApi(String name, String description) { 68 | LOG.info("Creating API with name " + name); 69 | 70 | CreateRestApiInput input = new CreateRestApiInput(); 71 | input.setName(name); 72 | input.setDescription(description); 73 | 74 | return apiGateway.createRestApi(input); 75 | } 76 | 77 | protected void rollback(RestApi api) { 78 | deleteApi(api); 79 | } 80 | 81 | protected void deleteApi(RestApi api) { 82 | LOG.info("Deleting API " + api.getId()); 83 | api.deleteRestApi(); 84 | } 85 | 86 | protected Optional getRootResource(RestApi api) { 87 | for (Resource r : buildResourceList(api)) { 88 | if ("/".equals(r.getPath())) { 89 | return Optional.of(r); 90 | } 91 | } 92 | return Optional.empty(); 93 | } 94 | 95 | // todo: optimize number of calls to this as it is an expensive operation 96 | protected List buildResourceList(RestApi api) { 97 | List resourceList = new ArrayList<>(); 98 | 99 | Resources resources = api.getResources(); 100 | resourceList.addAll(resources.getItem()); 101 | 102 | LOG.debug("Building list of resources. Stack trace: ", new Throwable()); 103 | 104 | final RateLimiter rl = RateLimiter.create(2); 105 | while (resources._isLinkAvailable("next")) { 106 | rl.acquire(); 107 | resources = resources.getNext(); 108 | resourceList.addAll(resources.getItem()); 109 | } 110 | 111 | return resourceList; 112 | } 113 | 114 | protected void deleteDefaultModels(RestApi api) { 115 | buildModelList(api).stream().forEach(model -> { 116 | LOG.info("Removing default model " + model.getName()); 117 | try { 118 | model.deleteModel(); 119 | } catch (Throwable ignored) { 120 | } // todo: temporary catch until API fix 121 | }); 122 | } 123 | 124 | protected List buildModelList(RestApi api) { 125 | List modelList = new ArrayList<>(); 126 | 127 | Models models = api.getModels(); 128 | modelList.addAll(models.getItem()); 129 | 130 | while (models._isLinkAvailable("next")) { 131 | models = models.getNext(); 132 | modelList.addAll(models.getItem()); 133 | } 134 | 135 | return modelList; 136 | } 137 | 138 | protected RestApi getApi(String id) { 139 | return apiGateway.getRestApiById(id); 140 | } 141 | 142 | protected void createModel(RestApi api, String modelName, String description, String schema, String modelContentType) { 143 | this.processedModels.add(modelName); 144 | 145 | CreateModelInput input = new CreateModelInput(); 146 | 147 | input.setName(modelName); 148 | input.setDescription(description); 149 | input.setContentType(modelContentType); 150 | input.setSchema(schema); 151 | 152 | api.createModel(input); 153 | } 154 | 155 | protected void updateModel(RestApi api, String modelName, String schema) { 156 | this.processedModels.add(modelName); 157 | 158 | api.getModelByName(modelName).updateModel(createPatchDocument(createReplaceOperation("/schema", schema))); 159 | } 160 | 161 | protected void cleanupModels(RestApi api, Set models) { 162 | List existingModels = buildModelList(api); 163 | Stream modelsToDelete = existingModels.stream().filter(model -> !models.contains(model.getName())); 164 | 165 | modelsToDelete.forEach(model -> { 166 | LOG.info("Removing deleted model " + model.getName()); 167 | model.deleteModel(); 168 | }); 169 | } 170 | 171 | protected Optional getResource(String parentResourceId, String pathPart, List resources) { 172 | for (Resource r : resources) { 173 | if (pathEquals(pathPart, r.getPathPart()) && r.getParentId().equals(parentResourceId)) { 174 | return Optional.of(r); 175 | } 176 | } 177 | return Optional.empty(); 178 | } 179 | 180 | protected boolean pathEquals(String p1, String p2) { 181 | return (StringUtils.isBlank(p1) && StringUtils.isBlank(p2)) || p1.equals(p2); 182 | } 183 | 184 | protected Optional getResource(RestApi api, String fullPath) { 185 | for (Resource r : buildResourceList(api)) { 186 | if (r.getPath().equals(fullPath)) { 187 | return Optional.of(r); 188 | } 189 | } 190 | 191 | return Optional.empty(); 192 | } 193 | 194 | protected Optional getModel(RestApi api, String modelName) { 195 | try { 196 | return Optional.of(api.getModelByName(modelName)); 197 | } catch (Exception ignored) { 198 | return Optional.empty(); 199 | } 200 | } 201 | 202 | protected boolean methodExists(Resource resource, String httpMethod) { 203 | return resource.getResourceMethods().get(httpMethod.toUpperCase()) != null; 204 | } 205 | 206 | protected void deleteResource(Resource resource) { 207 | if (resource._isLinkAvailable("resource:delete")) { 208 | try { 209 | resource.deleteResource(); 210 | } catch (NotFoundException error) {} 211 | } 212 | // can't delete root resource 213 | } 214 | 215 | /** 216 | * Build the full resource path, including base path, add any missing leading '/', remove any trailing '/', 217 | * and remove any double '/' 218 | * @param basePath the base path 219 | * @param resourcePath the resource path 220 | * @return the full path 221 | */ 222 | protected String buildResourcePath(String basePath, String resourcePath) { 223 | if (basePath == null) { 224 | basePath = ""; 225 | } 226 | String base = trimSlashes(basePath); 227 | if (!base.equals("")) { 228 | base = "/" + base; 229 | } 230 | String result = StringUtils.removeEnd(base + "/" + trimSlashes(resourcePath), "/"); 231 | if (result.equals("")) { 232 | result = "/"; 233 | } 234 | return result; 235 | } 236 | 237 | private String trimSlashes(String path) { 238 | return StringUtils.removeEnd(StringUtils.removeStart(path, "/"), "/"); 239 | } 240 | 241 | protected Resource createResource(RestApi api, String parentResourceId, String part, List resources) { 242 | final Optional existingResource = getResource(parentResourceId, part, resources); 243 | 244 | // create resource if doesn't exist 245 | if (!existingResource.isPresent()) { 246 | 247 | LOG.info("Creating resource '" + part + "' on " + parentResourceId); 248 | 249 | CreateResourceInput input = new CreateResourceInput(); 250 | input.setPathPart(part); 251 | Resource resource = api.getResourceById(parentResourceId); 252 | 253 | Resource created = resource.createResource(input); 254 | 255 | resources.add(created); 256 | 257 | return created; 258 | } else { 259 | return existingResource.get(); 260 | } 261 | } 262 | 263 | protected String getStringValue(Object in) { 264 | return in == null ? null : String.valueOf(in); // use null value instead of "null" 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/com/amazonaws/service/apigateway/importer/util/PatchUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.util; 16 | 17 | import com.amazonaws.services.apigateway.model.PatchDocument; 18 | import com.amazonaws.services.apigateway.model.PatchOperation; 19 | 20 | import java.util.ArrayList; 21 | 22 | public class PatchUtils { 23 | 24 | public static PatchOperation createReplaceOperation(String path, String value) { 25 | PatchOperation op = new PatchOperation(); 26 | op.setOp("replace"); 27 | op.setPath(path); 28 | op.setValue(value); 29 | return op; 30 | } 31 | 32 | public static PatchOperation createRemoveOperation(String path) { 33 | PatchOperation op = new PatchOperation(); 34 | op.setOp("remove"); 35 | op.setPath(path); 36 | return op; 37 | } 38 | 39 | public static PatchOperation createAddOperation(String path, String value) { 40 | PatchOperation op = new PatchOperation(); 41 | op.setOp("add"); 42 | op.setPath(path); 43 | op.setValue(value); 44 | return op; 45 | } 46 | 47 | public static PatchDocument createPatchDocument(PatchOperation... ops) { 48 | PatchDocument pd = new PatchDocument(); 49 | pd.setPatchOperations(new ArrayList<>()); 50 | for (PatchOperation op : ops) { 51 | pd.getPatchOperations().add(op); 52 | } 53 | return pd; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/ApiImporterMainTest.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.service.apigateway.importer; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.Test; 5 | 6 | import java.net.URISyntaxException; 7 | import java.nio.file.Paths; 8 | 9 | public class ApiImporterMainTest { 10 | 11 | @Test 12 | @Ignore 13 | public void test() throws URISyntaxException { 14 | ApiImporterMain main = new ApiImporterMain(); 15 | 16 | String[] args = {"--create", Paths.get(getClass().getResource("/swagger/apigateway.json").toURI()).toString()}; 17 | main.main(args); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/config/RamlApiImporterTestModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.config; 16 | 17 | import com.amazonaws.service.apigateway.importer.RamlApiFileImporter; 18 | import com.amazonaws.service.apigateway.importer.RamlApiImporter; 19 | import com.amazonaws.service.apigateway.importer.impl.ApiGatewayRamlFileImporter; 20 | import com.amazonaws.service.apigateway.importer.impl.sdk.ApiGatewaySdkRamlApiImporter; 21 | import com.amazonaws.services.apigateway.model.ApiGateway; 22 | import com.google.inject.AbstractModule; 23 | import org.mockito.Mockito; 24 | 25 | public class RamlApiImporterTestModule extends AbstractModule { 26 | 27 | @Override 28 | protected void configure() { 29 | bind(RamlApiFileImporter.class).to(ApiGatewayRamlFileImporter.class); 30 | bind(RamlApiImporter.class).to(ApiGatewaySdkRamlApiImporter.class); 31 | bind(ApiGateway.class).toInstance(Mockito.mock(ApiGateway.class)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/config/SwaggerApiImporterTestModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.config; 16 | 17 | import com.amazonaws.service.apigateway.importer.SwaggerApiFileImporter; 18 | import com.amazonaws.service.apigateway.importer.SwaggerApiImporter; 19 | import com.amazonaws.service.apigateway.importer.impl.ApiGatewaySwaggerFileImporter; 20 | import com.amazonaws.service.apigateway.importer.impl.sdk.ApiGatewaySdkSwaggerApiImporter; 21 | import com.amazonaws.services.apigateway.model.ApiGateway; 22 | import com.google.inject.AbstractModule; 23 | import org.mockito.Mockito; 24 | 25 | public class SwaggerApiImporterTestModule extends AbstractModule { 26 | 27 | @Override 28 | protected void configure() { 29 | bind(SwaggerApiFileImporter.class).to(ApiGatewaySwaggerFileImporter.class); 30 | bind(SwaggerApiImporter.class).to(ApiGatewaySdkSwaggerApiImporter.class); 31 | bind(ApiGateway.class).toInstance(Mockito.mock(ApiGateway.class)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/impl/ApiGatewayRamlFileImporterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl; 16 | 17 | import com.amazonaws.service.apigateway.importer.RamlApiFileImporter; 18 | import com.amazonaws.service.apigateway.importer.config.RamlApiImporterTestModule; 19 | import com.amazonaws.services.apigateway.model.*; 20 | import com.amazonaws.util.json.JSONException; 21 | import com.amazonaws.util.json.JSONObject; 22 | import com.amazonaws.util.json.JSONTokener; 23 | import com.google.inject.Guice; 24 | import com.google.inject.Injector; 25 | import org.apache.log4j.BasicConfigurator; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.mockito.Mock; 29 | import org.mockito.Mockito; 30 | 31 | import java.io.FileNotFoundException; 32 | import java.io.FileReader; 33 | import java.net.URISyntaxException; 34 | import java.util.Arrays; 35 | 36 | import static org.mockito.Matchers.any; 37 | import static org.mockito.Matchers.argThat; 38 | import static org.mockito.Mockito.atLeastOnce; 39 | import static org.mockito.Mockito.mock; 40 | import static org.mockito.Mockito.times; 41 | import static org.mockito.Mockito.verify; 42 | import static org.mockito.Mockito.when; 43 | 44 | public class ApiGatewayRamlFileImporterTest { 45 | private RamlApiFileImporter importer; 46 | 47 | private final String API_GATEWAY = "/raml/apigateway.raml"; 48 | private final String API_GATEWAY_CONFIG = "/raml/apigateway.json"; 49 | 50 | @Mock 51 | private ApiGateway client; 52 | 53 | @Mock 54 | private Resource mockResource; 55 | 56 | @Mock 57 | private Resource mockChildResource; 58 | 59 | @Mock 60 | private RestApi mockRestApi; 61 | 62 | @Before 63 | public void setUp() throws Exception { 64 | BasicConfigurator.configure(); 65 | 66 | Injector injector = Guice.createInjector(new RamlApiImporterTestModule()); 67 | 68 | client = injector.getInstance(ApiGateway.class); 69 | importer = injector.getInstance(RamlApiFileImporter.class); 70 | 71 | RestApis mockRestApis = mock(RestApis.class); 72 | Integration mockIntegration = Mockito.mock(Integration.class); 73 | 74 | Method mockMethod = Mockito.mock(Method.class); 75 | when(mockMethod.getHttpMethod()).thenReturn("GET"); 76 | when(mockMethod.putIntegration(any())).thenReturn(mockIntegration); 77 | 78 | mockChildResource = Mockito.mock(Resource.class); 79 | when(mockChildResource.getPath()).thenReturn("/child"); 80 | when(mockChildResource.putMethod(any(), any())).thenReturn(mockMethod); 81 | 82 | mockResource = Mockito.mock(Resource.class); 83 | when(mockResource.getPath()).thenReturn("/"); 84 | when(mockResource.createResource(any())).thenReturn(mockChildResource); 85 | when(mockResource.putMethod(any(), any())).thenReturn(mockMethod); 86 | 87 | Resources mockResources = mock(Resources.class); 88 | when(mockResources.getItem()).thenReturn(Arrays.asList(mockResource)); 89 | 90 | Model mockModel = Mockito.mock(Model.class); 91 | when(mockModel.getName()).thenReturn("test model"); 92 | 93 | Models mockModels = mock(Models.class); 94 | when(mockModels.getItem()).thenReturn(Arrays.asList(mockModel)); 95 | 96 | mockRestApi = mock(RestApi.class); 97 | when(mockRestApi.getResources()).thenReturn(mockResources); 98 | when(mockRestApi.getModels()).thenReturn(mockModels); 99 | when(mockRestApi.getResourceById(any())).thenReturn(mockResource); 100 | 101 | when(client.getRestApis()).thenReturn(mockRestApis); 102 | when(client.createRestApi(any())).thenReturn(mockRestApi); 103 | 104 | importer.importApi(getResourcePath(API_GATEWAY), getJsonObject(API_GATEWAY_CONFIG)); 105 | } 106 | 107 | @Test 108 | public void testImport_create_api() throws Exception { 109 | verify(client, times(1)).createRestApi(any()); 110 | } 111 | 112 | @Test 113 | public void testImport_create_resources() throws Exception { 114 | // to simplify mocking, all child resources are added to the root resource, and parent resources will be added multiple times 115 | // /v1, /v1/products, /v1, /v1/products, /v1/products/child 116 | verify(mockResource, atLeastOnce()).createResource(argThat(new LambdaMatcher<>(i -> i.getPathPart().equals("v1")))); 117 | verify(mockResource, atLeastOnce()).createResource(argThat(new LambdaMatcher<>(i -> i.getPathPart().equals("products")))); 118 | verify(mockResource, atLeastOnce()).createResource(argThat(new LambdaMatcher<>(i -> i.getPathPart().equals("child")))); 119 | } 120 | 121 | @Test 122 | public void testImport_create_methods() throws Exception { 123 | verify(mockChildResource, times(1)).putMethod( 124 | argThat(new LambdaMatcher<>(i -> i.getAuthorizationType().equals("AWS_IAM"))), 125 | argThat(new LambdaMatcher<>(i -> i.equals("GET")))); 126 | verify(mockChildResource, times(1)).putMethod( 127 | argThat(new LambdaMatcher<>(i -> i.getAuthorizationType().equals("NONE"))), 128 | argThat(new LambdaMatcher<>(i -> i.equals("POST")))); 129 | } 130 | 131 | @Test 132 | public void testImport_create_models() throws Exception { 133 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Product")))); 134 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("PriceEstimate")))); 135 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Profile")))); 136 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Activity")))); 137 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Activities")))); 138 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Error")))); 139 | verify(mockRestApi, atLeastOnce()).createModel(argThat(new LambdaMatcher<>(i -> i.getName().matches("model\\w+")))); 140 | } 141 | 142 | // todo: add more tests 143 | private String getResourcePath(String path) throws URISyntaxException { 144 | return getClass().getResource(path).toURI().toString(); 145 | } 146 | 147 | private JSONObject getJsonObject(String path) throws FileNotFoundException, JSONException { 148 | return new JSONObject(new JSONTokener(new FileReader(getClass().getResource(path).getFile()))); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/impl/ApiGatewaySwaggerFileImporterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl; 16 | 17 | import com.amazonaws.service.apigateway.importer.SwaggerApiFileImporter; 18 | import com.amazonaws.service.apigateway.importer.config.SwaggerApiImporterTestModule; 19 | import com.amazonaws.services.apigateway.model.ApiGateway; 20 | import com.amazonaws.services.apigateway.model.Integration; 21 | import com.amazonaws.services.apigateway.model.Method; 22 | import com.amazonaws.services.apigateway.model.Model; 23 | import com.amazonaws.services.apigateway.model.Models; 24 | import com.amazonaws.services.apigateway.model.Resource; 25 | import com.amazonaws.services.apigateway.model.Resources; 26 | import com.amazonaws.services.apigateway.model.RestApi; 27 | import com.amazonaws.services.apigateway.model.RestApis; 28 | import com.google.inject.Guice; 29 | import com.google.inject.Injector; 30 | import org.apache.log4j.BasicConfigurator; 31 | import org.junit.Before; 32 | import org.junit.Test; 33 | import org.mockito.Mock; 34 | import org.mockito.Mockito; 35 | 36 | import java.net.URISyntaxException; 37 | import java.nio.file.Paths; 38 | import java.util.Arrays; 39 | 40 | import static org.mockito.Matchers.any; 41 | import static org.mockito.Matchers.argThat; 42 | import static org.mockito.Mockito.atLeastOnce; 43 | import static org.mockito.Mockito.mock; 44 | import static org.mockito.Mockito.times; 45 | import static org.mockito.Mockito.verify; 46 | import static org.mockito.Mockito.when; 47 | 48 | public class ApiGatewaySwaggerFileImporterTest { 49 | private SwaggerApiFileImporter importer; 50 | 51 | private final String API_GATEWAY = "/swagger/apigateway.json"; 52 | 53 | @Mock 54 | private ApiGateway client; 55 | 56 | @Mock 57 | private Resource mockResource; 58 | 59 | @Mock 60 | private Resource mockChildResource; 61 | 62 | @Mock 63 | private RestApi mockRestApi; 64 | 65 | @Before 66 | public void setUp() throws Exception { 67 | BasicConfigurator.configure(); 68 | 69 | Injector injector = Guice.createInjector(new SwaggerApiImporterTestModule()); 70 | 71 | client = injector.getInstance(ApiGateway.class); 72 | importer = injector.getInstance(SwaggerApiFileImporter.class); 73 | 74 | RestApis mockRestApis = mock(RestApis.class); 75 | Integration mockIntegration = Mockito.mock(Integration.class); 76 | 77 | Method mockMethod = Mockito.mock(Method.class); 78 | when(mockMethod.getHttpMethod()).thenReturn("GET"); 79 | when(mockMethod.putIntegration(any())).thenReturn(mockIntegration); 80 | 81 | mockChildResource = Mockito.mock(Resource.class); 82 | when(mockChildResource.getPath()).thenReturn("/child"); 83 | when(mockChildResource.putMethod(any(), any())).thenReturn(mockMethod); 84 | 85 | mockResource = Mockito.mock(Resource.class); 86 | when(mockResource.getPath()).thenReturn("/"); 87 | when(mockResource.createResource(any())).thenReturn(mockChildResource); 88 | when(mockResource.putMethod(any(), any())).thenReturn(mockMethod); 89 | 90 | Resources mockResources = mock(Resources.class); 91 | when(mockResources.getItem()).thenReturn(Arrays.asList(mockResource)); 92 | 93 | Model mockModel = Mockito.mock(Model.class); 94 | when(mockModel.getName()).thenReturn("test model"); 95 | 96 | Models mockModels = mock(Models.class); 97 | when(mockModels.getItem()).thenReturn(Arrays.asList(mockModel)); 98 | 99 | mockRestApi = mock(RestApi.class); 100 | when(mockRestApi.getResources()).thenReturn(mockResources); 101 | when(mockRestApi.getModels()).thenReturn(mockModels); 102 | when(mockRestApi.getResourceById(any())).thenReturn(mockResource); 103 | 104 | when(client.getRestApis()).thenReturn(mockRestApis); 105 | when(client.createRestApi(any())).thenReturn(mockRestApi); 106 | 107 | importer.importApi(getResourcePath(API_GATEWAY)); 108 | } 109 | 110 | @Test 111 | public void testImport_create_api() throws Exception { 112 | verify(client, times(1)).createRestApi(any()); 113 | } 114 | 115 | @Test 116 | public void testImport_create_resources() throws Exception { 117 | // to simplify mocking, all child resources are added to the root resource, and parent resources will be added multiple times 118 | // /v1, /v1/products, /v1, /v1/products, /v1/products/child 119 | verify(mockResource, atLeastOnce()).createResource(argThat(new LambdaMatcher<>(i -> i.getPathPart().equals("v1")))); 120 | verify(mockResource, atLeastOnce()).createResource(argThat(new LambdaMatcher<>(i -> i.getPathPart().equals("products")))); 121 | verify(mockResource, atLeastOnce()).createResource(argThat(new LambdaMatcher<>(i -> i.getPathPart().equals("child")))); 122 | } 123 | 124 | @Test 125 | public void testImport_create_methods() throws Exception { 126 | verify(mockChildResource, times(1)).putMethod( 127 | argThat(new LambdaMatcher<>(i -> i.getAuthorizationType().equals("AWS_IAM"))), 128 | argThat(new LambdaMatcher<>(i -> i.equals("GET")))); 129 | verify(mockChildResource, times(1)).putMethod( 130 | argThat(new LambdaMatcher<>(i -> i.getAuthorizationType().equals("NONE"))), 131 | argThat(new LambdaMatcher<>(i -> i.equals("POST")))); 132 | } 133 | 134 | @Test 135 | public void testImport_create_models() throws Exception { 136 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Product")))); 137 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("PriceEstimate")))); 138 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Profile")))); 139 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Activity")))); 140 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Activities")))); 141 | verify(mockRestApi, times(1)).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Error")))); 142 | verify(mockRestApi, atLeastOnce()).createModel(argThat(new LambdaMatcher<>(i -> i.getName().equals("Anarrayofproducts")))); 143 | } 144 | 145 | // todo: add more tests 146 | private String getResourcePath(String path) throws URISyntaxException { 147 | return Paths.get(getClass().getResource(path).toURI()).toString(); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/impl/LambdaMatcher.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.service.apigateway.importer.impl; 2 | 3 | import org.hamcrest.BaseMatcher; 4 | import org.hamcrest.Description; 5 | 6 | import java.util.Optional; 7 | import java.util.function.Predicate; 8 | 9 | public class LambdaMatcher extends BaseMatcher { 10 | private final Predicate matcher; 11 | private final Optional description; 12 | 13 | public LambdaMatcher(Predicate matcher) { 14 | this(matcher, null); 15 | } 16 | 17 | public LambdaMatcher(Predicate matcher, String description) { 18 | this.matcher = matcher; 19 | this.description = Optional.ofNullable(description); 20 | } 21 | 22 | @SuppressWarnings("unchecked") 23 | @Override 24 | public boolean matches(Object argument) { 25 | return matcher.test((T) argument); 26 | } 27 | 28 | @Override 29 | public void describeTo(Description description) { 30 | this.description.ifPresent(description::appendText); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/impl/sdk/ApiGatewaySdkApiImporterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl.sdk; 16 | 17 | import com.amazonaws.service.apigateway.importer.config.SwaggerApiImporterTestModule; 18 | import com.google.inject.Guice; 19 | import com.google.inject.Injector; 20 | import org.apache.log4j.BasicConfigurator; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | 26 | public class ApiGatewaySdkApiImporterTest { 27 | 28 | private ApiGatewaySdkApiImporter client; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | BasicConfigurator.configure(); 33 | 34 | Injector injector = Guice.createInjector(new SwaggerApiImporterTestModule()); 35 | client = injector.getInstance(ApiGatewaySdkApiImporter.class); 36 | } 37 | 38 | @Test 39 | public void testBuildResourcePath_happy() { 40 | String basePath = "/v1"; 41 | String path = "/1/2/3"; 42 | assertEquals("/v1/1/2/3", client.buildResourcePath(basePath, path)); 43 | } 44 | 45 | @Test 46 | public void testBuildResourcePath_baseBlank() { 47 | String basePath = ""; 48 | String path = "/1/2/3"; 49 | assertEquals("/1/2/3", client.buildResourcePath(basePath, path)); 50 | } 51 | 52 | @Test 53 | public void testBuildResourcePath_baseSlash() { 54 | String basePath = "/"; 55 | String path = "/1/2/3"; 56 | assertEquals("/1/2/3", client.buildResourcePath(basePath, path)); 57 | } 58 | 59 | @Test 60 | public void testBuildResourcePath_bothBlank() { 61 | String basePath = ""; 62 | String path = ""; 63 | assertEquals("/", client.buildResourcePath(basePath, path)); 64 | } 65 | 66 | @Test 67 | public void testBuildResourcePath_baseTrailingSlash() { 68 | String basePath = "/v1/"; 69 | String path = "/1/2"; 70 | assertEquals("/v1/1/2", client.buildResourcePath(basePath, path)); 71 | } 72 | 73 | @Test 74 | public void testBuildResourcePath_bothTrailingSlash() { 75 | String basePath = "/v1/"; 76 | String path = "/1/2/"; 77 | assertEquals("/v1/1/2", client.buildResourcePath(basePath, path)); 78 | } 79 | 80 | @Test 81 | public void testBuildResourcePath_bothMissingSlash() { 82 | String basePath = "v1"; 83 | String path = "1/2"; 84 | assertEquals("/v1/1/2", client.buildResourcePath(basePath, path)); 85 | } 86 | 87 | @Test 88 | public void testBuildResourcePath_pathTrailingSlash() { 89 | String basePath = ""; 90 | String path = "/1/2/"; 91 | assertEquals("/1/2", client.buildResourcePath(basePath, path)); 92 | } 93 | 94 | @Test 95 | public void testBuildResourcePath_pathRoot() { 96 | String basePath = "/v1"; 97 | String path = "/"; 98 | assertEquals("/v1", client.buildResourcePath(basePath, path)); 99 | } 100 | 101 | @Test 102 | public void testBuildResourcePath_nested() { 103 | String basePath = "/v1/2/3"; 104 | String path = "/4/5/6"; 105 | assertEquals("/v1/2/3/4/5/6", client.buildResourcePath(basePath, path)); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/impl/sdk/ApiGatewaySdkSwaggerApiImporterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | package com.amazonaws.service.apigateway.importer.impl.sdk; 16 | 17 | import com.amazonaws.service.apigateway.importer.config.SwaggerApiImporterTestModule; 18 | import com.google.inject.Guice; 19 | import com.google.inject.Injector; 20 | import io.swagger.models.Response; 21 | import junit.framework.Assert; 22 | import org.apache.log4j.BasicConfigurator; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | 26 | public class ApiGatewaySdkSwaggerApiImporterTest { 27 | 28 | private ApiGatewaySdkSwaggerApiImporter client; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | BasicConfigurator.configure(); 33 | 34 | Injector injector = Guice.createInjector(new SwaggerApiImporterTestModule()); 35 | client = injector.getInstance(ApiGatewaySdkSwaggerApiImporter.class); 36 | } 37 | 38 | @Test 39 | public void testGenerateModelName_description() { 40 | Response r = new Response(); 41 | r.setDescription("Descriptive text will be converted to model name !@$%#^$#%"); 42 | Assert.assertEquals("Wrong model name", "Descriptivetextwillbeconvertedtomodelname", client.generateModelName(r)); 43 | } 44 | 45 | @Test 46 | public void testGenerateModelName_guid() { 47 | String generated = client.generateModelName(new Response()); 48 | Assert.assertTrue("Wrong model name", generated.startsWith("model")); 49 | Assert.assertEquals("Wrong model name", 13, generated.length()); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /tst/com/amazonaws/service/apigateway/importer/integration/LargeApiIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.service.apigateway.importer.integration; 2 | 3 | import com.amazonaws.service.apigateway.importer.ApiImporterMain; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | 7 | import java.net.URISyntaxException; 8 | import java.nio.file.Paths; 9 | 10 | public class LargeApiIntegrationTest { 11 | 12 | @Test 13 | @Ignore // todo: integrate into CI 14 | public void test() throws URISyntaxException { 15 | ApiImporterMain main = new ApiImporterMain(); 16 | 17 | String[] args = {"--create", Paths.get(getClass().getResource("/swagger/large.json").toURI()).toString()}; 18 | main.main(args); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tst/resources/raml/apigateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "/child": { 3 | "get": { 4 | "auth": { 5 | "type": "aws_iam" 6 | }, 7 | "integration": { 8 | "type": "aws", 9 | "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:ACCOUNT_ID:function:myFunction/invocations", 10 | "httpMethod": "POST", 11 | "credentials": "arn:aws:iam::ACCOUNT_ID:role/lambda_exec_role", 12 | "requestTemplates": { 13 | "application/json": "json request template 2", 14 | "application/xml": "xml request template 2" 15 | }, 16 | "requestParameters": { 17 | "integration.request.path.integrationPathParam": "method.request.querystring.latitude", 18 | "integration.request.querystring.integrationQueryParam": "method.request.querystring.longitude" 19 | }, 20 | "cacheNamespace": "cache namespace", 21 | "cacheKeyParameters": [], 22 | "responses": { 23 | "2\\d{2}": { 24 | "statusCode": "200", 25 | "responseParameters": { 26 | "method.response.header.test-method-response-header": "integration.response.header.integrationResponseHeaderParam1" 27 | }, 28 | "responseTemplates": { 29 | "application/json": "json 200 response template", 30 | "application/xml": "xml 200 response template" 31 | } 32 | }, 33 | "default": { 34 | "statusCode": "400", 35 | "responseParameters": { 36 | "method.response.header.test-method-response-header": "'static value'" 37 | }, 38 | "responseTemplates": { 39 | "application/json": "json 400 response template", 40 | "application/xml": "xml 400 response template" 41 | } 42 | } 43 | } 44 | } 45 | }, 46 | "post": { 47 | "auth": { 48 | "type": "none" 49 | }, 50 | "integration": { 51 | "type": "http", 52 | "uri": "https://api.github.com", 53 | "httpMethod": "GET", 54 | "responses": { 55 | "2//d{2}": { 56 | "statusCode": "200" 57 | }, 58 | "default": { 59 | "statusCode": "400", 60 | "responseParameters": { 61 | "method.response.header.test-method-response-header": "'static value'" 62 | }, 63 | "responseTemplates": { 64 | "application/json": "json 400 response template", 65 | "application/xml": "xml 400 response template" 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tst/resources/raml/apigateway.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | 3 | title: API Gateway Test API 4 | version: 1.0.0 5 | 6 | documentation: 7 | - title: About 8 | content: Move your app forward with the Uber API 9 | 10 | baseUri: https://api.uber.com/v1 11 | 12 | mediaType: application/json 13 | 14 | schemas: 15 | - Product: | 16 | { 17 | "properties": { 18 | "product_id": { 19 | "type": "string", 20 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 21 | }, 22 | "description": { 23 | "type": "string", 24 | "description": "Description of product." 25 | }, 26 | "display_name": { 27 | "type": "string", 28 | "description": "Display name of product." 29 | }, 30 | "capacity": { 31 | "type": "string", 32 | "description": "Capacity of product. For example, 4 people." 33 | }, 34 | "image": { 35 | "type": "string", 36 | "description": "Image URL representing the product." 37 | } 38 | } 39 | } 40 | - PriceEstimate: | 41 | { 42 | "properties": { 43 | "product_id": { 44 | "type": "string", 45 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" 46 | }, 47 | "currency_code": { 48 | "type": "string", 49 | "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 50 | }, 51 | "display_name": { 52 | "type": "string", 53 | "description": "Display name of product." 54 | }, 55 | "estimate": { 56 | "type": "string", 57 | "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." 58 | }, 59 | "low_estimate": { 60 | "type": "number", 61 | "description": "Lower bound of the estimated price." 62 | }, 63 | "high_estimate": { 64 | "type": "number", 65 | "description": "Upper bound of the estimated price." 66 | }, 67 | "surge_multiplier": { 68 | "type": "number", 69 | "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." 70 | } 71 | } 72 | } 73 | - Profile: | 74 | { 75 | "properties": { 76 | "first_name": { 77 | "type": "string", 78 | "description": "First name of the Uber user." 79 | }, 80 | "last_name": { 81 | "type": "string", 82 | "description": "Last name of the Uber user." 83 | }, 84 | "email": { 85 | "type": "string", 86 | "description": "Email address of the Uber user" 87 | }, 88 | "picture": { 89 | "type": "string", 90 | "description": "Image URL of the Uber user." 91 | }, 92 | "promo_code": { 93 | "type": "string", 94 | "description": "Promo code of the Uber user." 95 | } 96 | } 97 | } 98 | - Activity: | 99 | { 100 | "properties": { 101 | "uuid": { 102 | "type": "string", 103 | "description": "Unique identifier for the activity" 104 | } 105 | } 106 | } 107 | - Activities: | 108 | { 109 | "properties": { 110 | "offset": { 111 | "type": "integer", 112 | "format": "int32", 113 | "description": "Position in pagination." 114 | }, 115 | "limit": { 116 | "type": "integer", 117 | "format": "int32", 118 | "description": "Number of items to retrieve (100 max)." 119 | }, 120 | "count": { 121 | "type": "integer", 122 | "format": "int32", 123 | "description": "Total number of items available." 124 | }, 125 | "history": { 126 | "type": "array", 127 | "items": { 128 | "properties": { 129 | "uuid": { 130 | "type": "string", 131 | "description": "Unique identifier for the activity" 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | - Error: | 139 | { 140 | "properties": { 141 | "code": { 142 | "type": "integer", 143 | "format": "int32" 144 | }, 145 | "message": { 146 | "type": "string" 147 | }, 148 | "fields": { 149 | "type": "string" 150 | } 151 | } 152 | } 153 | 154 | /products: 155 | get: 156 | description: | 157 | The Products endpoint returns information about the *Uber* products 158 | offered at a given location. The response includes the display name 159 | and other details about each product, and lists the products in the 160 | proper display order. 161 | queryParameters: 162 | latitude: 163 | description: Latitude component of location. 164 | type: number 165 | required: true 166 | longitude: 167 | description: Longitude component of location. 168 | type: number 169 | required: true 170 | responses: 171 | 200: 172 | description: An array of products 173 | body: 174 | schema: | 175 | { 176 | "type": "array", 177 | "items": { 178 | "properties": { 179 | "product_id": { 180 | "type": "string", 181 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 182 | }, 183 | "description": { 184 | "type": "string", 185 | "description": "Description of product." 186 | }, 187 | "display_name": { 188 | "type": "string", 189 | "description": "Display name of product." 190 | }, 191 | "capacity": { 192 | "type": "string", 193 | "description": "Capacity of product. For example, 4 people." 194 | }, 195 | "image": { 196 | "type": "string", 197 | "description": "Image URL representing the product." 198 | } 199 | } 200 | } 201 | } 202 | headers: 203 | test-method-response-header: 204 | type: string 205 | 400: 206 | description: Bad Request 207 | body: 208 | schema: Error 209 | /child: 210 | post: 211 | description: | 212 | The Products endpoint returns information about the *Uber* products 213 | offered at a given location. The response includes the display name 214 | and other details about each product, and lists the products in the 215 | proper display order. 216 | queryParameters: 217 | latitude: 218 | description: Latitude component of location. 219 | type: number 220 | required: true 221 | longitude: 222 | description: Longitude component of location. 223 | type: number 224 | required: true 225 | responses: 226 | 200: 227 | description: An array of products 228 | body: 229 | schema: | 230 | { 231 | "type": "array", 232 | "items": { 233 | "properties": { 234 | "product_id": { 235 | "type": "string", 236 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 237 | }, 238 | "description": { 239 | "type": "string", 240 | "description": "Description of product." 241 | }, 242 | "display_name": { 243 | "type": "string", 244 | "description": "Display name of product." 245 | }, 246 | "capacity": { 247 | "type": "string", 248 | "description": "Capacity of product. For example, 4 people." 249 | }, 250 | "image": { 251 | "type": "string", 252 | "description": "Image URL representing the product." 253 | } 254 | } 255 | } 256 | } 257 | headers: 258 | test-method-response-header: 259 | type: string 260 | 400: 261 | description: Bad Request 262 | body: 263 | schema: Error 264 | -------------------------------------------------------------------------------- /tst/resources/raml/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "/{version}/endpoint": { 3 | "get": { 4 | "auth" : { 5 | "type" : "aws_iam" 6 | }, 7 | "integration" : { 8 | "type" : "aws", 9 | "uri" : "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:MY_ACCT_ID:function:helloWorld/invocations", 10 | "httpMethod" : "POST", 11 | "credentials" : "arn:aws:iam::MY_ACCT_ID:role/lambda_exec_role", 12 | "requestTemplates" : { 13 | "application/json" : "json request template 2", 14 | "application/xml" : "xml request template 2" 15 | }, 16 | "requestParameters" : { 17 | "integration.request.path.integrationPathParam" : "method.request.querystring.latitude", 18 | "integration.request.querystring.integrationQueryParam" : "method.request.querystring.longitude" 19 | }, 20 | "cacheNamespace" : "cache-namespace", 21 | "cacheKeyParameters" : [], 22 | "responses" : { 23 | "2\\d{2}" : { 24 | "statusCode" : "200", 25 | "responseParameters" : { 26 | "method.response.header.test-method-response-header" : "integration.response.header.integrationResponseHeaderParam1" 27 | }, 28 | "responseTemplates" : { 29 | "application/json" : "json 200 response template", 30 | "application/xml" : "xml 200 response template" 31 | } 32 | }, 33 | "default" : { 34 | "statusCode" : "400", 35 | "responseParameters" : { 36 | "method.response.header.test-method-response-header" : "'static value'" 37 | }, 38 | "responseTemplates" : { 39 | "application/json" : "json 400 response template", 40 | "application/xml" : "xml 400 response template" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tst/resources/raml/example.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | 3 | title: Example API 4 | 5 | baseUri: http://example.com/{version} 6 | 7 | schemas: 8 | - endpointJson: > 9 | { 10 | "type": "string" 11 | } 12 | 13 | mediaType: application/json 14 | 15 | /endpoint: 16 | get: 17 | body: 18 | schema: endpointJson 19 | post: 20 | body: 21 | -------------------------------------------------------------------------------- /tst/resources/swagger/apigateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "API Gateway Test API", 5 | "description": "Move your app forward with the Uber API", 6 | "version": "1.0.0" 7 | }, 8 | "host": "api.uber.com", 9 | "schemes": [ 10 | "https" 11 | ], 12 | "basePath": "/v1", 13 | "produces": [ 14 | "application/json" 15 | ], 16 | "security" : [{ 17 | "api_key" : [] 18 | }], 19 | "securityDefinitions" : { 20 | "api_key": { 21 | "type": "apiKey", 22 | "name": "x-api-key", 23 | "in": "header" 24 | } 25 | }, 26 | "paths": { 27 | "/products": { 28 | "get": { 29 | "summary": "Product Types", 30 | "description": "The Products endpoint returns information about the *Uber* products\noffered at a given location. The response includes the display name\nand other details about each product, and lists the products in the\nproper display order.\n", 31 | "parameters": [ 32 | { 33 | "name": "latitude", 34 | "in": "query", 35 | "description": "Latitude component of location.", 36 | "required": true, 37 | "type": "number", 38 | "format": "double" 39 | }, 40 | { 41 | "name": "longitude", 42 | "in": "query", 43 | "description": "Longitude component of location.", 44 | "required": true, 45 | "type": "number", 46 | "format": "double" 47 | } 48 | ], 49 | "tags": [ 50 | "Products" 51 | ], 52 | "responses": { 53 | "200": { 54 | "description": "An array of products", 55 | "schema": { 56 | "type": "array", 57 | "items": { 58 | "$ref": "#/definitions/Product" 59 | } 60 | }, 61 | "headers" : { 62 | "test-method-response-header" : { 63 | "type" : "string" 64 | } 65 | } 66 | }, 67 | "400": { 68 | "description": "Bad request", 69 | "schema": { 70 | "$ref": "#/definitions/Error" 71 | } 72 | }, 73 | "default": { 74 | "description": "Unexpected error", 75 | "schema": { 76 | "$ref": "#/definitions/Error" 77 | } 78 | } 79 | }, 80 | "security" : [{ 81 | "api_key" : [] 82 | }], 83 | "x-amazon-apigateway-auth" : { 84 | "type" : "aws_iam" 85 | }, 86 | "x-amazon-apigateway-integration" : { 87 | "type" : "aws", 88 | "uri" : "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:ACCOUNT_ID:function:myFunction/invocations", 89 | "httpMethod" : "POST", 90 | "credentials" : "arn:aws:iam::ACCOUNT_ID:role/lambda_exec_role", 91 | "requestTemplates" : { 92 | "application/json" : "json request template 2", 93 | "application/xml" : "xml request template 2" 94 | }, 95 | "requestParameters" : { 96 | "integration.request.path.integrationPathParam" : "method.request.querystring.latitude", 97 | "integration.request.querystring.integrationQueryParam" : "method.request.querystring.longitude" 98 | }, 99 | "cacheNamespace" : "cache namespace", 100 | "cacheKeyParameters" : [], 101 | "responses" : { 102 | "2\\d{2}" : { 103 | "statusCode" : "200", 104 | "responseParameters" : { 105 | "method.response.header.test-method-response-header" : "integration.response.header.integrationResponseHeaderParam1" 106 | }, 107 | "responseTemplates" : { 108 | "application/json" : "json 200 response template", 109 | "application/xml" : "xml 200 response template" 110 | } 111 | }, 112 | "default" : { 113 | "statusCode" : "400", 114 | "responseParameters" : { 115 | "method.response.header.test-method-response-header" : "'static value'" 116 | }, 117 | "responseTemplates" : { 118 | "application/json" : "json 400 response template", 119 | "application/xml" : "xml 400 response template" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "/products/child": { 127 | "post": { 128 | "summary": "Product Types", 129 | "description": "The Products endpoint returns information about the *Uber* products\noffered at a given location. The response includes the display name\nand other details about each product, and lists the products in the\nproper display order.\n", 130 | "parameters": [ 131 | { 132 | "name": "latitude", 133 | "in": "query", 134 | "description": "Latitude component of location.", 135 | "required": true, 136 | "type": "number", 137 | "format": "double" 138 | }, 139 | { 140 | "name": "longitude", 141 | "in": "query", 142 | "description": "Longitude component of location.", 143 | "required": true, 144 | "type": "number", 145 | "format": "double" 146 | } 147 | ], 148 | "tags": [ 149 | "Products" 150 | ], 151 | "responses": { 152 | "200": { 153 | "description": "An array of products", 154 | "schema": { 155 | "type": "array", 156 | "items": { 157 | "$ref": "#/definitions/Product" 158 | } 159 | }, 160 | "headers" : { 161 | "test-method-response-header" : { 162 | "type" : "string" 163 | } 164 | } 165 | }, 166 | "400": { 167 | "description": "Bad request", 168 | "schema": { 169 | "$ref": "#/definitions/Error" 170 | } 171 | }, 172 | "default": { 173 | "description": "Unexpected error", 174 | "schema": { 175 | "$ref": "#/definitions/Error" 176 | } 177 | } 178 | }, 179 | "security" : [{ 180 | "api_key" : [] 181 | }], 182 | "x-amazon-apigateway-auth" : { 183 | "type" : "none" 184 | }, 185 | "x-amazon-apigateway-integration" : { 186 | "type" : "http", 187 | "uri" : "https://api.github.com", 188 | "httpMethod" : "GET", 189 | "responses" : { 190 | "2\\d{2}" : { 191 | "statusCode" : "200" 192 | }, 193 | "default" : { 194 | "statusCode" : "400", 195 | "responseParameters" : { 196 | "method.response.header.test-method-response-header" : "'static value'" 197 | }, 198 | "responseTemplates" : { 199 | "application/json" : "json 400 response template", 200 | "application/xml" : "xml 400 response template" 201 | } 202 | } 203 | } 204 | } 205 | } 206 | } 207 | }, 208 | "definitions": { 209 | "Product": { 210 | "properties": { 211 | "product_id": { 212 | "type": "string", 213 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 214 | }, 215 | "description": { 216 | "type": "string", 217 | "description": "Description of product." 218 | }, 219 | "display_name": { 220 | "type": "string", 221 | "description": "Display name of product." 222 | }, 223 | "capacity": { 224 | "type": "string", 225 | "description": "Capacity of product. For example, 4 people." 226 | }, 227 | "image": { 228 | "type": "string", 229 | "description": "Image URL representing the product." 230 | } 231 | } 232 | }, 233 | "PriceEstimate": { 234 | "properties": { 235 | "product_id": { 236 | "type": "string", 237 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" 238 | }, 239 | "currency_code": { 240 | "type": "string", 241 | "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 242 | }, 243 | "display_name": { 244 | "type": "string", 245 | "description": "Display name of product." 246 | }, 247 | "estimate": { 248 | "type": "string", 249 | "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." 250 | }, 251 | "low_estimate": { 252 | "type": "number", 253 | "description": "Lower bound of the estimated price." 254 | }, 255 | "high_estimate": { 256 | "type": "number", 257 | "description": "Upper bound of the estimated price." 258 | }, 259 | "surge_multiplier": { 260 | "type": "number", 261 | "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." 262 | } 263 | } 264 | }, 265 | "Profile": { 266 | "properties": { 267 | "first_name": { 268 | "type": "string", 269 | "description": "First name of the Uber user." 270 | }, 271 | "last_name": { 272 | "type": "string", 273 | "description": "Last name of the Uber user." 274 | }, 275 | "email": { 276 | "type": "string", 277 | "description": "Email address of the Uber user" 278 | }, 279 | "picture": { 280 | "type": "string", 281 | "description": "Image URL of the Uber user." 282 | }, 283 | "promo_code": { 284 | "type": "string", 285 | "description": "Promo code of the Uber user." 286 | } 287 | } 288 | }, 289 | "Activity": { 290 | "properties": { 291 | "uuid": { 292 | "type": "string", 293 | "description": "Unique identifier for the activity" 294 | } 295 | } 296 | }, 297 | "Activities": { 298 | "properties": { 299 | "offset": { 300 | "type": "integer", 301 | "format": "int32", 302 | "description": "Position in pagination." 303 | }, 304 | "limit": { 305 | "type": "integer", 306 | "format": "int32", 307 | "description": "Number of items to retrieve (100 max)." 308 | }, 309 | "count": { 310 | "type": "integer", 311 | "format": "int32", 312 | "description": "Total number of items available." 313 | }, 314 | "history": { 315 | "type": "array", 316 | "items": { 317 | "$ref": "#/definitions/Activity" 318 | } 319 | } 320 | } 321 | }, 322 | "Error": { 323 | "properties": { 324 | "code": { 325 | "type": "integer", 326 | "format": "int32" 327 | }, 328 | "message": { 329 | "type": "string" 330 | }, 331 | "fields": { 332 | "type": "string" 333 | } 334 | } 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /tst/resources/swagger/basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "", 3 | "apis": [ 4 | 5 | ], 6 | "basePath": "http://192.168.1.1:8000", 7 | "models": { 8 | 9 | }, 10 | "resourcePath": "/api", 11 | "swaggerVersion": "1.2" 12 | } -------------------------------------------------------------------------------- /tst/resources/swagger/large.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Uber API v2", 5 | "description": "Move your app forward with the Uber API", 6 | "version": "1.0.0" 7 | }, 8 | "host": "api.uber.com", 9 | "schemes": [ 10 | "https" 11 | ], 12 | "basePath": "/v1", 13 | "produces": [ 14 | "application/json" 15 | ], 16 | "paths": { 17 | "/" : { 18 | "get": { 19 | } 20 | }, 21 | "/level1" : { 22 | "get": { 23 | } 24 | }, 25 | "/level1/level2" : { 26 | "get": { 27 | } 28 | }, 29 | "/level1/level2/level3" : { 30 | "post": { 31 | } 32 | }, 33 | "/1" : {"get": {}}, 34 | "/2" : {"get": {}}, 35 | "/3" : {"get": {}}, 36 | "/4" : {"get": {}}, 37 | "/5" : {"get": {}}, 38 | "/6" : {"get": {}}, 39 | "/7" : {"get": {}}, 40 | "/8" : {"get": {}}, 41 | "/9" : {"get": {}}, 42 | "/10" : {"get": {}}, 43 | "/11" : {"get": {}}, 44 | "/12" : {"get": {}}, 45 | "/13" : {"get": {}}, 46 | "/14" : {"get": {}}, 47 | "/15" : {"get": {}}, 48 | "/16" : {"get": {}}, 49 | "/17" : {"get": {}}, 50 | "/18" : {"get": {}}, 51 | "/19" : {"get": {}}, 52 | "/20" : {"get": {}}, 53 | "/21" : {"get": {}}, 54 | "/22" : {"get": {}}, 55 | "/23" : {"get": {}}, 56 | "/24" : {"get": {}}, 57 | "/25" : {"get": {}}, 58 | "/26" : {"get": {}}, 59 | "/27" : {"get": {}}, 60 | "/28" : {"get": {}}, 61 | "/29" : {"get": {}}, 62 | "/30" : {"get": {}}, 63 | "/31" : {"get": {}}, 64 | "/33" : {"get": {}}, 65 | "/34" : {"get": {}}, 66 | "/35" : {"get": {}}, 67 | "/36" : {"get": {}}, 68 | "/37" : {"get": {}}, 69 | "/38" : {"get": {}}, 70 | "/39" : {"get": {}}, 71 | "/40" : {"get": {}}, 72 | "/41" : {"get": {}}, 73 | "/42" : {"get": {}}, 74 | "/43" : {"get": {}}, 75 | "/44" : {"get": {}}, 76 | "/45" : {"get": {}}, 77 | "/46" : {"get": {}}, 78 | "/47" : {"get": {}}, 79 | "/48" : {"get": {}}, 80 | "/49" : {"get": {}}, 81 | "/50" : {"get": {}}, 82 | "/51" : {"get": {}}, 83 | "/52" : {"get": {}}, 84 | "/53" : {"get": {}}, 85 | "/54" : {"get": {}}, 86 | "/55" : {"get": {}}, 87 | "/56" : {"get": {}}, 88 | "/57" : {"get": {}}, 89 | "/58" : {"get": {}}, 90 | "/59" : {"get": {}} 91 | 92 | }, 93 | "definitions": { 94 | "Product": { 95 | "properties": { 96 | "product_id": { 97 | "type": "string", 98 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 99 | }, 100 | "description": { 101 | "type": "string", 102 | "description": "Description of product." 103 | }, 104 | "display_name": { 105 | "type": "string", 106 | "description": "Display name of product." 107 | }, 108 | "capacity": { 109 | "type": "string", 110 | "description": "Capacity of product. For example, 4 people." 111 | }, 112 | "image": { 113 | "type": "string", 114 | "description": "Image URL representing the product." 115 | } 116 | } 117 | }, 118 | "PriceEstimate": { 119 | "properties": { 120 | "product_id": { 121 | "type": "string", 122 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" 123 | }, 124 | "currency_code": { 125 | "type": "string", 126 | "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 127 | }, 128 | "display_name": { 129 | "type": "string", 130 | "description": "Display name of product." 131 | }, 132 | "estimate": { 133 | "type": "string", 134 | "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." 135 | }, 136 | "low_estimate": { 137 | "type": "number", 138 | "description": "Lower bound of the estimated price." 139 | }, 140 | "high_estimate": { 141 | "type": "number", 142 | "description": "Upper bound of the estimated price." 143 | }, 144 | "surge_multiplier": { 145 | "type": "number", 146 | "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." 147 | } 148 | } 149 | }, 150 | "Profile": { 151 | "properties": { 152 | "first_name": { 153 | "type": "string", 154 | "description": "First name of the Uber user." 155 | }, 156 | "last_name": { 157 | "type": "string", 158 | "description": "Last name of the Uber user." 159 | }, 160 | "email": { 161 | "type": "string", 162 | "description": "Email address of the Uber user" 163 | }, 164 | "picture": { 165 | "type": "string", 166 | "description": "Image URL of the Uber user." 167 | }, 168 | "promo_code": { 169 | "type": "string", 170 | "description": "Promo code of the Uber user." 171 | } 172 | } 173 | }, 174 | "Activity": {"properties": {"uuid": {"type": "string"}}}, 175 | "Activity1": {"properties": {"uuid": {"type": "string"}}}, 176 | "Activity2": {"properties": {"uuid": {"type": "string"}}}, 177 | "Activity3": {"properties": {"uuid": {"type": "string"}}}, 178 | "Activity4": {"properties": {"uuid": {"type": "string"}}}, 179 | "Activity5": {"properties": {"uuid": {"type": "string"}}}, 180 | "Activity6": {"properties": {"uuid": {"type": "string"}}}, 181 | "Activity7": {"properties": {"uuid": {"type": "string"}}}, 182 | "Activity8": {"properties": {"uuid": {"type": "string"}}}, 183 | "Activity9": {"properties": {"uuid": {"type": "string"}}}, 184 | "Activity10": {"properties": {"uuid": {"type": "string"}}}, 185 | "Activity11": {"properties": {"uuid": {"type": "string"}}}, 186 | "Activity12": {"properties": {"uuid": {"type": "string"}}}, 187 | "Activity13": {"properties": {"uuid": {"type": "string"}}}, 188 | "Activity14": {"properties": {"uuid": {"type": "string"}}}, 189 | "Activity15": {"properties": {"uuid": {"type": "string"}}}, 190 | "Activity16": {"properties": {"uuid": {"type": "string"}}}, 191 | "Activity17": {"properties": {"uuid": {"type": "string"}}}, 192 | "Activity18": {"properties": {"uuid": {"type": "string"}}}, 193 | "Activity19": {"properties": {"uuid": {"type": "string"}}}, 194 | "Activity20": {"properties": {"uuid": {"type": "string"}}}, 195 | "Activity21": {"properties": {"uuid": {"type": "string"}}}, 196 | "Activity22": {"properties": {"uuid": {"type": "string"}}}, 197 | "Activity23": {"properties": {"uuid": {"type": "string"}}}, 198 | "Activity24": {"properties": {"uuid": {"type": "string"}}}, 199 | "Activity25": {"properties": {"uuid": {"type": "string"}}}, 200 | "Activity26": {"properties": {"uuid": {"type": "string"}}}, 201 | "Activity27": {"properties": {"uuid": {"type": "string"}}}, 202 | "Activity28": {"properties": {"uuid": {"type": "string"}}}, 203 | "Activity29": {"properties": {"uuid": {"type": "string"}}}, 204 | "Activities": { 205 | "properties": { 206 | "offset": { 207 | "type": "integer", 208 | "format": "int32", 209 | "description": "Position in pagination." 210 | }, 211 | "limit": { 212 | "type": "integer", 213 | "format": "int32", 214 | "description": "Number of items to retrieve (100 max)." 215 | }, 216 | "count": { 217 | "type": "integer", 218 | "format": "int32", 219 | "description": "Total number of items available." 220 | }, 221 | "history": { 222 | "type": "array", 223 | "items": { 224 | "$ref": "#/definitions/Activity" 225 | } 226 | } 227 | } 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /tst/resources/swagger/petstore-expanded.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "name": "Swagger API Team", 10 | "email": "foo@example.com", 11 | "url": "http://madskristensen.net" 12 | }, 13 | "license": { 14 | "name": "MIT", 15 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 16 | } 17 | }, 18 | "host": "petstore.swagger.io", 19 | "basePath": "/api", 20 | "schemes": [ 21 | "http" 22 | ], 23 | "consumes": [ 24 | "application/json" 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "paths": { 30 | "/pets": { 31 | "get": { 32 | "description": "Returns all pets from the system that the user has access to\nNam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.\n\nSed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.\n", 33 | "operationId": "findPets", 34 | "parameters": [ 35 | { 36 | "name": "tags", 37 | "in": "query", 38 | "description": "tags to filter by", 39 | "required": false, 40 | "type": "array", 41 | "collectionFormat": "csv", 42 | "items": { 43 | "type": "string" 44 | } 45 | }, 46 | { 47 | "name": "limit", 48 | "in": "query", 49 | "description": "maximum number of results to return", 50 | "required": false, 51 | "type": "integer", 52 | "format": "int32" 53 | } 54 | ], 55 | "responses": { 56 | "200": { 57 | "description": "pet response", 58 | "schema": { 59 | "type": "array", 60 | "items": { 61 | "$ref": "#/definitions/Pet" 62 | } 63 | } 64 | }, 65 | "default": { 66 | "description": "unexpected error", 67 | "schema": { 68 | "$ref": "#/definitions/Error" 69 | } 70 | } 71 | } 72 | }, 73 | "post": { 74 | "description": "Creates a new pet in the store. Duplicates are allowed", 75 | "operationId": "addPet", 76 | "parameters": [ 77 | { 78 | "name": "pet", 79 | "in": "body", 80 | "description": "Pet to add to the store", 81 | "required": true, 82 | "schema": { 83 | "$ref": "#/definitions/NewPet" 84 | } 85 | } 86 | ], 87 | "responses": { 88 | "200": { 89 | "description": "pet response", 90 | "schema": { 91 | "$ref": "#/definitions/Pet" 92 | } 93 | }, 94 | "default": { 95 | "description": "unexpected error", 96 | "schema": { 97 | "$ref": "#/definitions/Error" 98 | } 99 | } 100 | } 101 | } 102 | }, 103 | "/pets/{id}": { 104 | "get": { 105 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 106 | "operationId": "find pet by id", 107 | "parameters": [ 108 | { 109 | "name": "id", 110 | "in": "path", 111 | "description": "ID of pet to fetch", 112 | "required": true, 113 | "type": "integer", 114 | "format": "int64" 115 | } 116 | ], 117 | "responses": { 118 | "200": { 119 | "description": "pet response", 120 | "schema": { 121 | "$ref": "#/definitions/Pet" 122 | } 123 | }, 124 | "default": { 125 | "description": "unexpected error", 126 | "schema": { 127 | "$ref": "#/definitions/Error" 128 | } 129 | } 130 | } 131 | }, 132 | "delete": { 133 | "description": "deletes a single pet based on the ID supplied", 134 | "operationId": "deletePet", 135 | "parameters": [ 136 | { 137 | "name": "id", 138 | "in": "path", 139 | "description": "ID of pet to delete", 140 | "required": true, 141 | "type": "integer", 142 | "format": "int64" 143 | } 144 | ], 145 | "responses": { 146 | "204": { 147 | "description": "pet deleted" 148 | }, 149 | "default": { 150 | "description": "unexpected error", 151 | "schema": { 152 | "$ref": "#/definitions/Error" 153 | } 154 | } 155 | } 156 | } 157 | } 158 | }, 159 | "definitions": { 160 | "Pet": { 161 | "type": "object", 162 | "allOf": [ 163 | { 164 | "$ref": "#/definitions/NewPet" 165 | }, 166 | { 167 | "required": [ 168 | "id" 169 | ], 170 | "properties": { 171 | "id": { 172 | "type": "integer", 173 | "format": "int64" 174 | } 175 | } 176 | } 177 | ] 178 | }, 179 | "NewPet": { 180 | "type": "object", 181 | "required": [ 182 | "name" 183 | ], 184 | "properties": { 185 | "name": { 186 | "type": "string" 187 | }, 188 | "tag": { 189 | "type": "string" 190 | } 191 | } 192 | }, 193 | "Error": { 194 | "type": "object", 195 | "required": [ 196 | "code", 197 | "message" 198 | ], 199 | "properties": { 200 | "code": { 201 | "type": "integer", 202 | "format": "int32" 203 | }, 204 | "message": { 205 | "type": "string" 206 | } 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /tst/resources/swagger/petstore-minimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "name": "Swagger API Team" 10 | }, 11 | "license": { 12 | "name": "MIT" 13 | } 14 | }, 15 | "host": "petstore.swagger.io", 16 | "basePath": "/api", 17 | "schemes": [ 18 | "http" 19 | ], 20 | "consumes": [ 21 | "application/json" 22 | ], 23 | "produces": [ 24 | "application/json" 25 | ], 26 | "paths": { 27 | "/pets": { 28 | "get": { 29 | "description": "Returns all pets from the system that the user has access to", 30 | "produces": [ 31 | "application/json" 32 | ], 33 | "responses": { 34 | "200": { 35 | "description": "A list of pets.", 36 | "schema": { 37 | "type": "array", 38 | "items": { 39 | "$ref": "#/definitions/Pet" 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }, 47 | "definitions": { 48 | "Pet": { 49 | "type": "object", 50 | "required": [ 51 | "id", 52 | "name" 53 | ], 54 | "properties": { 55 | "id": { 56 | "type": "integer", 57 | "format": "int64" 58 | }, 59 | "name": { 60 | "type": "string" 61 | }, 62 | "tag": { 63 | "type": "string" 64 | } 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /tst/resources/swagger/petstore-simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "name": "Swagger API Team" 10 | }, 11 | "license": { 12 | "name": "MIT" 13 | } 14 | }, 15 | "host": "petstore.swagger.io", 16 | "basePath": "/api", 17 | "schemes": [ 18 | "http" 19 | ], 20 | "consumes": [ 21 | "application/json" 22 | ], 23 | "produces": [ 24 | "application/json" 25 | ], 26 | "paths": { 27 | "/pets": { 28 | "get": { 29 | "description": "Returns all pets from the system that the user has access to", 30 | "operationId": "findPets", 31 | "produces": [ 32 | "application/json", 33 | "application/xml", 34 | "text/xml", 35 | "text/html" 36 | ], 37 | "parameters": [ 38 | { 39 | "name": "tags", 40 | "in": "query", 41 | "description": "tags to filter by", 42 | "required": false, 43 | "type": "array", 44 | "items": { 45 | "type": "string" 46 | }, 47 | "collectionFormat": "csv" 48 | }, 49 | { 50 | "name": "limit", 51 | "in": "query", 52 | "description": "maximum number of results to return", 53 | "required": false, 54 | "type": "integer", 55 | "format": "int32" 56 | } 57 | ], 58 | "responses": { 59 | "200": { 60 | "description": "pet response", 61 | "schema": { 62 | "type": "array", 63 | "items": { 64 | "$ref": "#/definitions/Pet" 65 | } 66 | } 67 | }, 68 | "default": { 69 | "description": "unexpected error", 70 | "schema": { 71 | "$ref": "#/definitions/ErrorModel" 72 | } 73 | } 74 | } 75 | }, 76 | "post": { 77 | "description": "Creates a new pet in the store. Duplicates are allowed", 78 | "operationId": "addPet", 79 | "produces": [ 80 | "application/json" 81 | ], 82 | "parameters": [ 83 | { 84 | "name": "pet", 85 | "in": "body", 86 | "description": "Pet to add to the store", 87 | "required": true, 88 | "schema": { 89 | "$ref": "#/definitions/PetInput" 90 | } 91 | } 92 | ], 93 | "responses": { 94 | "200": { 95 | "description": "pet response", 96 | "schema": { 97 | "$ref": "#/definitions/Pet" 98 | } 99 | }, 100 | "default": { 101 | "description": "unexpected error", 102 | "schema": { 103 | "$ref": "#/definitions/ErrorModel" 104 | } 105 | } 106 | } 107 | } 108 | }, 109 | "/pets/{id}": { 110 | "get": { 111 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 112 | "operationId": "findPetById", 113 | "produces": [ 114 | "application/json", 115 | "application/xml", 116 | "text/xml", 117 | "text/html" 118 | ], 119 | "parameters": [ 120 | { 121 | "name": "id", 122 | "in": "path", 123 | "description": "ID of pet to fetch", 124 | "required": true, 125 | "type": "integer", 126 | "format": "int64" 127 | } 128 | ], 129 | "responses": { 130 | "200": { 131 | "description": "pet response", 132 | "schema": { 133 | "$ref": "#/definitions/Pet" 134 | } 135 | }, 136 | "default": { 137 | "description": "unexpected error", 138 | "schema": { 139 | "$ref": "#/definitions/ErrorModel" 140 | } 141 | } 142 | } 143 | }, 144 | "delete": { 145 | "description": "deletes a single pet based on the ID supplied", 146 | "operationId": "deletePet", 147 | "parameters": [ 148 | { 149 | "name": "id", 150 | "in": "path", 151 | "description": "ID of pet to delete", 152 | "required": true, 153 | "type": "integer", 154 | "format": "int64" 155 | } 156 | ], 157 | "responses": { 158 | "204": { 159 | "description": "pet deleted" 160 | }, 161 | "default": { 162 | "description": "unexpected error", 163 | "schema": { 164 | "$ref": "#/definitions/ErrorModel" 165 | } 166 | } 167 | } 168 | } 169 | } 170 | }, 171 | "definitions": { 172 | "Pet": { 173 | "type": "object", 174 | "required": [ 175 | "id", 176 | "name" 177 | ], 178 | "properties": { 179 | "id": { 180 | "type": "integer", 181 | "format": "int64" 182 | }, 183 | "name": { 184 | "type": "string" 185 | }, 186 | "tag": { 187 | "type": "string" 188 | } 189 | } 190 | }, 191 | "PetInput": { 192 | "type": "object", 193 | "allOf": [ 194 | { 195 | "$ref": "#/definitions/Pet" 196 | }, 197 | { 198 | "required": [ 199 | "name" 200 | ], 201 | "properties": { 202 | "id": { 203 | "type": "integer", 204 | "format": "int64" 205 | } 206 | } 207 | } 208 | ] 209 | }, 210 | "ErrorModel": { 211 | "type": "object", 212 | "required": [ 213 | "code", 214 | "message" 215 | ], 216 | "properties": { 217 | "code": { 218 | "type": "integer", 219 | "format": "int32" 220 | }, 221 | "message": { 222 | "type": "string" 223 | } 224 | } 225 | } 226 | } 227 | } -------------------------------------------------------------------------------- /tst/resources/swagger/petstore-with-external-docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "name": "Swagger API Team", 10 | "email": "apiteam@swagger.io", 11 | "url": "http://swagger.io" 12 | }, 13 | "license": { 14 | "name": "MIT", 15 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 16 | } 17 | }, 18 | "externalDocs": { 19 | "description": "find more info here", 20 | "url": "https://swagger.io/about" 21 | }, 22 | "host": "petstore.swagger.io", 23 | "basePath": "/api", 24 | "schemes": [ 25 | "http" 26 | ], 27 | "consumes": [ 28 | "application/json" 29 | ], 30 | "produces": [ 31 | "application/json" 32 | ], 33 | "paths": { 34 | "/pets": { 35 | "get": { 36 | "description": "Returns all pets from the system that the user has access to", 37 | "operationId": "findPets", 38 | "externalDocs": { 39 | "description": "find more info here", 40 | "url": "https://swagger.io/about" 41 | }, 42 | "produces": [ 43 | "application/json", 44 | "application/xml", 45 | "text/xml", 46 | "text/html" 47 | ], 48 | "parameters": [ 49 | { 50 | "name": "tags", 51 | "in": "query", 52 | "description": "tags to filter by", 53 | "required": false, 54 | "type": "array", 55 | "items": { 56 | "type": "string" 57 | }, 58 | "collectionFormat": "csv" 59 | }, 60 | { 61 | "name": "limit", 62 | "in": "query", 63 | "description": "maximum number of results to return", 64 | "required": false, 65 | "type": "integer", 66 | "format": "int32" 67 | } 68 | ], 69 | "responses": { 70 | "200": { 71 | "description": "pet response", 72 | "schema": { 73 | "type": "array", 74 | "items": { 75 | "$ref": "#/definitions/Pet" 76 | } 77 | } 78 | }, 79 | "default": { 80 | "description": "unexpected error", 81 | "schema": { 82 | "$ref": "#/definitions/ErrorModel" 83 | } 84 | } 85 | } 86 | }, 87 | "post": { 88 | "description": "Creates a new pet in the store. Duplicates are allowed", 89 | "operationId": "addPet", 90 | "produces": [ 91 | "application/json" 92 | ], 93 | "parameters": [ 94 | { 95 | "name": "pet", 96 | "in": "body", 97 | "description": "Pet to add to the store", 98 | "required": true, 99 | "schema": { 100 | "$ref": "#/definitions/NewPet" 101 | } 102 | } 103 | ], 104 | "responses": { 105 | "200": { 106 | "description": "pet response", 107 | "schema": { 108 | "$ref": "#/definitions/Pet" 109 | } 110 | }, 111 | "default": { 112 | "description": "unexpected error", 113 | "schema": { 114 | "$ref": "#/definitions/ErrorModel" 115 | } 116 | } 117 | } 118 | } 119 | }, 120 | "/pets/{id}": { 121 | "get": { 122 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 123 | "operationId": "findPetById", 124 | "produces": [ 125 | "application/json", 126 | "application/xml", 127 | "text/xml", 128 | "text/html" 129 | ], 130 | "parameters": [ 131 | { 132 | "name": "id", 133 | "in": "path", 134 | "description": "ID of pet to fetch", 135 | "required": true, 136 | "type": "integer", 137 | "format": "int64" 138 | } 139 | ], 140 | "responses": { 141 | "200": { 142 | "description": "pet response", 143 | "schema": { 144 | "$ref": "#/definitions/Pet" 145 | } 146 | }, 147 | "default": { 148 | "description": "unexpected error", 149 | "schema": { 150 | "$ref": "#/definitions/ErrorModel" 151 | } 152 | } 153 | } 154 | }, 155 | "delete": { 156 | "description": "deletes a single pet based on the ID supplied", 157 | "operationId": "deletePet", 158 | "parameters": [ 159 | { 160 | "name": "id", 161 | "in": "path", 162 | "description": "ID of pet to delete", 163 | "required": true, 164 | "type": "integer", 165 | "format": "int64" 166 | } 167 | ], 168 | "responses": { 169 | "204": { 170 | "description": "pet deleted" 171 | }, 172 | "default": { 173 | "description": "unexpected error", 174 | "schema": { 175 | "$ref": "#/definitions/ErrorModel" 176 | } 177 | } 178 | } 179 | } 180 | } 181 | }, 182 | "definitions": { 183 | "Pet": { 184 | "type": "object", 185 | "required": [ 186 | "id", 187 | "name" 188 | ], 189 | "externalDocs": { 190 | "description": "find more info here", 191 | "url": "https://swagger.io/about" 192 | }, 193 | "properties": { 194 | "id": { 195 | "type": "integer", 196 | "format": "int64" 197 | }, 198 | "name": { 199 | "type": "string" 200 | }, 201 | "tag": { 202 | "type": "string" 203 | } 204 | } 205 | }, 206 | "NewPet": { 207 | "type": "object", 208 | "allOf": [ 209 | { 210 | "$ref": "#/definitions/Pet" 211 | }, 212 | { 213 | "required": [ 214 | "name" 215 | ], 216 | "properties": { 217 | "id": { 218 | "type": "integer", 219 | "format": "int64" 220 | } 221 | } 222 | } 223 | ] 224 | }, 225 | "ErrorModel": { 226 | "type": "object", 227 | "required": [ 228 | "code", 229 | "message" 230 | ], 231 | "properties": { 232 | "code": { 233 | "type": "integer", 234 | "format": "int32" 235 | }, 236 | "message": { 237 | "type": "string" 238 | } 239 | } 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /tst/resources/swagger/petstore.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "swagger": "2.0", 4 | "info": { 5 | "version": "1.0.0", 6 | "title": "Swagger Petstore", 7 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 8 | "termsOfService": "http://swagger.io/terms/", 9 | "contact": { 10 | "name": "Swagger API Team", 11 | "email": "apiteam@swagger.io", 12 | "url": "http://swagger.io" 13 | }, 14 | "license": { 15 | "name": "MIT", 16 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 17 | } 18 | }, 19 | "externalDocs": { 20 | "description": "find more info here", 21 | "url": "https://swagger.io/about" 22 | }, 23 | "host": "petstore.swagger.io", 24 | "basePath": "/api", 25 | "schemes": [ 26 | "http" 27 | ], 28 | "consumes": [ 29 | "application/json" 30 | ], 31 | "produces": [ 32 | "application/json" 33 | ], 34 | "paths": { 35 | "/pets2/pets3" : {}, 36 | "/pets": { 37 | "get": { 38 | "description": "Returns all pets from the system that the user has access to", 39 | "operationId": "findPets", 40 | "externalDocs": { 41 | "description": "find more info here", 42 | "url": "https://swagger.io/about" 43 | }, 44 | "produces": [ 45 | "application/json", 46 | "application/xml", 47 | "text/xml", 48 | "text/html" 49 | ], 50 | "parameters": [ 51 | { 52 | "name": "tags", 53 | "in": "query", 54 | "description": "tags to filter by", 55 | "required": false, 56 | "type": "array", 57 | "items": { 58 | "type": "string" 59 | }, 60 | "collectionFormat": "csv" 61 | }, 62 | { 63 | "name": "limit", 64 | "in": "query", 65 | "description": "maximum number of results to return", 66 | "required": false, 67 | "type": "integer", 68 | "format": "int32" 69 | } 70 | ], 71 | "responses": { 72 | "200": { 73 | "description": "pet response", 74 | "schema": { 75 | "type": "array", 76 | "items": { 77 | "$ref": "#/definitions/Pet" 78 | } 79 | } 80 | }, 81 | "default": { 82 | "description": "unexpected error", 83 | "schema": { 84 | "$ref": "#/definitions/ErrorModel" 85 | } 86 | } 87 | } 88 | }, 89 | "post": { 90 | "description": "Creates a new pet in the store. Duplicates are allowed", 91 | "operationId": "addPet", 92 | "produces": [ 93 | "application/json" 94 | ], 95 | "parameters": [ 96 | { 97 | "name": "pet", 98 | "in": "body", 99 | "description": "Pet to add to the store", 100 | "required": true, 101 | "schema": { 102 | "$ref": "#/definitions/NewPet" 103 | } 104 | } 105 | ], 106 | "responses": { 107 | "200": { 108 | "description": "pet response", 109 | "schema": { 110 | "$ref": "#/definitions/Pet" 111 | } 112 | }, 113 | "default": { 114 | "description": "unexpected error", 115 | "schema": { 116 | "$ref": "#/definitions/ErrorModel" 117 | } 118 | } 119 | } 120 | } 121 | }, 122 | "/pets/{id}": { 123 | "get": { 124 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 125 | "operationId": "findPetById", 126 | "produces": [ 127 | "application/json", 128 | "application/xml", 129 | "text/xml", 130 | "text/html" 131 | ], 132 | "parameters": [ 133 | { 134 | "name": "id", 135 | "in": "path", 136 | "description": "ID of pet to fetch", 137 | "required": true, 138 | "type": "integer", 139 | "format": "int64" 140 | } 141 | ], 142 | "responses": { 143 | "200": { 144 | "description": "pet response", 145 | "schema": { 146 | "$ref": "#/definitions/Pet" 147 | } 148 | }, 149 | "default": { 150 | "description": "unexpected error", 151 | "schema": { 152 | "$ref": "#/definitions/ErrorModel" 153 | } 154 | } 155 | } 156 | }, 157 | "delete": { 158 | "description": "deletes a single pet based on the ID supplied", 159 | "operationId": "deletePet", 160 | "parameters": [ 161 | { 162 | "name": "id", 163 | "in": "path", 164 | "description": "ID of pet to delete", 165 | "required": true, 166 | "type": "integer", 167 | "format": "int64" 168 | } 169 | ], 170 | "responses": { 171 | "204": { 172 | "description": "pet deleted" 173 | }, 174 | "default": { 175 | "description": "unexpected error", 176 | "schema": { 177 | "$ref": "#/definitions/ErrorModel" 178 | } 179 | } 180 | } 181 | } 182 | } 183 | }, 184 | "definitions": { 185 | "Pet": { 186 | "required": [ 187 | "id", 188 | "name" 189 | ], 190 | 191 | "properties": { 192 | "id": { 193 | "type": "integer", 194 | "format": "int64" 195 | }, 196 | "name": { 197 | "type": "string" 198 | }, 199 | "tag": { 200 | "type": "string" 201 | } 202 | } 203 | }, 204 | "NewPet": { 205 | "allOf": [ 206 | { 207 | "$ref": "#/definitions/Pet" 208 | }, 209 | { 210 | "required": [ 211 | "name" 212 | ], 213 | "properties": { 214 | "id": { 215 | "type": "integer", 216 | "format": "int64" 217 | } 218 | } 219 | } 220 | ] 221 | }, 222 | "ErrorModel": { 223 | "required": [ 224 | "code", 225 | "message" 226 | ], 227 | "properties": { 228 | "code": { 229 | "type": "integer", 230 | "format": "int32" 231 | }, 232 | "message": { 233 | "type": "string" 234 | } 235 | } 236 | } 237 | } 238 | } -------------------------------------------------------------------------------- /tst/resources/swagger/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Uber API v2", 5 | "description": "Move your app forward with the Uber API", 6 | "version": "1.0.0" 7 | }, 8 | "host": "api.uber.com", 9 | "schemes": [ 10 | "https" 11 | ], 12 | "basePath": "/api/v1", 13 | "produces": [ 14 | "application/json" 15 | ], 16 | "paths": { 17 | "/" : { 18 | "get": { 19 | } 20 | }, 21 | "/level1" : { 22 | "get": { 23 | } 24 | }, 25 | "/level1/level2" : { 26 | "get": { 27 | } 28 | }, 29 | "/level1/level2/level3" : { 30 | "post": { 31 | } 32 | }, 33 | "noslash" : { 34 | "get": { 35 | } 36 | }, 37 | "postbody" : { 38 | "post": { 39 | "summary": "Product Types", 40 | "description": "The Products endpoint returns information about the *Uber* products\noffered at a given location. The response includes the display name\nand other details about each product, and lists the products in the\nproper display order.\n", 41 | "parameters": [ 42 | { 43 | "name": "postbody", 44 | "in": "body", 45 | "description": "Latitude component of location.", 46 | "required": true, 47 | "type": "object", 48 | "schema": { 49 | "properties": { 50 | "prop1": { 51 | "type": "string" 52 | }, 53 | "prop2": { 54 | "type": "array", 55 | "items": { 56 | "$ref": "#/definitions/Product" 57 | } 58 | 59 | } 60 | } 61 | } 62 | } 63 | ], 64 | "tags": [ 65 | "Products" 66 | ], 67 | "responses": { 68 | "200": { 69 | "description": "An array of products", 70 | "schema": { 71 | "type": "array", 72 | "items": { 73 | "$ref": "#/definitions/Product" 74 | } 75 | } 76 | }, 77 | "400": { 78 | "schema": { 79 | "properties": { 80 | "prop1" : { "type" : "string" }, 81 | "prop2" : { "type" : "array"} 82 | } 83 | } 84 | }, 85 | "default": { 86 | "description": "Unexpected error", 87 | "schema": { 88 | "$ref": "#/definitions/Error" 89 | } 90 | } 91 | } 92 | } 93 | }, 94 | "/products": { 95 | "get": { 96 | "summary": "Product Types", 97 | "description": "The Products endpoint returns information about the *Uber* products\noffered at a given location. The response includes the display name\nand other details about each product, and lists the products in the\nproper display order.\n", 98 | "parameters": [ 99 | { 100 | "name": "latitude", 101 | "in": "query", 102 | "description": "Latitude component of location.", 103 | "required": true, 104 | "type": "number", 105 | "format": "double" 106 | }, 107 | { 108 | "name": "longitude", 109 | "in": "query", 110 | "description": "Longitude component of location.", 111 | "required": true, 112 | "type": "number", 113 | "format": "double" 114 | } 115 | ], 116 | "tags": [ 117 | "Products" 118 | ], 119 | "responses": { 120 | "200": { 121 | "description": "An array of products", 122 | "schema": { 123 | "type": "array", 124 | "items": { 125 | "$ref": "#/definitions/Product" 126 | } 127 | } 128 | }, 129 | "400": { 130 | "schema": { 131 | "type": "object", 132 | "properties": { 133 | "prop1" : { "type" : "string" }, 134 | "prop2" : { "type" : "array"} 135 | } 136 | } 137 | }, 138 | "default": { 139 | "description": "Unexpected error", 140 | "schema": { 141 | "$ref": "#/definitions/Error" 142 | } 143 | } 144 | } 145 | } 146 | }, 147 | "/estimates/price": { 148 | "get": { 149 | "summary": "Price Estimates", 150 | "description": "The Price Estimates endpoint returns an estimated price range\nfor each product offered at a given location. The price estimate is\nprovided as a formatted string with the full price range and the localized\ncurrency symbol.

The response also includes low and high estimates,\nand the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for\nsituations requiring currency conversion. When surge is active for a particular\nproduct, its surge_multiplier will be greater than 1, but the price estimate\nalready factors in this multiplier.\n", 151 | "parameters": [ 152 | { 153 | "name": "start_latitude", 154 | "in": "query", 155 | "description": "Latitude component of start location.", 156 | "required": true, 157 | "type": "number", 158 | "format": "double" 159 | }, 160 | { 161 | "name": "start_longitude", 162 | "in": "query", 163 | "description": "Longitude component of start location.", 164 | "required": true, 165 | "type": "number", 166 | "format": "double" 167 | }, 168 | { 169 | "name": "end_latitude", 170 | "in": "query", 171 | "description": "Latitude component of end location.", 172 | "required": true, 173 | "type": "number", 174 | "format": "double" 175 | }, 176 | { 177 | "name": "end_longitude", 178 | "in": "query", 179 | "description": "Longitude component of end location.", 180 | "required": true, 181 | "type": "number", 182 | "format": "double" 183 | } 184 | ], 185 | "tags": [ 186 | "Estimates" 187 | ], 188 | "responses": { 189 | "200": { 190 | "description": "An array of price estimates by product", 191 | "schema": { 192 | "type": "array", 193 | "items": { 194 | "$ref": "#/definitions/PriceEstimate" 195 | } 196 | } 197 | }, 198 | "default": { 199 | "description": "Unexpected error", 200 | "schema": { 201 | "$ref": "#/definitions/Error" 202 | } 203 | } 204 | } 205 | }, 206 | 207 | "put" : { 208 | }, 209 | "delete" : { 210 | }, 211 | "options" : { 212 | }, 213 | "patch" : { 214 | } 215 | }, 216 | "/estimates/time": { 217 | "get": { 218 | "summary": "Time Estimates", 219 | "description": "The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs.", 220 | "parameters": [ 221 | { 222 | "name": "start_latitude", 223 | "in": "query", 224 | "description": "Latitude component of start location.", 225 | "required": true, 226 | "type": "number", 227 | "format": "double" 228 | }, 229 | { 230 | "name": "start_longitude", 231 | "in": "query", 232 | "description": "Longitude component of start location.", 233 | "required": true, 234 | "type": "number", 235 | "format": "double" 236 | }, 237 | { 238 | "name": "customer_uuid", 239 | "in": "query", 240 | "type": "string", 241 | "format": "uuid", 242 | "description": "Unique customer identifier to be used for experience customization." 243 | }, 244 | { 245 | "name": "product_id", 246 | "in": "query", 247 | "type": "string", 248 | "description": "Unique identifier representing a specific product for a given latitude & longitude." 249 | } 250 | ], 251 | "tags": [ 252 | "Estimates" 253 | ], 254 | "responses": { 255 | "200": { 256 | "description": "An array of products", 257 | "schema": { 258 | "type": "array", 259 | "items": { 260 | "$ref": "#/definitions/Product" 261 | } 262 | } 263 | }, 264 | "301": { 265 | "description": "An array of products", 266 | "schema": { 267 | "type": "array", 268 | "items": { 269 | "$ref": "#/definitions/Product" 270 | } 271 | } 272 | }, 273 | 274 | "default": { 275 | "description": "Unexpected error", 276 | "schema": { 277 | "$ref": "#/definitions/Error" 278 | } 279 | } 280 | } 281 | } 282 | }, 283 | "/me": { 284 | "get": { 285 | "summary": "User Profile", 286 | "description": "The User Profile endpoint returns information about the Uber user that has authorized with the application.", 287 | "tags": [ 288 | "User" 289 | ], 290 | "responses": { 291 | "200": { 292 | "description": "Profile information for a user", 293 | "schema": { 294 | "$ref": "#/definitions/Profile" 295 | } 296 | }, 297 | "default": { 298 | "description": "Unexpected error", 299 | "schema": { 300 | "$ref": "#/definitions/Error" 301 | } 302 | } 303 | } 304 | } 305 | }, 306 | "/history": { 307 | "get": { 308 | "summary": "User Activity", 309 | "description": "The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary.", 310 | "parameters": [ 311 | { 312 | "name": "offset", 313 | "in": "query", 314 | "type": "integer", 315 | "format": "int32", 316 | "description": "Offset the list of returned results by this amount. Default is zero." 317 | }, 318 | { 319 | "name": "limit", 320 | "in": "query", 321 | "type": "integer", 322 | "format": "int32", 323 | "description": "Number of items to retrieve. Default is 5, maximum is 100." 324 | } 325 | ], 326 | "tags": [ 327 | "User" 328 | ], 329 | "responses": { 330 | "200": { 331 | "description": "History information for the given user", 332 | "schema": { 333 | "$ref": "#/definitions/Activities" 334 | } 335 | }, 336 | "default": { 337 | "description": "Unexpected error", 338 | "schema": { 339 | "$ref": "#/definitions/Error" 340 | } 341 | } 342 | } 343 | } 344 | } 345 | }, 346 | "definitions": { 347 | "Product": { 348 | "properties": { 349 | "product_id": { 350 | "type": "string", 351 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 352 | }, 353 | "description": { 354 | "type": "string", 355 | "description": "Description of product." 356 | }, 357 | "display_name": { 358 | "type": "string", 359 | "description": "Display name of product." 360 | }, 361 | "capacity": { 362 | "type": "string", 363 | "description": "Capacity of product. For example, 4 people." 364 | }, 365 | "image": { 366 | "type": "string", 367 | "description": "Image URL representing the product." 368 | } 369 | } 370 | }, 371 | "PriceEstimate": { 372 | "properties": { 373 | "product_id": { 374 | "type": "string", 375 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" 376 | }, 377 | "currency_code": { 378 | "type": "string", 379 | "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 380 | }, 381 | "display_name": { 382 | "type": "string", 383 | "description": "Display name of product." 384 | }, 385 | "estimate": { 386 | "type": "string", 387 | "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." 388 | }, 389 | "low_estimate": { 390 | "type": "number", 391 | "description": "Lower bound of the estimated price." 392 | }, 393 | "high_estimate": { 394 | "type": "number", 395 | "description": "Upper bound of the estimated price." 396 | }, 397 | "surge_multiplier": { 398 | "type": "number", 399 | "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." 400 | } 401 | } 402 | }, 403 | "Profile": { 404 | "properties": { 405 | "first_name": { 406 | "type": "string", 407 | "description": "First name of the Uber user." 408 | }, 409 | "last_name": { 410 | "type": "string", 411 | "description": "Last name of the Uber user." 412 | }, 413 | "email": { 414 | "type": "string", 415 | "description": "Email address of the Uber user" 416 | }, 417 | "picture": { 418 | "type": "string", 419 | "description": "Image URL of the Uber user." 420 | }, 421 | "promo_code": { 422 | "type": "string", 423 | "description": "Promo code of the Uber user." 424 | } 425 | } 426 | }, 427 | "Activity": { 428 | "properties": { 429 | "uuid": { 430 | "type": "string", 431 | "description": "Unique identifier for the activity" 432 | } 433 | } 434 | }, 435 | "Activities": { 436 | "properties": { 437 | "offset": { 438 | "type": "integer", 439 | "format": "int32", 440 | "description": "Position in pagination." 441 | }, 442 | "limit": { 443 | "type": "integer", 444 | "format": "int32", 445 | "description": "Number of items to retrieve (100 max)." 446 | }, 447 | "count": { 448 | "type": "integer", 449 | "format": "int32", 450 | "description": "Total number of items available." 451 | }, 452 | "history": { 453 | "type": "array", 454 | "items": { 455 | "$ref": "#/definitions/Activity" 456 | } 457 | } 458 | } 459 | } 460 | } 461 | } -------------------------------------------------------------------------------- /tst/resources/swagger/uber.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Uber API", 5 | "description": "Move your app forward with the Uber API", 6 | "version": "1.0.0" 7 | }, 8 | "host": "api.uber.com", 9 | "schemes": [ 10 | "https" 11 | ], 12 | "basePath": "/v1", 13 | "produces": [ 14 | "application/json" 15 | ], 16 | "paths": { 17 | "/products": { 18 | "get": { 19 | "summary": "Product Types", 20 | "description": "The Products endpoint returns information about the *Uber* products\noffered at a given location. The response includes the display name\nand other details about each product, and lists the products in the\nproper display order.\n", 21 | "parameters": [ 22 | { 23 | "name": "latitude", 24 | "in": "query", 25 | "description": "Latitude component of location.", 26 | "required": true, 27 | "type": "number", 28 | "format": "double" 29 | }, 30 | { 31 | "name": "longitude", 32 | "in": "query", 33 | "description": "Longitude component of location.", 34 | "required": true, 35 | "type": "number", 36 | "format": "double" 37 | } 38 | ], 39 | "tags": [ 40 | "Products" 41 | ], 42 | "responses": { 43 | "200": { 44 | "description": "An array of products", 45 | "schema": { 46 | "type": "array", 47 | "items": { 48 | "$ref": "#/definitions/Product" 49 | } 50 | } 51 | }, 52 | "default": { 53 | "description": "Unexpected error", 54 | "schema": { 55 | "$ref": "#/definitions/Error" 56 | } 57 | } 58 | } 59 | } 60 | }, 61 | "/estimates/price": { 62 | "get": { 63 | "summary": "Price Estimates", 64 | "description": "The Price Estimates endpoint returns an estimated price range\nfor each product offered at a given location. The price estimate is\nprovided as a formatted string with the full price range and the localized\ncurrency symbol.

The response also includes low and high estimates,\nand the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for\nsituations requiring currency conversion. When surge is active for a particular\nproduct, its surge_multiplier will be greater than 1, but the price estimate\nalready factors in this multiplier.\n", 65 | "parameters": [ 66 | { 67 | "name": "start_latitude", 68 | "in": "query", 69 | "description": "Latitude component of start location.", 70 | "required": true, 71 | "type": "number", 72 | "format": "double" 73 | }, 74 | { 75 | "name": "start_longitude", 76 | "in": "query", 77 | "description": "Longitude component of start location.", 78 | "required": true, 79 | "type": "number", 80 | "format": "double" 81 | }, 82 | { 83 | "name": "end_latitude", 84 | "in": "query", 85 | "description": "Latitude component of end location.", 86 | "required": true, 87 | "type": "number", 88 | "format": "double" 89 | }, 90 | { 91 | "name": "end_longitude", 92 | "in": "query", 93 | "description": "Longitude component of end location.", 94 | "required": true, 95 | "type": "number", 96 | "format": "double" 97 | } 98 | ], 99 | "tags": [ 100 | "Estimates" 101 | ], 102 | "responses": { 103 | "200": { 104 | "description": "An array of price estimates by product", 105 | "schema": { 106 | "type": "array", 107 | "items": { 108 | "$ref": "#/definitions/PriceEstimate" 109 | } 110 | } 111 | }, 112 | "default": { 113 | "description": "Unexpected error", 114 | "schema": { 115 | "$ref": "#/definitions/Error" 116 | } 117 | } 118 | } 119 | } 120 | }, 121 | "/estimates/time": { 122 | "get": { 123 | "summary": "Time Estimates", 124 | "description": "The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs.", 125 | "parameters": [ 126 | { 127 | "name": "start_latitude", 128 | "in": "query", 129 | "description": "Latitude component of start location.", 130 | "required": true, 131 | "type": "number", 132 | "format": "double" 133 | }, 134 | { 135 | "name": "start_longitude", 136 | "in": "query", 137 | "description": "Longitude component of start location.", 138 | "required": true, 139 | "type": "number", 140 | "format": "double" 141 | }, 142 | { 143 | "name": "customer_uuid", 144 | "in": "query", 145 | "type": "string", 146 | "format": "uuid", 147 | "description": "Unique customer identifier to be used for experience customization." 148 | }, 149 | { 150 | "name": "product_id", 151 | "in": "query", 152 | "type": "string", 153 | "description": "Unique identifier representing a specific product for a given latitude & longitude." 154 | } 155 | ], 156 | "tags": [ 157 | "Estimates" 158 | ], 159 | "responses": { 160 | "200": { 161 | "description": "An array of products", 162 | "schema": { 163 | "type": "array", 164 | "items": { 165 | "$ref": "#/definitions/Product" 166 | } 167 | } 168 | }, 169 | "default": { 170 | "description": "Unexpected error", 171 | "schema": { 172 | "$ref": "#/definitions/Error" 173 | } 174 | } 175 | } 176 | } 177 | }, 178 | "/me": { 179 | "get": { 180 | "summary": "User Profile", 181 | "description": "The User Profile endpoint returns information about the Uber user that has authorized with the application.", 182 | "tags": [ 183 | "User" 184 | ], 185 | "responses": { 186 | "200": { 187 | "description": "Profile information for a user", 188 | "schema": { 189 | "$ref": "#/definitions/Profile" 190 | } 191 | }, 192 | "default": { 193 | "description": "Unexpected error", 194 | "schema": { 195 | "$ref": "#/definitions/Error" 196 | } 197 | } 198 | } 199 | } 200 | }, 201 | "/history": { 202 | "get": { 203 | "summary": "User Activity", 204 | "description": "The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary.", 205 | "parameters": [ 206 | { 207 | "name": "offset", 208 | "in": "query", 209 | "type": "integer", 210 | "format": "int32", 211 | "description": "Offset the list of returned results by this amount. Default is zero." 212 | }, 213 | { 214 | "name": "limit", 215 | "in": "query", 216 | "type": "integer", 217 | "format": "int32", 218 | "description": "Number of items to retrieve. Default is 5, maximum is 100." 219 | } 220 | ], 221 | "tags": [ 222 | "User" 223 | ], 224 | "responses": { 225 | "200": { 226 | "description": "History information for the given user", 227 | "schema": { 228 | "$ref": "#/definitions/Activities" 229 | } 230 | }, 231 | "default": { 232 | "description": "Unexpected error", 233 | "schema": { 234 | "$ref": "#/definitions/Error" 235 | } 236 | } 237 | } 238 | } 239 | } 240 | }, 241 | "definitions": { 242 | "Product": { 243 | "properties": { 244 | "product_id": { 245 | "type": "string", 246 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 247 | }, 248 | "description": { 249 | "type": "string", 250 | "description": "Description of product." 251 | }, 252 | "display_name": { 253 | "type": "string", 254 | "description": "Display name of product." 255 | }, 256 | "capacity": { 257 | "type": "string", 258 | "description": "Capacity of product. For example, 4 people." 259 | }, 260 | "image": { 261 | "type": "string", 262 | "description": "Image URL representing the product." 263 | } 264 | } 265 | }, 266 | "PriceEstimate": { 267 | "properties": { 268 | "product_id": { 269 | "type": "string", 270 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" 271 | }, 272 | "currency_code": { 273 | "type": "string", 274 | "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 275 | }, 276 | "display_name": { 277 | "type": "string", 278 | "description": "Display name of product." 279 | }, 280 | "estimate": { 281 | "type": "string", 282 | "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." 283 | }, 284 | "low_estimate": { 285 | "type": "number", 286 | "description": "Lower bound of the estimated price." 287 | }, 288 | "high_estimate": { 289 | "type": "number", 290 | "description": "Upper bound of the estimated price." 291 | }, 292 | "surge_multiplier": { 293 | "type": "number", 294 | "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." 295 | } 296 | } 297 | }, 298 | "Profile": { 299 | "properties": { 300 | "first_name": { 301 | "type": "string", 302 | "description": "First name of the Uber user." 303 | }, 304 | "last_name": { 305 | "type": "string", 306 | "description": "Last name of the Uber user." 307 | }, 308 | "email": { 309 | "type": "string", 310 | "description": "Email address of the Uber user" 311 | }, 312 | "picture": { 313 | "type": "string", 314 | "description": "Image URL of the Uber user." 315 | }, 316 | "promo_code": { 317 | "type": "string", 318 | "description": "Promo code of the Uber user." 319 | } 320 | } 321 | }, 322 | "Activity": { 323 | "properties": { 324 | "uuid": { 325 | "type": "string", 326 | "description": "Unique identifier for the activity" 327 | } 328 | } 329 | }, 330 | "Activities": { 331 | "properties": { 332 | "offset": { 333 | "type": "integer", 334 | "format": "int32", 335 | "description": "Position in pagination." 336 | }, 337 | "limit": { 338 | "type": "integer", 339 | "format": "int32", 340 | "description": "Number of items to retrieve (100 max)." 341 | }, 342 | "count": { 343 | "type": "integer", 344 | "format": "int32", 345 | "description": "Total number of items available." 346 | }, 347 | "history": { 348 | "type": "array", 349 | "items": { 350 | "$ref": "#/definitions/Activity" 351 | } 352 | } 353 | } 354 | }, 355 | "Error": { 356 | "properties": { 357 | "code": { 358 | "type": "integer", 359 | "format": "int32" 360 | }, 361 | "message": { 362 | "type": "string" 363 | }, 364 | "fields": { 365 | "type": "string" 366 | } 367 | } 368 | } 369 | } 370 | } -------------------------------------------------------------------------------- /tst/resources/swagger/uber.yaml: -------------------------------------------------------------------------------- 1 | # this is an example of the Uber API 2 | # as a demonstration of an API spec in YAML 3 | swagger: '2.0' 4 | info: 5 | title: Uber API 6 | description: Move your app forward with the Uber API 7 | version: "1.0.0" 8 | # the domain of the service 9 | host: api.uber.com 10 | # array of all schemes that your API supports 11 | schemes: 12 | - https 13 | # will be prefixed to all paths 14 | basePath: /v1 15 | produces: 16 | - application/json 17 | paths: 18 | /products: 19 | get: 20 | summary: Product Types 21 | description: | 22 | The Products endpoint returns information about the *Uber* products 23 | offered at a given location. The response includes the display name 24 | and other details about each product, and lists the products in the 25 | proper display order. 26 | parameters: 27 | - name: latitude 28 | in: query 29 | description: Latitude component of location. 30 | required: true 31 | type: number 32 | format: double 33 | - name: longitude 34 | in: query 35 | description: Longitude component of location. 36 | required: true 37 | type: number 38 | format: double 39 | tags: 40 | - Products 41 | responses: 42 | 200: 43 | description: An array of products 44 | schema: 45 | type: array 46 | items: 47 | $ref: '#/definitions/Product' 48 | default: 49 | description: Unexpected error 50 | schema: 51 | $ref: '#/definitions/Error' 52 | /estimates/price: 53 | get: 54 | summary: Price Estimates 55 | description: | 56 | The Price Estimates endpoint returns an estimated price range 57 | for each product offered at a given location. The price estimate is 58 | provided as a formatted string with the full price range and the localized 59 | currency symbol.

The response also includes low and high estimates, 60 | and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for 61 | situations requiring currency conversion. When surge is active for a particular 62 | product, its surge_multiplier will be greater than 1, but the price estimate 63 | already factors in this multiplier. 64 | parameters: 65 | - name: start_latitude 66 | in: query 67 | description: Latitude component of start location. 68 | required: true 69 | type: number 70 | format: double 71 | - name: start_longitude 72 | in: query 73 | description: Longitude component of start location. 74 | required: true 75 | type: number 76 | format: double 77 | - name: end_latitude 78 | in: query 79 | description: Latitude component of end location. 80 | required: true 81 | type: number 82 | format: double 83 | - name: end_longitude 84 | in: query 85 | description: Longitude component of end location. 86 | required: true 87 | type: number 88 | format: double 89 | tags: 90 | - Estimates 91 | responses: 92 | 200: 93 | description: An array of price estimates by product 94 | schema: 95 | type: array 96 | items: 97 | $ref: '#/definitions/PriceEstimate' 98 | default: 99 | description: Unexpected error 100 | schema: 101 | $ref: '#/definitions/Error' 102 | /estimates/time: 103 | get: 104 | summary: Time Estimates 105 | description: The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs. 106 | parameters: 107 | - name: start_latitude 108 | in: query 109 | description: Latitude component of start location. 110 | required: true 111 | type: number 112 | format: double 113 | - name: start_longitude 114 | in: query 115 | description: Longitude component of start location. 116 | required: true 117 | type: number 118 | format: double 119 | - name: customer_uuid 120 | in: query 121 | type: string 122 | format: uuid 123 | description: Unique customer identifier to be used for experience customization. 124 | - name: product_id 125 | in: query 126 | type: string 127 | description: Unique identifier representing a specific product for a given latitude & longitude. 128 | tags: 129 | - Estimates 130 | responses: 131 | 200: 132 | description: An array of products 133 | schema: 134 | type: array 135 | items: 136 | $ref: '#/definitions/Product' 137 | default: 138 | description: Unexpected error 139 | schema: 140 | $ref: '#/definitions/Error' 141 | /me: 142 | get: 143 | summary: User Profile 144 | description: The User Profile endpoint returns information about the Uber user that has authorized with the application. 145 | tags: 146 | - User 147 | responses: 148 | 200: 149 | description: Profile information for a user 150 | schema: 151 | $ref: '#/definitions/Profile' 152 | default: 153 | description: Unexpected error 154 | schema: 155 | $ref: '#/definitions/Error' 156 | /history: 157 | get: 158 | summary: User Activity 159 | description: The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary. 160 | parameters: 161 | - name: offset 162 | in: query 163 | type: integer 164 | format: int32 165 | description: Offset the list of returned results by this amount. Default is zero. 166 | - name: limit 167 | in: query 168 | type: integer 169 | format: int32 170 | description: Number of items to retrieve. Default is 5, maximum is 100. 171 | tags: 172 | - User 173 | responses: 174 | 200: 175 | description: History information for the given user 176 | schema: 177 | $ref: '#/definitions/Activities' 178 | default: 179 | description: Unexpected error 180 | schema: 181 | $ref: '#/definitions/Error' 182 | definitions: 183 | Product: 184 | properties: 185 | product_id: 186 | type: string 187 | description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. 188 | description: 189 | type: string 190 | description: Description of product. 191 | display_name: 192 | type: string 193 | description: Display name of product. 194 | capacity: 195 | type: string 196 | description: Capacity of product. For example, 4 people. 197 | image: 198 | type: string 199 | description: Image URL representing the product. 200 | PriceEstimate: 201 | properties: 202 | product_id: 203 | type: string 204 | description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles 205 | currency_code: 206 | type: string 207 | description: "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 208 | display_name: 209 | type: string 210 | description: Display name of product. 211 | estimate: 212 | type: string 213 | description: Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or "Metered" for TAXI. 214 | low_estimate: 215 | type: number 216 | description: Lower bound of the estimated price. 217 | high_estimate: 218 | type: number 219 | description: Upper bound of the estimated price. 220 | surge_multiplier: 221 | type: number 222 | description: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. 223 | Profile: 224 | properties: 225 | first_name: 226 | type: string 227 | description: First name of the Uber user. 228 | last_name: 229 | type: string 230 | description: Last name of the Uber user. 231 | email: 232 | type: string 233 | description: Email address of the Uber user 234 | picture: 235 | type: string 236 | description: Image URL of the Uber user. 237 | promo_code: 238 | type: string 239 | description: Promo code of the Uber user. 240 | Activity: 241 | properties: 242 | uuid: 243 | type: string 244 | description: Unique identifier for the activity 245 | Activities: 246 | properties: 247 | offset: 248 | type: integer 249 | format: int32 250 | description: Position in pagination. 251 | limit: 252 | type: integer 253 | format: int32 254 | description: Number of items to retrieve (100 max). 255 | count: 256 | type: integer 257 | format: int32 258 | description: Total number of items available. 259 | history: 260 | type: array 261 | items: 262 | $ref: '#/definitions/Activity' 263 | Error: 264 | properties: 265 | code: 266 | type: integer 267 | format: int32 268 | message: 269 | type: string 270 | fields: 271 | type: string 272 | --------------------------------------------------------------------------------