From 388b6a4faadbf63eb8e8d650a8d8e5d8b15ec36d Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:35:24 -0500 Subject: [PATCH 1/2] fix typo, style guide updates * Updated usage of the name Overlay, per the [Overlay style guide](https://github.com/OAI/Overlay-Specification/blob/main/CONTRIBUTING.md#style-guide-for-overlay-specification) * Updated the usage of OpenAPI `Description`, per the [OpenAPI style guide](https://spec.openapis.org/oas/v3.0.4#introduction) * Update example to use preferred keyword `examples` --- cmd/apigee-go-gen/mock/oas/cmd.go | 6 +- cmd/apigee-go-gen/render/apiproxy/cmd.go | 2 +- cmd/apigee-go-gen/render/sharedflow/cmd.go | 2 +- cmd/apigee-go-gen/render/template/cmd.go | 2 +- .../transform/oas-overlay/cmd.go | 6 +- .../transform/oas2-to-oas3/cmd.go | 2 +- docs/index.md | 6 +- docs/mock/commands/mock-oas.md | 6 +- ...pi-spec.md => mock-openapi-description.md} | 65 ++++++++++--------- docs/render/commands/render-apiproxy.md | 10 +-- docs/render/commands/render-sharedflow.md | 2 +- docs/render/commands/render-template.md | 2 +- ...i-spec.md => using-openapi-description.md} | 12 ++-- docs/transform/commands/apiproxy-to-yaml.md | 2 +- docs/transform/commands/oas-overlay.md | 8 +-- docs/transform/commands/oas2-to-oas3.md | 2 +- docs/transform/index.md | 4 +- examples/overlays/petstore-dog-example.yaml | 22 ++++--- examples/templates/oas3/apiproxy.yaml | 6 +- examples/templates/oas3/policies.yaml | 2 +- examples/yaml-first/petstore/apiproxy.yaml | 2 +- examples/yaml-first/petstore/policies.yaml | 2 +- .../mock_apiproxy_template/apiproxy.yaml | 8 +-- .../response-mocker.cjs | 2 +- pkg/common/resources/helper_functions.txt | 8 +-- pkg/common/resources/mock_features.txt | 2 +- pkg/common/resources/render_context.txt | 2 +- pkg/utils/openapi2.go | 2 +- pkg/utils/overlay.go | 10 +-- 29 files changed, 106 insertions(+), 101 deletions(-) rename docs/mock/{mock-openapi-spec.md => mock-openapi-description.md} (79%) rename docs/render/{using-openapi-spec.md => using-openapi-description.md} (88%) diff --git a/cmd/apigee-go-gen/mock/oas/cmd.go b/cmd/apigee-go-gen/mock/oas/cmd.go index 637c3e4..42383b6 100644 --- a/cmd/apigee-go-gen/mock/oas/cmd.go +++ b/cmd/apigee-go-gen/mock/oas/cmd.go @@ -28,7 +28,7 @@ var debug = flags.NewBool(false) var Cmd = &cobra.Command{ Use: "oas", - Short: "Generate a mock API proxy from an OpenAPI 3.X spec", + Short: "Generate a mock API proxy from an OpenAPI 3.X Description", Long: Usage(), RunE: func(cmd *cobra.Command, args []string) error { return mock.GenerateMockProxyBundle(string(input), string(output), bool(debug)) @@ -37,7 +37,7 @@ var Cmd = &cobra.Command{ func init() { Cmd.Flags().SortFlags = false - Cmd.Flags().VarP(&input, "input", "i", `path to OpenAPI spec (e.g. "./path/to/spec.yaml")`) + Cmd.Flags().VarP(&input, "input", "i", `path to OpenAPI Description (e.g. "./path/to/openapi.yaml")`) Cmd.Flags().VarP(&output, "output", "o", `output directory or zip file (e.g. "./path/to/apiproxy.zip")`) Cmd.Flags().VarP(&debug, "debug", "", `prints rendered template before creating API proxy bundle"`) @@ -49,7 +49,7 @@ func init() { func Usage() string { usageText := ` -This command generates a mock API proxy bundle from an OpenAPI 3.X Spec. +This command generates a mock API proxy bundle from an OpenAPI 3.X Description. The mock API proxy includes the following features: diff --git a/cmd/apigee-go-gen/render/apiproxy/cmd.go b/cmd/apigee-go-gen/render/apiproxy/cmd.go index 43e3e86..3f13132 100644 --- a/cmd/apigee-go-gen/render/apiproxy/cmd.go +++ b/cmd/apigee-go-gen/render/apiproxy/cmd.go @@ -67,7 +67,7 @@ func init() { Cmd.Flags().Var(&setValueStr, "set-string", `sets key=value (string), e.g. "base_path=/v1/hello" `) Cmd.Flags().Var(&setValueFile, "values", `sets keys/values from YAML file, e.g. "./values.yaml"`) Cmd.Flags().Var(&setFile, "set-file", `sets key=value where value is the content of a file, e.g. "my_data=./from/file.txt"`) - Cmd.Flags().Var(&setOAS, "set-oas", `sets key=value where value is an OpenAPI spec, e.g. "my_spec=./petstore.yaml"`) + Cmd.Flags().Var(&setOAS, "set-oas", `sets key=value where value is an OpenAPI Description, e.g. "my_spec=./petstore.yaml"`) Cmd.Flags().Var(&setGRPC, "set-grpc", `sets key=value where value is a gRPC proto, e.g. "my_proto=./greeter.proto"`) Cmd.Flags().Var(&setGraphQL, "set-graphql", `sets key=value where value is a GraphQL schema, e.g. "my_schema=./resorts.graphql"`) Cmd.Flags().Var(&setJSON, "set-json", `sets key=value where value is JSON, e.g. 'servers=["server1","server2"]'`) diff --git a/cmd/apigee-go-gen/render/sharedflow/cmd.go b/cmd/apigee-go-gen/render/sharedflow/cmd.go index 28fb83b..59c4975 100644 --- a/cmd/apigee-go-gen/render/sharedflow/cmd.go +++ b/cmd/apigee-go-gen/render/sharedflow/cmd.go @@ -67,7 +67,7 @@ func init() { Cmd.Flags().Var(&setValueStr, "set-string", `sets key=value (string), e.g. "base_path=/v1/hello" `) Cmd.Flags().Var(&setValueFile, "values", `sets keys/values from YAML file, e.g. "./values.yaml"`) Cmd.Flags().Var(&setFile, "set-file", `sets key=value where value is the content of a file, e.g. "my_data=./from/file.txt"`) - Cmd.Flags().Var(&setOAS, "set-oas", `sets key=value where value is an OpenAPI spec, e.g. "my_spec=./petstore.yaml"`) + Cmd.Flags().Var(&setOAS, "set-oas", `sets key=value where value is an OpenAPI Description, e.g. "my_spec=./petstore.yaml"`) Cmd.Flags().Var(&setGRPC, "set-grpc", `sets key=value where value is a gRPC proto, e.g. "my_proto=./greeter.proto"`) Cmd.Flags().Var(&setGraphQL, "set-graphql", `sets key=value where value is a GraphQL schema, e.g. "my_schema=./resorts.graphql"`) Cmd.Flags().Var(&setJSON, "set-json", `sets key=value where value is JSON, e.g. 'servers=["server1","server2"]'`) diff --git a/cmd/apigee-go-gen/render/template/cmd.go b/cmd/apigee-go-gen/render/template/cmd.go index 403f285..d6383dd 100644 --- a/cmd/apigee-go-gen/render/template/cmd.go +++ b/cmd/apigee-go-gen/render/template/cmd.go @@ -57,7 +57,7 @@ func init() { Cmd.Flags().Var(&setValueStr, "set-string", `sets key=value (string), e.g. "base_path=/v1/hello" `) Cmd.Flags().Var(&setValueFile, "values", `sets keys/values from YAML file, e.g. "./values.yaml"`) Cmd.Flags().Var(&setFile, "set-file", `sets key=value where value is the content of a file, e.g. "my_data=./from/file.txt"`) - Cmd.Flags().Var(&setOAS, "set-oas", `sets key=value where value is an OpenAPI spec, e.g. "my_spec=./petstore.yaml"`) + Cmd.Flags().Var(&setOAS, "set-oas", `sets key=value where value is an OpenAPI Description, e.g. "my_spec=./petstore.yaml"`) Cmd.Flags().Var(&setGRPC, "set-grpc", `sets key=value where value is a gRPC proto, e.g. "my_proto=./greeter.proto"`) Cmd.Flags().Var(&setGraphQL, "set-graphql", `sets key=value where value is a GraphQL schema, e.g. "my_schema=./resorts.graphql"`) Cmd.Flags().Var(&setJSON, "set-json", `sets key=value where value is JSON, e.g. 'servers=["server1","server2"]'`) diff --git a/cmd/apigee-go-gen/transform/oas-overlay/cmd.go b/cmd/apigee-go-gen/transform/oas-overlay/cmd.go index cb6b2ac..87ebe9d 100644 --- a/cmd/apigee-go-gen/transform/oas-overlay/cmd.go +++ b/cmd/apigee-go-gen/transform/oas-overlay/cmd.go @@ -26,7 +26,7 @@ var output flags.String var Cmd = &cobra.Command{ Use: "oas-overlay", - Short: "Transforms Open API spec by applying overlay file", + Short: "Transforms OpenAPI Description by applying Overlay file", RunE: func(cmd *cobra.Command, args []string) error { return utils.OASOverlay(string(overlay), string(spec), string(output)) }, @@ -35,8 +35,8 @@ var Cmd = &cobra.Command{ func init() { Cmd.Flags().SortFlags = false - Cmd.Flags().VarP(&spec, "spec", "s", "path to OpenAPI spec file (optional)") - Cmd.Flags().VarP(&overlay, "overlay", "", "path to overlay file") + Cmd.Flags().VarP(&spec, "spec", "s", "path to OpenAPI Description file (optional)") + Cmd.Flags().VarP(&overlay, "overlay", "", "path to Overlay file") Cmd.Flags().VarP(&output, "output", "o", "path to output file, or omit to use stdout") _ = Cmd.MarkFlagRequired("overlay") diff --git a/cmd/apigee-go-gen/transform/oas2-to-oas3/cmd.go b/cmd/apigee-go-gen/transform/oas2-to-oas3/cmd.go index bbb686c..dcee2b8 100644 --- a/cmd/apigee-go-gen/transform/oas2-to-oas3/cmd.go +++ b/cmd/apigee-go-gen/transform/oas2-to-oas3/cmd.go @@ -26,7 +26,7 @@ var allowCycles = flags.NewBool(false) var Cmd = &cobra.Command{ Use: "oas2-to-oas3", - Short: "Transforms the input OpenAPI 2 spec into OpenAPI 3 spec", + Short: "Transforms the input OpenAPI 2 Description into an OpenAPI 3 Description", RunE: func(cmd *cobra.Command, args []string) error { return utils.OAS2FileToOAS3File(string(input), string(output), bool(allowCycles)) }, diff --git a/docs/index.md b/docs/index.md index 645aab7..d19669e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,7 +25,7 @@ The `apigee-go-gen` CLI tool streamlines your Apigee development experience usin * **[Transformation commands](./transform/index.md)** Easily convert between Apigee's API proxy format and YAML for better readability and management. * **[Template rendering commands](./render/index.md)** Enjoy powerful customization and dynamic configuration options, inspired by the flexibility of Helm using the Go [text/template](https://pkg.go.dev/text/template) engine. -* **[Mock generation command](./mock/mock-openapi-spec.md)** Effortlessly create a mock API proxy from your OpenAPI 3.X spec, complete with dynamic response bodies, headers, and status codes. +* **[Mock generation command](./mock/mock-openapi-description.md)** Effortlessly create a mock API proxy from your OpenAPI 3.X Description, complete with dynamic response bodies, headers, and status codes. By using this tool alongside the [Apigee CLI](https://github.com/apigee/apigeecli), you'll unlock a highly customizable workflow. This is perfect for both streamlined local development and robust CI/CD pipelines. @@ -59,8 +59,8 @@ This approach has the potential to address the current challenges, offering: - [x] **Faster time to production** > Leverage Apigee community templates to save time and resources. -- [x] **Stay in sync with API specs** -> Auto-update templated API Proxies to stay in sync with the spec, while preserving customizations. +- [x] **Stay in sync with API descriptions** +> Auto-update templated API Proxies to stay in sync with the api description, while preserving customizations. ## Support diff --git a/docs/mock/commands/mock-oas.md b/docs/mock/commands/mock-oas.md index 7183b46..37d332d 100644 --- a/docs/mock/commands/mock-oas.md +++ b/docs/mock/commands/mock-oas.md @@ -15,17 +15,17 @@ limitations under the License. --> -This command generates a mock API proxy bundle from your OpenAPI 3 specification. +This command generates a mock API proxy bundle from your OpenAPI 3 Description. ## Usage The `mock oas` command takes the following parameters: ```text - -i, --input string path to OpenAPI spec (e.g. "./path/to/spec.yaml") + -i, --input string path to OpenAPI Description (e.g. "./path/to/openapi.yaml") -o, --output string output directory or zip file (e.g. "./path/to/apiproxy.zip") -h, --help help for oas ``` -> See how the mock API proxy bundle works over at the [Mock OpenAPI Spec](../mock-openapi-spec.md) doc page +> See how the mock API proxy bundle works over at the [Mock OpenAPI Description](../mock-openapi-description.md) doc page diff --git a/docs/mock/mock-openapi-spec.md b/docs/mock/mock-openapi-description.md similarity index 79% rename from docs/mock/mock-openapi-spec.md rename to docs/mock/mock-openapi-description.md index fc0f488..25c675b 100644 --- a/docs/mock/mock-openapi-spec.md +++ b/docs/mock/mock-openapi-description.md @@ -1,4 +1,4 @@ -# Mock OpenAPI Spec +# Mock OpenAPI Description -You can use the [mock oas](./commands/mock-oas.md) command to create a mock API proxy from your OpenAPI 3 specification, allowing you to simulate API behavior without a real backend. +You can use the [mock oas](./commands/mock-oas.md) command to create a mock API proxy from your OpenAPI 3 Description, allowing you to simulate API behavior without a real backend. ## Examples @@ -42,9 +42,9 @@ apigee-go-gen mock oas \ The generated mock API proxy supports the following features. -### :white_check_mark: Base Path from Spec +### :white_check_mark: Base Path from OAS Description -The `Base Path` for the mock API proxy is derived from the first element of the [servers](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.1.md#oas-servers) array in your OpenAPI spec. +The `Base Path` for the mock API proxy is derived from the first element of the [servers](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.1.md#oas-servers) array in your OpenAPI Description. For example, if your server array looks like this: @@ -73,7 +73,7 @@ This built-in CORS support ensures that your mock API behaves like a real API in ### :white_check_mark: Request Validation -By default, the mock API proxy validates the incoming requests against your specification. +By default, the mock API proxy validates the incoming requests against your description. This ensures that the HTTP headers, query parameters, and request body all conform to the defined rules. This helps you catch errors in your client code early on. @@ -95,7 +95,7 @@ The mock API proxy automatically generates different status codes for your mock **Want more control?** You can use headers to select response the status code: * **Specific status code:** Use the `Mock-Status` header in your request and set it to the desired code (e.g., `Mock-Status: 404`). -* **Random status code:** Use the `Mock-Fuzz: true` header to get a random status code from your spec. +* **Random status code:** Use the `Mock-Fuzz: true` header to get a random status code from your description. If you use both `Mock-Status` and `Mock-Fuzz`, `Mock-Status` takes precedence. @@ -116,7 +116,7 @@ If you use both `Accept` and `Mock-Fuzz`, the `Accept` header will take preceden ### :white_check_mark: Dynamic Response Body -The mock API proxy can generate realistic response bodies based on your OpenAPI spec. +The mock API proxy can generate realistic response bodies based on your OpenAPI Description. Here's how it determines what to send back for any particular operation's response (in order): @@ -184,29 +184,29 @@ The following fields are supported when generating examples from a JSON schema: * `minLength`, `maxLength` fields - string length chosen randomly between these values * `integer` type * `minimum`, `maximum` fields - a random integer value chosen randomly between these values - * `exclusiveMinimuim` field (boolean, JSON-Schema 4) - * `exclusiveMaximum` field (boolean, JSON-Schema 4) + * `exclusiveMinimum` field (boolean, JSON-Schema 4, number, JSON Schema 7 or greater) + * `exclusiveMaximum` field (boolean, JSON-Schema 4, number, JSON Schema 7 or greater) * `multipleOf` field * `number` type * `minimum`, `maximum` fields - a random float value chosen randomly between these values - * `exclusiveMinimuim` field (boolean, JSON-Schema 4) - * `exclusiveMaximum` field (boolean, JSON-Schema 4) + * `exclusiveMinimum` field (boolean, JSON-Schema 4, number, JSON Schema 7 or greater) + * `exclusiveMaximum` field (boolean, JSON-Schema 4, number, JSON Schema 7 or greater) * `multipleOf` field Markdown -## Enriching your OpenAPI Spec with Examples +## Enriching your OpenAPI Description with Examples -Sometimes, your OpenAPI specification might be missing response examples or schemas. In other cases, the examples might be very large and difficult to include directly in the spec. Overlays provide a solution for these situations. +Sometimes, your OpenAPI Description might be missing response examples or schemas. In other cases, the examples might be very large and difficult to include directly in the description. Overlays provide a solution for these situations. -**What is an overlay?** +**What is an Overlay?** -An overlay is a separate file that allows you to add or modify information in your existing OpenAPI spec. This is useful for adding examples, schemas, or any other data that you want to keep separate from your main specification file. To learn more about how overlays work, you can refer to the [overlay specification](https://spec.openapis.org/overlay/latest.html). +An Overlay is a separate file that allows you to add or modify information in your existing OpenAPI Description. This is useful for adding examples, schemas, or any other data that you want to keep separate from your main description file. To learn more about how Overlays work, you can refer to the [Overlay Specification](https://spec.openapis.org/overlay/latest.html). -**How to use an overlay** +**How to use an Overlay** -Here's how you can use an overlay to add a static example to an API operation: +Here's how you can use an Overlay to add a static example to an API operation: -1. **Create an overlay file:** This file defines the changes you want to make to your OpenAPI spec. Here's an example that adds a sample response for the `/pet/findByStatus` operation: +1. **Create an Overlay file:** This file defines the changes you want to make to your OpenAPI Description. Here's an example that adds a sample response for the `/pet/findByStatus` operation: ```yaml overlay: 1.0.0 @@ -218,20 +218,23 @@ Here's how you can use an overlay to add a static example to an API operation: update: content: 'application/json': - example: - { - "id": 1, - "photoUrls": [], - "name": "Rin Tin Tin", - "category": { - "id": 1, - "name": "Dog" - } - } + examples: + findByStatus: + value: + { + "id": 1, + "photoUrls": [], + "name": "Rin Tin Tin", + "category": { + "id": 1, + "name": "Dog" + } + } + ``` -2. **Apply the overlay to your OpenAPI spec:** Use the `apigee-go-gen` tool to combine your overlay file with your OpenAPI spec: +2. **Apply the Overlay to your OpenAPI Description:** Use the `apigee-go-gen` tool to combine your Overlay file with your OpenAPI Description: ```bash apigee-go-gen transform oas-overlay \ @@ -240,7 +243,7 @@ Here's how you can use an overlay to add a static example to an API operation: --output ./out/specs/petstore-overlaid.yaml ``` -3. **Generate a mock API proxy:** You can now use the updated OpenAPI spec to generate a mock API proxy in Apigee: +3. **Generate a mock API proxy:** You can now use the updated OpenAPI Description to generate a mock API proxy in Apigee: ```bash apigee-go-gen mock oas \ @@ -248,4 +251,4 @@ Here's how you can use an overlay to add a static example to an API operation: --output ./out/mock-apiproxies/petstore.zip ``` -This process allows you to manage your OpenAPI spec more effectively by keeping your examples and other supplementary data in separate files. \ No newline at end of file +This process allows you to manage your OpenAPI Description more effectively by keeping your examples and other supplementary data in separate files. \ No newline at end of file diff --git a/docs/render/commands/render-apiproxy.md b/docs/render/commands/render-apiproxy.md index 5a7a8c5..1e47b92 100644 --- a/docs/render/commands/render-apiproxy.md +++ b/docs/render/commands/render-apiproxy.md @@ -21,13 +21,13 @@ Under the hood, this command combines the [render template](./render-template.md Using a template based workflow offers several advantages over working directly with the traditional Apigee API proxy bundle. e.g. -- [x] **Enhanced Customization** +- [x] **Enhanced Customization** > Tweak your API proxy configurations with the readability of YAML. -- [x] **Seamless Spec Synchronization** -> Template-generated API proxy bundles can be easily synced with your specs by re-generating them when changes occur. +- [x] **Seamless OAS Description Synchronization** +> Template-generated API proxy bundles can be easily synced with your descriptions by re-generating them when changes occur. -- [x] **Streamline Your Development** +- [x] **Streamline Your Development** > YAML's versatility allows for easy version control, automation, and integration into CI/CD pipelines. ## Usage @@ -46,7 +46,7 @@ The `render apiproxy` command takes the following parameters: --set-string string sets key=value (string), e.g. "base_path=/v1/hello" --values string sets keys/values from YAML file, e.g. "./values.yaml" --set-file string sets key=value where value is the content of a file, e.g. "my_data=./from/file.txt" - --set-oas string sets key=value where value is an OpenAPI spec, e.g. "my_spec=./petstore.yaml" + --set-oas string sets key=value where value is an OpenAPI Description, e.g. "my_spec=./petstore.yaml" --set-grpc string sets key=value where value is a gRPC proto, e.g. "my_proto=./greeter.proto" --set-graphql string sets key=value where value is a GraphQL schema, e.g. "my_schema=./resorts.graphql" --set-json string sets key=value where value is JSON, e.g. 'servers=["server1","server2"]' diff --git a/docs/render/commands/render-sharedflow.md b/docs/render/commands/render-sharedflow.md index 7f35289..e454301 100644 --- a/docs/render/commands/render-sharedflow.md +++ b/docs/render/commands/render-sharedflow.md @@ -38,7 +38,7 @@ The `render sharedflow` command takes the following parameters: --set-string string sets key=value (string), e.g. "base_path=/v1/hello" --values string sets keys/values from YAML file, e.g. "./values.yaml" --set-file string sets key=value where value is the content of a file, e.g. "my_data=./from/file.txt" - --set-oas string sets key=value where value is an OpenAPI spec, e.g. "my_spec=./petstore.yaml" + --set-oas string sets key=value where value is an OpenAPI Description, e.g. "my_spec=./petstore.yaml" --set-grpc string sets key=value where value is a gRPC proto, e.g. "my_proto=./greeter.proto" --set-graphql string sets key=value where value is a GraphQL schema, e.g. "my_schema=./resorts.graphql" --set-json string sets key=value where value is JSON, e.g. 'servers=["server1","server2"]' diff --git a/docs/render/commands/render-template.md b/docs/render/commands/render-template.md index 5cad4c1..e45d1a9 100644 --- a/docs/render/commands/render-template.md +++ b/docs/render/commands/render-template.md @@ -35,7 +35,7 @@ The `render template` command takes the following parameters: --set-string string sets key=value (string), e.g. "base_path=/v1/hello" --values string sets keys/values from YAML file, e.g. "./values.yaml" --set-file string sets key=value where value is the content of a file, e.g. "my_data=./from/file.txt" - --set-oas string sets key=value where value is an OpenAPI spec, e.g. "my_spec=./petstore.yaml" + --set-oas string sets key=value where value is an OpenAPI Description, e.g. "my_spec=./petstore.yaml" --set-grpc string sets key=value where value is a gRPC proto, e.g. "my_proto=./greeter.proto" --set-graphql string sets key=value where value is a GraphQL schema, e.g. "my_schema=./resorts.graphql" --set-json string sets key=value where value is JSON, e.g. 'servers=["server1","server2"]' diff --git a/docs/render/using-openapi-spec.md b/docs/render/using-openapi-description.md similarity index 88% rename from docs/render/using-openapi-spec.md rename to docs/render/using-openapi-description.md index 6cdeaba..a7d9596 100644 --- a/docs/render/using-openapi-spec.md +++ b/docs/render/using-openapi-description.md @@ -1,4 +1,4 @@ -# Using OpenAPI Spec +# Using OpenAPI Description -You can use the [render apiproxy](./commands/render-apiproxy.md) command to create an Apigee API proxy bundle using a template and an [OpenAPI spec](https://www.openapis.org/) as input. +You can use the [render apiproxy](./commands/render-apiproxy.md) command to create an Apigee API proxy bundle using a template and an [OpenAPI Description](https://www.openapis.org/) as input. ## How it works - [x] **Start with Your Template** > This is your baseline. Include any standard policies or settings you want in your final proxy. - [x] **Customize the Output** -> Your template uses special placeholders that are replaced with details from your OpenAPI spec. +> Your template uses special placeholders that are replaced with details from your OpenAPI Description. - [x] **Control the Output** -> Use control logic in your template to adjust your proxy configuration based on your OpenAPI spec. -- [x] **Access the Spec** -> Use `--set-oas` to access the OpenAPI as a [map](https://go.dev/blog/maps) (and as text) during template rendering. +> Use control logic in your template to adjust your proxy configuration based on your OpenAPI Description. +- [x] **Access the OAS Description** +> Use `--set-oas` to access the OpenAPI Description as a [map](https://go.dev/blog/maps) (and as text) during template rendering. !!! Note Both OAS2 and OAS3 are supported using the `--set-oas` flag diff --git a/docs/transform/commands/apiproxy-to-yaml.md b/docs/transform/commands/apiproxy-to-yaml.md index 0c7fc54..fa0085c 100644 --- a/docs/transform/commands/apiproxy-to-yaml.md +++ b/docs/transform/commands/apiproxy-to-yaml.md @@ -98,7 +98,7 @@ Below is the general structure of a bundle ./apiproxy/resources/java/ ./apiproxy/resources/java/library.jar -# Optional OpenAPI spec files (for use with the OAS policy) +# Optional OpenAPI Description files (for use with the OAS policy) ./apiproxy/resources/oas ./apiproxy/resources/oas/openapi.yaml diff --git a/docs/transform/commands/oas-overlay.md b/docs/transform/commands/oas-overlay.md index 67c5831..8e41d2f 100644 --- a/docs/transform/commands/oas-overlay.md +++ b/docs/transform/commands/oas-overlay.md @@ -15,7 +15,7 @@ limitations under the License. --> -This command applies an [OpenAPI Overlay 1.0](https://github.com/OAI/Overlay-Specification/blob/main/versions/1.0.0.md) to an OpenAPI spec. +This command applies an [OpenAPI Overlay 1.0](https://github.com/OAI/Overlay-Specification/blob/main/versions/1.0.0.md) to an OpenAPI Description. ## Usage @@ -23,13 +23,13 @@ The `oas-overlay` command takes the following parameters: * `--overlay` is the path to the OpenAPI Overlay (either as JSON or YAML) -* `--spec` (*optional*) is the path to the OpenAPI Spec to transform (either as JSON or YAML) +* `--spec` (*optional*) is the path to the OpenAPI Description to transform (either as JSON or YAML) * `--output` is the document to be created (either as JSON or YAML) -> The `--spec` parameter is optional. If omitted, the OAS path is read form the `extends` property of the overlay. -> In this case, the path is relative to the location of the overlay file itself. +> The `--spec` parameter is optional. If omitted, the OAS path is read from the `extends` property of the Overlay. +> In this case, the path is relative to the location of the Overlay file itself. diff --git a/docs/transform/commands/oas2-to-oas3.md b/docs/transform/commands/oas2-to-oas3.md index b572ef3..9009251 100644 --- a/docs/transform/commands/oas2-to-oas3.md +++ b/docs/transform/commands/oas2-to-oas3.md @@ -15,7 +15,7 @@ limitations under the License. --> -This command takes an OpenAPI 2 spec (also known as Swagger) and converts it into an OpenAPI 3 spec. +This command takes an OpenAPI 2 Description (also known as Swagger) and converts it into an OpenAPI 3 Description. ## Usage diff --git a/docs/transform/index.md b/docs/transform/index.md index 5e7f9e0..7f7abc5 100644 --- a/docs/transform/index.md +++ b/docs/transform/index.md @@ -32,10 +32,10 @@ The `apigee-go-gen` includes the following set of `transform` commands to help y * [sharedflow-to-yaml](./commands/sharedflow-to-yaml.md) - Transforms an Apigee API shared flow bundle to a YAML doc * [yaml-to-sharedflow](./commands/yaml-to-sharedflow.md) - Transforms a YAML doc to an Apigee shared flow bundle -* [oas2-to-oas3](./commands/oas2-to-oas3.md) - Transforms an OpenAPI 2 spec (also known as Swagger) into OpenAPI 3 +* [oas2-to-oas3](./commands/oas2-to-oas3.md) - Transforms an OpenAPI 2 Description (also known as Swagger) into OpenAPI 3 * [resolve-refs](./commands/resolve-refs.md) - Replace external $refs (JSONRefs) in a YAML or JSON doc with actual values -* [oas-overlay](./commands/oas-overlay.md) - Transforms an Open API spec by applying an [overlay](https://github.com/OAI/Overlay-Specification/blob/main/versions/1.0.0.md) file +* [oas-overlay](./commands/oas-overlay.md) - Transforms an OpenAPI Description by applying an [Overlay](https://github.com/OAI/Overlay-Specification/blob/main/versions/1.0.0.md) file diff --git a/examples/overlays/petstore-dog-example.yaml b/examples/overlays/petstore-dog-example.yaml index a1501a5..690120e 100644 --- a/examples/overlays/petstore-dog-example.yaml +++ b/examples/overlays/petstore-dog-example.yaml @@ -7,13 +7,15 @@ actions: update: content: 'application/json': - example: - { - "id": 1, - "photoUrls": [], - "name": "Rin Tin Tin", - "category": { - "id": 1, - "name": "Dog" - } - } \ No newline at end of file + examples: + findByStatus: + value: + { + "id": 1, + "photoUrls": [], + "name": "Rin Tin Tin", + "category": { + "id": 1, + "name": "Dog" + } + } diff --git a/examples/templates/oas3/apiproxy.yaml b/examples/templates/oas3/apiproxy.yaml index 2c3980a..1b62595 100755 --- a/examples/templates/oas3/apiproxy.yaml +++ b/examples/templates/oas3/apiproxy.yaml @@ -76,9 +76,9 @@ TargetEndpoints: Resources: - Resource: Type: oas - #{{ os_writefile "./spec.yaml" $.Values.spec_string }} - #{{ remove_oas_extensions "./spec.yaml" }} - Path: ./spec.yaml + #{{ os_writefile "./openapi.yaml" $.Values.spec_string }} + #{{ remove_oas_extensions "./openapi.yaml" }} + Path: ./openapi.yaml - Resource: Type: properties #{{ os_copyfile "./test.properties" "./resources/test.properties" }} diff --git a/examples/templates/oas3/policies.yaml b/examples/templates/oas3/policies.yaml index e56a317..829b382 100644 --- a/examples/templates/oas3/policies.yaml +++ b/examples/templates/oas3/policies.yaml @@ -18,7 +18,7 @@ .name: OAS-Validate DisplayName: OAS-Validate Source: request - OASResource: oas://spec.yaml + OASResource: oas://openapi.yaml - RaiseFault: .continueOnError: false .enabled: true diff --git a/examples/yaml-first/petstore/apiproxy.yaml b/examples/yaml-first/petstore/apiproxy.yaml index c221eba..110c7ad 100755 --- a/examples/yaml-first/petstore/apiproxy.yaml +++ b/examples/yaml-first/petstore/apiproxy.yaml @@ -51,7 +51,7 @@ TargetEndpoints: Resources: - Resource: Type: oas - Path: ./spec.yaml + Path: ./openapi.yaml - Resource: Type: properties Path: ./test.properties \ No newline at end of file diff --git a/examples/yaml-first/petstore/policies.yaml b/examples/yaml-first/petstore/policies.yaml index b2da692..f9eefbb 100755 --- a/examples/yaml-first/petstore/policies.yaml +++ b/examples/yaml-first/petstore/policies.yaml @@ -18,7 +18,7 @@ .name: OAS-Validate DisplayName: OAS-Validate Source: request - OASResource: oas://spec.yaml + OASResource: oas://openapi.yaml - RaiseFault: .continueOnError: false .enabled: true diff --git a/pkg/common/mock_apiproxy_template/apiproxy.yaml b/pkg/common/mock_apiproxy_template/apiproxy.yaml index 49627da..f481c2c 100644 --- a/pkg/common/mock_apiproxy_template/apiproxy.yaml +++ b/pkg/common/mock_apiproxy_template/apiproxy.yaml @@ -33,12 +33,12 @@ Policies: .name: OAS-Validate DisplayName: OAS-Validate Source: request - OASResource: oas://spec.json + OASResource: oas://openapi.json - AssignMessage: .name: AM-LoadSpec AssignVariable: Name: spec_json - ResourceURL: oas://spec.json + ResourceURL: oas://openapi.json - AssignMessage: .continueOnError: false .enabled: true @@ -94,10 +94,10 @@ ProxyEndpoints: .name: no-route TargetEndpoints: [] Resources: - #{{ os_writefile "./spec.json" ($.Values.spec | toPrettyJson) }} + #{{ os_writefile "./openapi.json" ($.Values.spec | toPrettyJson) }} - Resource: Type: oas - Path: ./spec.json + Path: ./openapi.json #{{ os_copyfile "./response-mocker.cjs" "./response-mocker.cjs" }} - Resource: Type: jsc diff --git a/pkg/common/mock_apiproxy_template/response-mocker.cjs b/pkg/common/mock_apiproxy_template/response-mocker.cjs index c36b26c..b035106 100644 --- a/pkg/common/mock_apiproxy_template/response-mocker.cjs +++ b/pkg/common/mock_apiproxy_template/response-mocker.cjs @@ -1362,7 +1362,7 @@ function pathMatches(path, pathTemplate) { function parseSpec(json) { var parsed_spec = {}; if (!isString(json) || json === "") { - throw new Error("could not find OpenAPI spec, set spec_json flow variable"); + throw new Error("could not find OpenAPI Description, set spec_json flow variable"); } try { diff --git a/pkg/common/resources/helper_functions.txt b/pkg/common/resources/helper_functions.txt index de72638..27f4fc1 100644 --- a/pkg/common/resources/helper_functions.txt +++ b/pkg/common/resources/helper_functions.txt @@ -73,13 +73,13 @@ e.g. {{ fmt_printf "url: %%v\n" $url }} remove_oas_extensions(src string) string - Removes the OpenAPI spec extensions from the file specified by src + Removes the OpenAPI Description extensions from the file specified by src The file must already exist in the output directory - This is useful if to make the spec files small within the generated bundles + This is useful if to make the OAS Description files small within the generated bundles e.g. - {{ os_writefile "./spec.yaml" $.Values.spec_string }} - {{ remove_oas_extensions "./spec.yaml" }} + {{ os_writefile "./openapi.yaml" $.Values.spec_string }} + {{ remove_oas_extensions "./openapi.yaml" }} Sprig Template functions from Sprig library diff --git a/pkg/common/resources/mock_features.txt b/pkg/common/resources/mock_features.txt index ed943c1..e95563e 100644 --- a/pkg/common/resources/mock_features.txt +++ b/pkg/common/resources/mock_features.txt @@ -1,7 +1,7 @@ 1. Request validation - The mock API proxy includes an OpenAPI spec validation policy. + The mock API proxy includes an OpenAPI Description validation policy. This is used to validate the request body, headers, and query parameters. You can pass the 'Mock-Validate-Request: false' header to skip this policy. diff --git a/pkg/common/resources/render_context.txt b/pkg/common/resources/render_context.txt index 1b86300..2c00a3b 100644 --- a/pkg/common/resources/render_context.txt +++ b/pkg/common/resources/render_context.txt @@ -21,7 +21,7 @@ (see https://pkg.go.dev/github.com/vektah/gqlparser/v2/ast#Schema) --set-oas key=./path/to/oas.yaml - Sets individual key/value. The value is the OpenAPI spec as a map[string]any. + Sets individual key/value. The value is the OpenAPI Description as a map[string]any. (Only OAS 2.0 and OAS 3.X.X supported) --set-grpc key=./path/to/protobuf.proto diff --git a/pkg/utils/openapi2.go b/pkg/utils/openapi2.go index 6ff35f1..84bb27d 100644 --- a/pkg/utils/openapi2.go +++ b/pkg/utils/openapi2.go @@ -80,7 +80,7 @@ func OAS2FileToOAS3File(input string, output string, allowCycles bool) error { if slices.IndexFunc(oas2node.Content[0].Content, func(n *yaml.Node) bool { return n.Value == "swagger" }) < 0 { - return errors.Errorf("input %s is not an OpenAPI 2.0 spec", input) + return errors.Errorf("input %s is not an OpenAPI 2.0 Description", input) } cycles, err := YAMLDetectRefCycles(oas2node, input) diff --git a/pkg/utils/overlay.go b/pkg/utils/overlay.go index 31c6594..e72792d 100644 --- a/pkg/utils/overlay.go +++ b/pkg/utils/overlay.go @@ -42,12 +42,12 @@ func OASOverlay(overlayFile string, specFile string, outputFile string) error { return err } - //if extends field is set, and there is no spec file specified, use the extends field + //if extends field is set, and there is no OAS Description file specified, use the extends field if extendsField != nil && specFile == "" { if path.IsAbs(*extendsField) { specFile = *extendsField } else { - //when using extends, if path is relative, then it's relative to the overlay itself + //when using extends, if path is relative, then it's relative to the Overlay itself dir := path.Dir(overlayFile) specFile = path.Join(dir, *extendsField) } @@ -73,7 +73,7 @@ func OASOverlay(overlayFile string, specFile string, outputFile string) error { if slices.IndexFunc(specNode.Content[0].Content, func(n *yaml.Node) bool { return n.Value == "openapi" }) < 0 { - return errors.Errorf("%s is not an OpenAPI 3.X spec file", specFile) + return errors.Errorf("%s is not an OpenAPI 3.X Description file", specFile) } resultNode, err := ApplyOASOverlay(overlayNode, specNode, overlayFile, specFile) @@ -181,7 +181,7 @@ func getActionTarget(actionNode *yaml.Node, overlayFile string) (*yaml.Node, err targetNode := results[0] if targetNode.Kind != yaml.ScalarNode { - return nil, errors.Errorf("'target' field within overlay action is not a string at %s:%d", overlayFile, targetNode.Line) + return nil, errors.Errorf("'target' field within Overlay action is not a string at %s:%d", overlayFile, targetNode.Line) } return targetNode, nil @@ -225,7 +225,7 @@ func getActionRemove(actionNode *yaml.Node, overlayFile string) (*bool, error) { if removeNode.Kind != yaml.ScalarNode || !(removeNode.Value == "true" || removeNode.Value == "false") { - return nil, errors.Errorf("'remove' field within overlay action is not boolean at %s:%d", overlayFile, removeNode.Line) + return nil, errors.Errorf("'remove' field within Overlay action is not boolean at %s:%d", overlayFile, removeNode.Line) } remove = results[0].Value == "true" From 7fa35710a1f536d74c75a2e69ba99a74be686599 Mon Sep 17 00:00:00 2001 From: Miguel Mendoza Date: Wed, 13 Nov 2024 13:39:36 -0800 Subject: [PATCH 2/2] fix: update failing tests cases after style changes --- .gitignore | 3 ++- .../testdata/mocks/petstore/exp-apiproxy.zip | Bin 17651 -> 17667 bytes pkg/utils/overlay_test.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f36f76a..06b39cd 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ dist dist/ site venv -.venv \ No newline at end of file +.venv +__pycache__ \ No newline at end of file diff --git a/pkg/mock/testdata/mocks/petstore/exp-apiproxy.zip b/pkg/mock/testdata/mocks/petstore/exp-apiproxy.zip index bb549836082760c4f68aed7fa490aba4b8b30c32..5977d4ba5516e92eec3c2d69f9b1f92bcc08848d 100644 GIT binary patch delta 10176 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 Wr2vq+;Cd-OBz>5FFr3)`#{Lg_NR}@E delta 10204 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