|
| 1 | +# OpenAPI Mock API Proxy |
| 2 | +<!-- |
| 3 | + Copyright 2024 Google LLC |
| 4 | +
|
| 5 | + Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | + you may not use this file except in compliance with the License. |
| 7 | + You may obtain a copy of the License at |
| 8 | +
|
| 9 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +
|
| 11 | + Unless required by applicable law or agreed to in writing, software |
| 12 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + See the License for the specific language governing permissions and |
| 15 | + limitations under the License. |
| 16 | +--> |
| 17 | + |
| 18 | +This is a [reference API Proxy implementation](./apiproxy) that lets you easily create a mock API proxy from your OpenAPI 3 specification, allowing you to simulate API behavior without a real backend. |
| 19 | + |
| 20 | +The reference mock API proxy supports the following features. |
| 21 | + |
| 22 | +* [CORS](#cors) (using Apigee [CORS](https://cloud.google.com/apigee/docs/api-platform/reference/policies/cors-policy) policy) |
| 23 | +* [Request Validation](#request-validation) (using Apigee [OASValidation](https://cloud.google.com/apigee/docs/api-platform/reference/policies/oas-validation-policy)) |
| 24 | +* [Dynamic Response Status Code](#dynamic-response-status-code) (using a custom [JavaScript](https://cloud.google.com/apigee/docs/api-platform/reference/policies/javascript-policy) policy) |
| 25 | +* [Dynamic Response Content Type](#dynamic-response-content-type) (using a custom [JavaScript](https://cloud.google.com/apigee/docs/api-platform/reference/policies/javascript-policy) policy) |
| 26 | +* [Dynamic Response Body](#dynamic-response-body) (using a custom [JavaScript](https://cloud.google.com/apigee/docs/api-platform/reference/policies/javascript-policy) policy) |
| 27 | + |
| 28 | + |
| 29 | +## Customizing The Mock API Proxy |
| 30 | + |
| 31 | +This [reference implementation](./apiproxy) provides a solid foundation for building your own mock API proxy. You can customize it by adding your own policies, modifying the existing configuration, and using your own OpenAPI specification. This is a great way to learn about Apigee or to achieve more advanced customizations. |
| 32 | + |
| 33 | +At the very minimum, you have to: |
| 34 | + |
| 35 | +1. Update the `<BasePath>/v3/petstore</BasePath>` element within the [default.xml](./apiproxy/proxies/default.xml) proxy endpoint. |
| 36 | +2. Replace the included sample [spec.json](./apiproxy/resources/oas/spec.json) file with your own OpenAPI 3 spec file. |
| 37 | + |
| 38 | +> The OpenAPI 3 spec file has to be in JSON format. |
| 39 | +> This is so that it can be used by the main JavaScript policy. |
| 40 | +
|
| 41 | + |
| 42 | +Then, you can use the [Apigee CLI](https://github.com/apigee/apigeecli/releases/) tool, and run the following command to deploy the mock API proxy. e.g. |
| 43 | + |
| 44 | +```shell |
| 45 | +APIGEE_ORG="target-apigee-org-name" |
| 46 | +APIGEE_ENV="target-apigee-env" |
| 47 | +TOKEN="$(gcloud auth print-access-token)" |
| 48 | + |
| 49 | +apigeecli apis create bundle \ |
| 50 | + --name my-apiproxy-v1 \ |
| 51 | + --proxy-folder ./apiproxy \ |
| 52 | + --org "${APIGEE_ORG}" \ |
| 53 | + --env "${APIGEE_ENV}" \ |
| 54 | + --ovr \ |
| 55 | + --token "${TOKEN}" \ |
| 56 | + --wait |
| 57 | +``` |
| 58 | + |
| 59 | +**Looking for a faster way?** |
| 60 | + |
| 61 | +If you just need to quickly generate a mock API proxy from your OpenAPI 3 spec, the [apigee-go-gen](https://apigee.github.io/apigee-go-gen/installation/) tool can help. |
| 62 | + |
| 63 | +The tool's [mock oas](https://apigee.github.io/apigee-go-gen/mock/mock-openapi-spec/) command automates the process, saving you time and effort. |
| 64 | + |
| 65 | +Here is an example of how to generate a mock API proxy using the `apigee-go-gen` tool. |
| 66 | + |
| 67 | +```shell |
| 68 | +apigee-go-gen mock oas \ |
| 69 | + --input ./examples/specs/oas3/petstore.yaml \ |
| 70 | + --output ./out/mock-apiproxies/petstore.zip |
| 71 | +``` |
| 72 | +It is that simple. All the information needed to generate the mock API proxy is derived from the input spec itself. |
| 73 | + |
| 74 | +Under the hood, it is using the same reference implementation JavaScript policy from this repo. |
| 75 | + |
| 76 | +Finally, you just as shown before, you would use the [Apigee CLI](https://github.com/apigee/apigeecli/releases/) to deploy the API proxy bundle. |
| 77 | + |
| 78 | +## Mock API Proxy Features |
| 79 | + |
| 80 | +See below for more details on each feature. |
| 81 | + |
| 82 | +### CORS |
| 83 | + |
| 84 | +The reference mock API proxy makes it easy to test your API from various browser-based clients. |
| 85 | + |
| 86 | +Here's how it works: |
| 87 | + |
| 88 | +* **Automatic CORS Headers:** The proxy automatically adds the necessary CORS headers (like `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, etc.) to all responses. |
| 89 | + |
| 90 | +* **Preflight Requests:** The proxy correctly handles preflight `OPTIONS` requests, responding with the appropriate CORS headers to indicate allowed origins, methods, and headers. |
| 91 | + |
| 92 | +* **Permissive Configuration:** By default, the CORS policy is configured to be as permissive as possible, allowing requests from any origin with any HTTP method and headers. This maximizes flexibility for your testing. |
| 93 | + |
| 94 | +The CORS policy ensures that your mock API behaves like a real API in a browser environment, simplifying your development and testing workflow. |
| 95 | + |
| 96 | +### Request Validation |
| 97 | + |
| 98 | +The reference mock API proxy validates the incoming requests against your specification. |
| 99 | +This ensures that the HTTP headers, query parameters, and request body all conform to the defined rules. |
| 100 | + |
| 101 | +This helps you catch errors in your client code early on. |
| 102 | + |
| 103 | +You can disable request validation by passing the header: |
| 104 | + |
| 105 | +``` |
| 106 | +Mock-Validate-Request: false |
| 107 | +``` |
| 108 | + |
| 109 | +### Dynamic Response Status Code |
| 110 | + |
| 111 | +The reference mock API proxy generates different status codes for your mock API responses. Here's how it works: |
| 112 | + |
| 113 | +* **Prioritizes success:** If the [operation](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.1.md#operation-object) allows `HTTP 200` status code, the proxy will use it. |
| 114 | +* **Random selection:** If `HTTP 200` is not allowed for a particular operation, the proxy will pick a random status code from those allowed. |
| 115 | + |
| 116 | +**Want more control?** You can use headers to select response the status code: |
| 117 | + |
| 118 | +* **Specific status code:** Use the `Mock-Status` header in your request and set it to the desired code (e.g., `Mock-Status: 404`). |
| 119 | +* **Random status code:** Use the `Mock-Fuzz: true` header to get a random status code from your spec. |
| 120 | + |
| 121 | +If you use both `Mock-Status` and `Mock-Fuzz`, `Mock-Status` takes precedence. |
| 122 | + |
| 123 | +### Dynamic Response Content-Type |
| 124 | + |
| 125 | +The reference mock API proxy automatically selects the `Content-Type` for responses. Here is how it works: |
| 126 | + |
| 127 | +* **JSON preferred:** If the operation allows `application/json`, the proxy will default to using it. |
| 128 | +* **Random selection:** If `application/json` is not available, the proxy will randomly choose from the media types available for that operation. |
| 129 | + |
| 130 | +**Want more control?** You can use headers to select the response Content-Type: |
| 131 | + |
| 132 | +* **Standard `Accept` header:** You can use the standard `Accept` header in your request to request a specific media type (e.g., `Accept: application/xml`). |
| 133 | +* **Random media type:** Alternatively, use the `Mock-Fuzz: true` header to have the proxy select a random media type the available ones. |
| 134 | + |
| 135 | +If you use both `Accept` and `Mock-Fuzz`, the `Accept` header will take precedence. |
| 136 | + |
| 137 | + |
| 138 | +### Dynamic Response Body |
| 139 | + |
| 140 | +The reference mock API proxy generates realistic response bodies based on your OpenAPI spec. |
| 141 | + |
| 142 | +Here's how it determines what to send back for any particular operation's response (in order): |
| 143 | + |
| 144 | +1. **Prioritizes `example` field:** If the response includes an `example` field, the proxy will use that example. |
| 145 | + |
| 146 | +2. **Handles multiple `examples`:** If the response has an `examples` field with multiple examples, the proxy will randomly select one. You can use the `Mock-Example` header to specify which example you want (e.g., `Mock-Example: my-example`). |
| 147 | + |
| 148 | +3. **Uses schema examples:** If no response examples are provided, but the schema for the response has an `example`, the proxy will use that. |
| 149 | + |
| 150 | +4. **Generates from schema:** As a last resort, the proxy will generate a random example based on the response schema. This works for JSON, YAML, and XML. |
| 151 | + |
| 152 | +You can use the `Mock-Fuzz: true` header to force the proxy to always generate a random example from the schema, even if other static examples are available. |
| 153 | + |
| 154 | + |
| 155 | +### Repeatable API Responses |
| 156 | + |
| 157 | +The reference mock API proxy uses a special technique to make its responses seem random, while still allowing you to get the same response again if needed. Here's how it works: |
| 158 | + |
| 159 | +* **Pseudo-random numbers:** The "random" choices the proxy makes (like status codes and content) are actually generated using a pseudo-random number generator (PRNG). This means the responses look random, but are determined by a starting value called a "seed." |
| 160 | + |
| 161 | +* **Unique seeds:** Each request uses a different seed, so responses vary. However, the seed is provided in a special response header called `Mock-Seed`. |
| 162 | + |
| 163 | +* **Getting the same response:** To get an identical response, simply include the `Mock-Seed` header in a new request, using the value from a previous response. This forces the proxy to use the same seed and generate the same "random" choices, resulting in an identical response. |
| 164 | + |
| 165 | +This feature is super helpful for: |
| 166 | + |
| 167 | +* **Testing:** Ensuring your tests always get the same response. |
| 168 | +* **Debugging:** Easily recreating specific scenarios to pinpoint issues in application code. |
| 169 | + |
| 170 | +Essentially, by using the `Mock-Seed` header, you can control the randomness of the mock API responses, making them repeatable for testing and debugging. |
| 171 | + |
| 172 | +### Example Generation from JSON Schemas |
| 173 | + |
| 174 | +The following fields are supported when generating examples from a JSON schema: |
| 175 | + |
| 176 | +* `$ref` - local references are followed |
| 177 | +* `$oneOf` - chooses a random schema |
| 178 | +* `$anyOf` - chooses a random schema |
| 179 | +* `$allOf` - combines all schemas |
| 180 | +* `object` type |
| 181 | + * `required` field - all required properties are chosen |
| 182 | + * `properties` field - a random set of properties is chosen |
| 183 | + * `additionalProperties` field - only used when there are no `properties` defined |
| 184 | +* `array` type |
| 185 | + * `minItems`, `maxItems` fields - array length chosen randomly between these values |
| 186 | + * `items` field - determines the type of array elements |
| 187 | + * `prefixItems` (not supported yet) |
| 188 | +* `null` type |
| 189 | +* `const` type |
| 190 | +* `boolean` type - true or false randomly chosen |
| 191 | +* `string` type |
| 192 | + * `enum` field - a random value is chosen from the list |
| 193 | + * `pattern` field (not supported yet) |
| 194 | + * `format` field |
| 195 | + * `date-time` format |
| 196 | + * `date` format |
| 197 | + * `time` format |
| 198 | + * `email` format |
| 199 | + * `uuid` format |
| 200 | + * `uri` format |
| 201 | + * `hostname` format |
| 202 | + * `ipv4` format |
| 203 | + * `ipv6` format |
| 204 | + * `duration` format |
| 205 | + * `minLength`, `maxLength` fields - string length chosen randomly between these values |
| 206 | +* `integer` type |
| 207 | + * `minimum`, `maximum` fields - a random integer value chosen randomly between these values |
| 208 | + * `exclusiveMinimuim` field (boolean, JSON-Schema 4) |
| 209 | + * `exclusiveMaximum` field (boolean, JSON-Schema 4) |
| 210 | + * `multipleOf` field |
| 211 | +* `number` type |
| 212 | + * `minimum`, `maximum` fields - a random float value chosen randomly between these values |
| 213 | + * `exclusiveMinimuim` field (boolean, JSON-Schema 4) |
| 214 | + * `exclusiveMaximum` field (boolean, JSON-Schema 4) |
| 215 | + * `multipleOf` field |
0 commit comments