Converting Smithy to OpenAPI#

This guide describes how Smithy models can be converted to OpenAPI specifications.

Introduction#

OpenAPI is a standard for describing RESTful APIs. While Smithy has it's own interface definition language that's completely independent of OpenAPI, there are use cases for authoring API models in Smithy and converting them to OpenAPI using both ad-hoc and automated workflows. For example, integration with Amazon API Gateway, access to OpenAPI tools like SwaggerUI, or access to OpenAPI client and server code generators when Smithy generators are not available.

Smithy models can be converted to OpenAPI through smithy-build using the openapi plugin or through code using the software.amazon.smithy:smithy-openapi Java package.

Note

The Smithy openapi plugin currently supports OpenAPI 3.0.2 and OpenAPI 3.1.0.

Differences between Smithy and OpenAPI#

Smithy and OpenAPI take very different approaches to modeling APIs. Smithy is protocol agnostic, which means it focuses on the interfaces and abstractions that are provided to end-users rather than how the data is sent over the wire. While Smithy can define RESTful APIs, RPC APIs, pub/sub APIs, and more, OpenAPI only defines RESTful APIs. Both approaches have their own strengths and weaknesses. For example, while Smithy can define a much broader set of functionality, services, and client behaviors, it requires abstractions that have their own underlying complexity. OpenAPI is more permissive in the kinds of services it can describe, making it easier to adapt to existing web services, but at the same time making it easier to author APIs that provide a poor customer experience when using clients in strongly-typed languages.

Unsupported features#

Converting a Smithy model to OpenAPI is a lossy conversion. Various features in a Smithy model are not currently supported in the OpenAPI conversion.

Unsupported features

  • endpoint trait and hostLabel trait: These traits are used to dynamically alter the endpoint of an operation based on input. They are not supported in OpenAPI.

  • HTTP prefix headers: "Prefix headers" are used in Smithy to bind all headers under a common prefix into a single property of the input or output of an API operation. This can be used for things like Amazon S3's x-amz-meta-* headers. OpenAPI does not currently support this kind of header.

  • Greedy labels: Greedy labels are used in HTTP URIs to act as a placeholder for multiple segments of a URI (for example, /foo/{baz+}/bar). Some OpenAPI vendors/tooling support greedy labels (for example, Amazon API Gateway) while other do not. The converter will pass greedy labels through into the OpenAPI document by default, but they can be forbidden through the forbidGreedyLabels flag.

  • Non-RESTful routing: HTTP routing schemes that aren't based on methods and unique URIs are not supported in OpenAPI (for example, routing to operations based on a specific header or query string parameter).

  • Non-HTTP protocols: Protocols that do not send requests over HTTP are not supported with OpenAPI (for example, an MQTT-based protocol modeled with Smithy would need to also support an HTTP-based protocol to be converted to OpenAPI).

  • aws.api#arn trait: This trait does not influence resources defined in the OpenAPI model natively, nor via any of the Amazon API Gateway extensions.

Compatibility notes

  • Streaming: Smithy allows blob and string shapes to be marked as streaming, meaning that their contents should not be loaded into memory by clients or servers. While this isn't technically unsupported in OpenAPI, some vendors like API Gateway do not currently support streaming large payloads.

Lossy metadata

  • Resources: Smithy resource metadata is not carried over into the OpenAPI specification.
  • Custom traits: Custom traits defined in a Smithy model are not converted and added to the OpenAPI specification. Copying Smithy traits into OpenAPI as extensions requires the use of a custom software.amazon.smithy.openapi.fromsmithy.OpenApiExtension.

Converting to OpenAPI with smithy-build#

The openapi plugin contained in the software.amazon.smithy:smithy-openapi package can be used with smithy-build and the Smithy Gradle plugin to build OpenAPI specifications from Smithy models.

Install required tools

Before you proceed, make sure you have the Smithy CLI installed.

The following example shows how to use the Smithy CLI to build an OpenAPI specification from a Smithy model.

smithy-build.json#
{
  "version": "1.0",
  "sources": ["models"],
  "maven": {
    "dependencies": [
      "software.amazon.smithy:smithy-openapi:1.48.0",
      // Required for restJson1 trait.
      "software.amazon.smithy:smithy-aws-traits:1.48.0"
    ]
  },
  "plugins": {
    "openapi": {
      "service": "smithy.example#Weather",
      "protocol": "aws.protocols#restJson1",
      "version": "3.1.0"
    }
  }
}

To build the Smithy model and generate the OpenAPI artifact, run smithy build.

Tip

You can clone a working version of this example using the Smithy CLI init command.

smithy init -t smithy-to-openapi -o <output_directory>

Install required tools

Before you proceed, make sure you have gradle installed.

build.gradle.kts#
plugins {
    java
    // Use the `smithy-jar` plugin if you also want to package
    // smithy models into the JAR created by the `java` plugin.
    id("software.amazon.smithy-base").version("1.0.0")
}

dependencies {
    smithyBuild("software.amazon.smithy:smithy-aws-traits:1.48.0")

    // Required for restJson1 trait.
    implementation("software.amazon.smithy:smithy-aws-traits:1.48.0")
}
build.gradle#
plugins {
    id 'java'
    // Use the `smithy-jar` plugin if you also want to package
    // smithy models into the JAR created by the `java` plugin.
    id 'software.amazon.smithy-base' version '1.0.0'
}

dependencies {
    smithyBuild 'software.amazon.smithy:smithy-aws-traits:1.48.0'

    // Required for restJson1 trait.
    implementation 'software.amazon.smithy:smithy-aws-traits:1.48.0'
}

Important

A build-only dependency on "software.amazon.smithy:smithy-openapi:1.48.0" is required in order for smithy-build to map the "openapi" plugin name to the correct Java library implementation.

The Smithy Gradle plugin relies on a smithy-build.json file found at the root of a project to define the actual process of building the OpenAPI specification. The following example defines a smithy-build.json file that builds an OpenAPI specification from a service for the example.weather#Weather service using the aws.protocols#restJson1 protocol:

smithy-build.json#
{
  "version": "1.0",
  "plugins": {
    "openapi": {
      "service": "smithy.example#Weather",
      "protocol": "aws.protocols#restJson1",
      "version": "3.1.0"
    }
  }
}

To build the Smithy model and generate the OpenAPI artifact, run gradle build.

OpenAPI configuration settings#

The openapi plugin is highly configurable to support different OpenAPI tools and vendors.

Tip

You typically only need to configure the service and protocol settings to create a valid OpenAPI specification.

The following key-value pairs are supported:

service (string)

Required. The Smithy service shape ID to convert. For example, example.weather#Weather.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather"
        }
    }
}

Note

Any rename defined in the given service affects the generated schema names when converting to OpenAPI.

protocol (string)

The protocol shape ID to use when converting Smithy to OpenAPI. For example, aws.protocols#restJson1.

Important

  • protocol is required if a service supports multiple protocols.
  • A Smithy model can only be converted to OpenAPI if a corresponding software.amazon.smithy.openapi.fromsmithy.OpenApiProtocol implementation is registered by a software.amazon.smithy.openapi.fromsmithy.CoreExtension service provider found on the classpath.
smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "protocol": "aws.protocols#restJson1"
        }
    }
}
version (string)

Specifies the OpenAPI specification version. Currently supports OpenAPI 3.0.2 and OpenAPI 3.1.0. This option defaults to 3.0.2.

Note

The JSON schema version used for model schemas is the latest JSON schema version supported by the specified OpenAPI version. For example, OpenAPI version 3.1.0 will use JSON schema version draft2020-12 for model schemas.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "smithy.example#Weather",
            "version": "3.1.0"
        }
    }
}
tags (boolean)

Whether or not to include Smithy tags in the result as OpenAPI tags. The following example adds all tags in the Smithy model to the OpenAPI model.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "tags": true
        }
    }
}
supportedTags ([string])

Limits the exported tags to a specific set of tags. The value must be a list of strings. This property requires that tags is set to true in order to have an effect.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "tags": true,
            "supportedTags": ["foo", "baz", "bar"]
        }
    }
}
defaultBlobFormat (string)

Sets the default format property used when converting blob shapes in Smithy to strings in OpenAPI. Defaults to "byte", meaning Base64 encoded. See OpenAPI Data types for more information.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "defaultBlobFormat": "byte"
        }
    }
}
externalDocs ([string])

Limits the source of converted "externalDocs" fields to the specified priority ordered list of names in an externalDocumentation trait. This list is case insensitive. By default, this is a list of the following values: "Homepage", "API Reference", "User Guide", "Developer Guide", "Reference", and "Guide".

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "externalDocs": [
                "Homepage",
                "Custom"
            ]
        }
    }
}
keepUnusedComponents (boolean)

Set to true to prevent unused OpenAPI components from being removed from the created specification.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "keepUnusedComponents": true
        }
    }
}
jsonContentType (string)

Sets a custom media-type to associate with the JSON payload of JSON-based protocols.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "jsonContentType": "application/x-amz-json-1.1"
        }
    }
}
forbidGreedyLabels (boolean)

Set to true to forbid greedy URI labels. By default, greedy labels will appear as-is in the path generated for an operation. For example, "/{foo+}".

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "forbidGreedyLabels": true
        }
    }
}
removeGreedyParameterSuffix (boolean)

Set to true to remove the + suffix on the parameter name. By default, greedy labels will have a corresponding parameter name generated that will include the + suffix. Given a label "/{foo+}", the parameter name will be "foo+". If enabled, the parameter name will instead be "foo".

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "removeGreedyParameterSuffix": true
        }
    }
}
onHttpPrefixHeaders (string)

Specifies what to do when the httpPrefixHeaders trait is found in a model. OpenAPI does not support httpPrefixHeaders. By default, the conversion will fail when this trait is encountered, but this behavior can be customized using the following values for the onHttpPrefixHeaders setting:

  • FAIL: The default setting that causes the build to fail.
  • WARN: The header is omitted from the OpenAPI model and a warning is logged.
smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "onHttpPrefixHeaders": "WARN"
        }
    }
}
ignoreUnsupportedTraits (boolean)

Emits warnings rather than failing when unsupported traits like endpoint and hostLabel are encountered.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "ignoreUnsupportedTraits": true
        }
    }
}
substitutions (Map<String, any>)

Defines a map of strings to any JSON value to find and replace in the generated OpenAPI model.

String values are replaced if the string in its entirety matches one of the keys provided in the substitutions map. The corresponding value is then substituted for the string; this could even result in a string changing into an object, array, etc.

The following example will find all strings with a value of "REPLACE_ME" and replace the string with an array value of ["this is a", " replacement"] and replace all strings with a value of ANOTHER_REPLACEMENT with Hello!!!:

Warning

When possible, prefer jsonAdd instead because the update performed on the generated document is more explicit and resilient to change.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "substitutions": {
                "REPLACE_ME": ["this is a", " replacement"],
                "ANOTHER_REPLACEMENT": "Hello!!!"
            }
        }
    }
}
jsonAdd (Map<String, Node>)

Adds or replaces the JSON value in the generated OpenAPI document at the given JSON pointer locations with a different JSON value. The value must be a map where each key is a valid JSON pointer string as defined in RFC 6901. Each value in the map is the JSON value to add or replace at the given target.

Values are added using similar semantics of the "add" operation of JSON Patch, as specified in RFC 6902, with the exception that adding properties to an undefined object will create nested objects in the result as needed.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "jsonAdd": {
                "/info/title": "Replaced title value",
                "/info/nested/foo": {
                    "hi": "Adding this object created intermediate objects too!"
                },
                "/info/nested/foo/baz": true
            }
        }
    }
}
useIntegerType (boolean)

Set to true to use the "integer" type when converting byte, short, integer, and long shapes to OpenAPI. Configuring this setting to true, like the example below, is recommended.

By default, these shape types are converted to OpenAPI with a type of "number".

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "useIntegerType": true
        }
    }
}
disableIntegerFormat (boolean)

Set to true to disable setting the format property when using the "integer" type that is enabled by the useIntegerType configuration setting.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "useIntegerType": true,
            "disableIntegerFormat": true
        }
    }
}

With this enabled (the default), the format property is set to int32 or int64 for Integer or Long shapes respectively.

Weather.openapi.json#
{
    "Foo": {
        "type": "object",
        "properties": {
            "myInteger": {
                "type": "integer",
                "format": "int32"
            },
            "myLong": {
                "type": "integer",
                "format": "int64"
            }
        }
    }
}
onErrorStatusConflict (String)

Specifies how to resolve multiple error responses that share the same HTTP status code. This behavior can be enabled using the following values for the onErrorStatusConflict setting:

oneOf
Use OpenAPI's oneOf keyword to combine error responses with same HTTP status code. The oneOf option wraps schemas for contents of conflicting errors responses schemas into a synthetic union schema using OpenAPI's oneOf keyword.

By default, this setting is disabled. When enabled, a single combined response object will be included in the OpenAPI model output. Any member of the conflicting errors bound to a HTTP header will be added to the top-level response. If any headers conflict, an error will be thrown. Remaining members will be left in place on the conflicting errors. The modified conflicting errors are then added to the combined response object.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "smithy.example#Weather",
            "onErrorStatusConflict": "oneOf"
        }
    }
}

JSON schema configuration settings#

alphanumericOnlyRefs (boolean)

Creates JSON schema names that strip out non-alphanumeric characters.

This is necessary for compatibility with some vendors like Amazon API Gateway that only allow alphanumeric shape names.

Note

This setting is enabled by default when software.amazon.smithy:smithy-aws-apigateway-openapi is on the classpath and apiGatewayType is not set to DISABLED.

useJsonName (boolean)

Uses the value of the jsonName trait when creating JSON schema properties for structure and union shapes. This property MAY be automatically set to true depending on the protocol being converted.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "useJsonName": true
        }
    }
}
defaultTimestampFormat (string)

Sets the assumed timestampFormat trait value for timestamps with no explicit timestampFormat trait. The provided value is expected to be a string. Defaults to "date-time" if not set. Can be set to "date-time", "epoch-seconds", or "http-date".

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "defaultTimestampFormat": "epoch-seconds"
        }
    }
}
unionStrategy (string)

Configures how Smithy union shapes are converted to JSON Schema.

This property must be a string set to one of the following values:

  • oneOf: Converts to a schema that uses "oneOf". This is the default setting used if not configured.
  • object: Converts to an empty object "{}".
  • structure: Converts to an object with properties just like a structure.
smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "unionStrategy": "oneOf"
        }
    }
}
mapStrategy (string)

Configures how Smithy map shapes are converted to JSON Schema.

This property must be a string set to one of the following values:

  • propertyNames: Converts to a schema that uses a combination of "propertyNames" and "additionalProperties". This is the default setting used if not configured.
  • patternProperties: Converts to a schema that uses "patternProperties". If a map's key member or its target does not have a "pattern" trait, a default indicating one or more of any character (".+") is applied.
smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "mapStrategy": "propertyNames"
        }
    }
}
schemaDocumentExtensions (Map<String, any>)

Adds custom top-level key-value pairs to the created OpenAPI specification. Any existing value is overwritten.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "schemaDocumentExtensions": {
                "x-my-custom-top-level-property": "Hello!",
                "x-another-custom-top-level-property": {
                    "can be": ["complex", "value", "too!"]
                }
            }
        }
    }
}
disableFeatures ([string])

Disables JSON schema and OpenAPI property names from appearing in the generated OpenAPI model.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "disableFeatures": ["propertyNames"]
        }
    }
}
supportNonNumericFloats (boolean)

Set to true to add support for NaN, Infinity, and -Infinity in float and double shapes. These values will be serialized as strings. The JSON Schema document will be updated to refer to them as a "oneOf" of number and string.

By default, these non-numeric values are not supported.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "supportNonNumericFloats": true
        }
    }
}

When this is disabled (the default), references to floats/doubles will look like this:

Weather.openapi.json#
{
    "floatMember": {
        "type": "number"
    }
}

With this enabled, references to floats/doubles will look like this:

Weather.openapi.json#
{
    "floatMember": {
        "oneOf": [
            {
                "type": "number"
            },
            {
                "type": "string",
                "enum": [
                    "NaN",
                    "Infinity",
                    "-Infinity"
                ]
            }
        ]
    }
}
disableDefaultValues (boolean)

Set to true to disable adding default values.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "disableDefaultValues": true
        }
    }
}

With this disabled, default values will not appear in the output:

Weather.openapi.json#
{
    "Foo": {
        "type": "object",
        "properties": {
            "bam": {
                "type": "array",
                "items": {
                    "type": "string"
                }
            },
            "bar": {
                "type": "number"
            },
            "bat": {
                "$ref": "#/definitions/MyEnum"
            },
            "baz": {
                "type": "string"
            }
        }
    }
}

With this enabled (the default), default values will be added, with $ref pointers wrapped in an allOf:

Weather.openapi.json#
{
    "Foo": {
        "type": "object",
        "properties": {
            "bam": {
                "type": "array",
                "items": {
                    "type": "string"
                },
                "default": []
            },
            "bar": {
                "type": "number",
                "default": 0
            },
            "bat": {
                "allOf": [
                    {
                        "$ref": "#/definitions/MyEnum"
                    },
                    {
                        "default": "FOO"
                    }
                ]
            },
            "baz": {
                "type": "string",
                "default": ""
            }
        }
    }
}
disableIntEnums (boolean)

Set to true to disable setting the enum property for intEnum shapes.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "disableIntEnums": true
        }
    }
}

With this disabled, intEnum shapes will be inlined and the enum property will not be set:

Weather.openapi.json#
{
    "Foo": {
        "type": "object",
        "properties": {
            "bar": {
                "type": "number"
            }
        }
    }
}

With this enabled (the default), intEnum shapes will have the enum property set and the schema will use a $ref.

Weather.openapi.json#
{
    "Foo": {
        "type": "object",
        "properties": {
            "bar": {
                "$ref": "#/definitions/MyIntEnum"
            }
        }
    },
    "MyIntEnum": {
        "type": "number",
        "enum": [
            1,
            2
        ]
    }
}

Security schemes#

Smithy authentication traits applied to a service, resource, or operation are converted to OpenAPI security schemes that are defined and attached to the corresponding OpenAPI definitions.

Smithy will look for service providers on the classpath that implement software.amazon.smithy.openapi.fromsmithy.Smithy2OpenApiExtension. These service providers register software.amazon.smithy.openapi.fromsmithy.SecuritySchemeConverter implementations used to convert Smithy authentication traits to OpenAPI security schemes.

Smithy provides built-in support for the following authentication traits:

For example, given the following Smithy model:

main.smithy#
$version: "2"
namespace smithy.example

use aws.protocols#restJson1

@restJson1
@httpApiKeyAuth(name: "x-api-key", in: "header")
service Foo {
    version: "2006-03-01"
    operations: [ExampleOperation]
}

@http(method: "GET", uri: "/")
operation ExampleOperation {}

Smithy will generate the following OpenAPI model:

Foo.openapi.json#
{
    "openapi": "3.0.2",
    "info": {
        "title": "Foo",
        "version": "2006-03-01"
    },
    "paths": {
        "/": {
            "get": {
                "operationId": "ExampleOperation",
                "responses": {
                    "200": {
                        "description": "ExampleOperation response"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "smithy.api#httpApiKeyAuth": {
                "type": "apiKey",
                "name": "x-api-key",
                "in": "header"
            }
        }
    },
    "security": [
        {
            "smithy.api#httpApiKeyAuth": [ ]
        }
    ]
}

@examples trait conversion#

In Smithy, example values of input structure members and the corresponding output or error structure members for an operation are grouped together into one set of example values for an operation. Below is an example "unit" of FooOperation operation shape, which shows this logical grouping.

apply FooOperation @examples(
    [
        {
            title: "valid example",
            documentation: "valid example doc",
            input: {
                bar: "1234"
            },
            output: {
                baz: "5678"
            },
        }
    ]
)

However, example values in OpenAPI are scattered throughout the model, with each example value contained by the OpenAPI object the example value is for. The following is an example OpenAPI model for the above Smithy example value.

"paths": {
    "/": {
        "get": {
            "operationId": "FooOperation",
            "requestBody": {
                "content": {
                    "application/json": {
                        "examples": {
                            "FooOperation_example1": {
                                "summary": "valid example",
                                "description": "valid example doc",
                                "value": {
                                    "bar": "1234"
                                }
                            }
                        }
                    }
                }
            }
            "responses": {
                "200": {
                    "description": "FooOperation response",
                    "content": {
                        "application/json": {
                            "examples": {
                                "FooOperation_example1": {
                                    "summary": "valid example",
                                    "description": "valid example doc",
                                    "value": {
                                        "baz": "5678"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

OpenAPI conversion traits#

The software.amazon.smithy:smithy-openapi-traits package defines traits used to augment the conversion of a Smithy model into an OpenAPI specification.

smithy-build.json#
{
  "version": "1.0",
  "sources": ["models"],
  "maven": {
    "dependencies": [
      "software.amazon.smithy:smithy-openapi:1.48.0",
      "software.amazon.smithy:smithy-openapi-traits:1.48.0",
      // Required for restJson1 trait.
      "software.amazon.smithy:smithy-aws-traits:1.48.0"
    ]
  },
  "...": "..."
}

The following example shows how to add the package to your Gradle build alongside the smithy-openapi plugin:

build.gradle.kts#
plugins {
    java
    id("software.amazon.smithy.gradle.smithy-base").version("1.0.0")
}

dependencies {
    smithyBuild("software.amazon.smithy:smithy-openapi:1.48.0")

    implementation("software.amazon.smithy:smithy-openapi-traits:1.48.0")
    // Required for restJson1 trait.
    implementation("software.amazon.smithy:smithy-aws-traits:1.48.0")
}
build.gradle#
plugins {
    id 'java'
    id 'software.amazon.smithy.gradle.smithy-base' version '1.0.0'
}

dependencies {
    smithyBuild 'software.amazon.smithy:smithy-openapi:1.48.0'

    implementation 'software.amazon.smithy:smithy-openapi-traits:1.48.0'
    // Required for restJson1 trait.
    implementation 'software.amazon.smithy:smithy-aws-traits:1.48.0'
}

Refer to Converting to OpenAPI with smithy-build for more detailed information about using the plugin and Gradle.

specificationExtension trait#

Summary
Indicates a trait shape should be converted into an OpenAPI specification extension. Any custom trait that has been annotated with this trait will be serialized into the OpenAPI specification using its Smithy JSON AST representation.
Trait selector
[trait|trait]
Value type
structure

The specificationExtension trait is a structure that supports the following members:

Property Type Description
as string Explicitly name the specification extension. If set, it must begin with "x-". Otherwise, it defaults to the target trait's shape ID normalized with hyphens and prepended with "x-".

The following example defines a specification extension representing a custom metadata structure using the specificationExtension trait:

$version: "2"
namespace smithy.example

use smithy.openapi#specificationExtension

@trait
@specificationExtension(as: "x-meta")
structure metadata {
    owner: String
}

@output
@metadata(owner: "greetings-team-b")
structure GreetResponse {
    greeting: String
}

@readonly
@http(method: "GET", uri: "/greet")
@metadata(owner: "greetings-team-a")
operation Greet {
    output: GreetResponse
}

This results in an x-meta property being added to the respective objects in the OpenAPI output:

{
    "...": "...",
    "paths": {
        "/greet": {
            "get": {
                "operationId": "Greet",
                "responses": {
                    "200": {
                        "description": "Greet 200 response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/GreetResponseContent"
                                }
                            }
                        }
                    }
                },
                "x-meta": {
                    "owner": "greetings-team-a"
                }
            }
        }
    },
    "components": {
        "schemas": {
            "GreetResponseContent": {
                "type": "object",
                "properties": {
                    "greeting": {
                        "type": "string"
                    }
                },
                "x-meta": {
                    "owner": "greetings-team-b"
                }
            }
        }
    }
}

Supported trait locations#

Only a subset of OpenAPI locations are supported in the conversion:

Smithy Location OpenAPI Location
Service shape Root OpenAPI schema
Operation shape Operation object
Simple & Aggregate shapes Schema object

Unsupported use cases can likely be covered by the jsonAdd feature of the smithy-openapi plugin.

Amazon API Gateway extensions#

Smithy models can be converted to OpenAPI specifications that contain Amazon API Gateway extensions for defining things like integrations . These API Gateway extensions are automatically picked up by Smithy by adding a dependency on software.amazon.smithy:smithy-aws-apigateway-openapi.

smithy-build.json#
{
  "version": "1.0",
  "sources": ["models"],
  "maven": {
    "dependencies": [
      "...",
      "software.amazon.smithy:smithy-aws-apigateway-openapi:1.48.0"
    ]
  },
  "...": "..."
}
build.gradle.kts#
dependencies {
    smithyBuild("software.amazon.smithy:smithy-aws-apigateway-openapi:1.48.0")
}
build.gradle#
dependencies {
    smithyBuild 'software.amazon.smithy:smithy-aws-apigateway-openapi:1.48.0'
}

Amazon API Gateway configuration settings#

apiGatewayDefaults (string)

Sets recommended default configuration settings and allows for those defaults to be disabled.

This setting can be set to one of the following:

  • 2023-08-11 Set the defaults described below.
  • DISABLED Disables setting defaults.

The 2023-08-11 version sets the following configuration settings:

Important

This setting should be set explicitly to one of the allowed values. If omitted, it will default to 2023-08-11.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "apiGatewayDefaults": "2023-08-11"
        }
    }
}
apiGatewayType (string)

Defines the type of API Gateway to define in the generated OpenAPI model. This setting influences which API Gateway specific plugins apply to the generated OpenAPI model.

This setting can be set to one of the following:

  • REST: Generates a REST API. This is the default setting if not configured.
  • HTTP: Generates an HTTP API.
  • DISABLED: Disables all API Gateway modifications made to the OpenAPI model. This is useful if software.amazon.smithy:smithy-aws-apigateway-openapi is inadvertently placed on the classpath by a dependency.
smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "apiGatewayType": "REST"
        }
    }
}
disableCloudFormationSubstitution (boolean)

Disables automatically converting ${} templates in specific properties into CloudFormation intrinsic functions.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "disableCloudFormationSubstitution": true
        }
    }
}
additionalAllowedCorsHeaders ([string])

Sets additional allowed CORS headers on the preflight requests. If this option is not set, the default amz-sdk-invocation-id and amz-sdk-request headers will be added. By setting this option to an empty array, those default headers will be omitted.

smithy-build.json#
{
    "version": "1.0",
    "plugins": {
        "openapi": {
            "service": "example.weather#Weather",
            "additionalAllowedCorsHeaders": ["foo-header", "bar-header"]
        }
    }
}

Binary types#

The list of binary media types used by an API need to be specified for API Gateway in a top-level extension named x-amazon-apigateway-binary-media-types. Smithy will automatically detect every media type used in a service by collecting all of the mediaType trait values for all members marked with httpPayload trait.

Request validators#

Amazon API Gateway can perform request validation before forwarding a request to an integration. You can opt-in to this feature using the aws.apigateway#requestValidator trait.

Smithy will populate the value of the x-amazon-apigateway-request-validators and x-amazon-apigateway-request-validator OpenAPI extensions using the aws.apigateway#requestValidator traits found in a service. The aws.apigateway#requestValidator trait can be applied to a service to enable a specific kind of request validation on all operations within a service. It can also be applied to an operation to set a specific validator for the operation.

Smithy defines the following canned request validators:

full

Creates a request validator configured as

{
    "validateRequestBody": true,
    "validateRequestParameters": true
}
params-only

Creates a request validator configured as

{
    "validateRequestBody": false,
    "validateRequestParameters": true
}
body-only

Creates a request validator configured as

{
    "validateRequestBody": true,
    "validateRequestParameters": false
}

Smithy will gather all of the utilized request validators and add their declarations in a top-level x-amazon-apigateway-request-validators OpenAPI extension.

Integrations#

Smithy models can specify the backend integration configuration that Amazon API Gateway uses to for an operation.

If either of the above traits are applied to a service shape, then all operations in the service inherit the applied integration. If either trait is applied to a resource shape, then all operations of the resource and all child resources inherit the applied integration. If either trait is applied to an operation, then the operation uses a specific integration that overrides any integration inherited from a resource or service.

CORS functionality#

When the smithy.api#cors trait is applied to a service and apiGatewayType is set to REST, then Smithy performs the following additions during the OpenAPI conversion:

  • Adds CORS-preflight OPTIONS requests using mock API Gateway integrations.
  • Adds CORS-specific headers to every response in the API, including Access-Control-Allow-Origin, Access-Control-Expose-Headers, and Access-Control-Allow-Credentials where appropriate.
  • Adds static CORS response headers to API Gateway "gateway" responses. These are added only when no gateway responses are defined in the OpenAPI model.

Authorizers#

The x-amazon-apigateway-authorizer security scheme extension is added using the aws.apigateway#authorizers trait and aws.apigateway#authorizer trait.

The aws.apigateway#authorizers trait defines Lambda authorizers to attach to authentication schemes defined on a service. Authorizers are first defined on a service, and then attached to the service, resources, or operations using the aws.apigateway#authorizer-trait.

The following Smithy model:

$version: "2"
namespace smithy.example

use aws.apigateway#authorizer
use aws.apigateway#authorizers
use aws.auth#sigv4
use aws.protocols#restJson1

@restJson1
@sigv4(name: "service")
@authorizer("foo")
@authorizers(
    foo: {scheme: sigv4, type: "aws", uri: "arn:foo"}
    baz: {scheme: sigv4, type: "aws", uri: "arn:foo"}
)
service Example {
  version: "2019-06-17"
  operations: [OperationA, OperationB]
  resources: [ResourceA, ResourceB]
}

// Inherits the authorizer of the service
operation OperationA {}

// Overrides the authorizer of the service
@authorizer("baz")
operation OperationB {}

// Inherits the authorizer of the service
resource ResourceA {
  operations: [OperationC, OperationD]
}

// Inherits the authorizer of the service
operation OperationC {}

// Overrides the authorizer of the service
@authorizer("baz")
operation OperationD {}

// Overrides the authorizer of the service
@authorizer("baz")
resource ResourceB {
  operations: [OperationE, OperationF]
}

// Inherits the authorizer of ResourceB
operation OperationE {}

// Overrides the authorizer of ResourceB
@authorizer("foo")
operation OperationF {}

Is converted to the following OpenAPI model:

{
    "openapi": "3.0.2",
    "info": {
        "title": "Example",
        "version": "2019-06-17"
    },
    "paths": {
        "/a": {
            "get": {
                "operationId": "OperationA",
                "responses": {
                    "200": {
                        "description": "OperationA response"
                    }
                }
            }
        },
        "/b": {
            "get": {
                "operationId": "OperationB",
                "responses": {
                    "200": {
                        "description": "OperationB response"
                    }
                },
                "security": [
                    {
                        "baz": []
                    }
                ]
            }
        },
        "/c": {
            "get": {
                "operationId": "OperationC",
                "responses": {
                    "200": {
                        "description": "OperationC response"
                    }
                }
            }
        },
        "/d": {
            "get": {
                "operationId": "OperationD",
                "responses": {
                    "200": {
                        "description": "OperationD response"
                    }
                },
                "security": [
                    {
                        "baz": []
                    }
                ]
            }
        },
        "/e": {
            "get": {
                "operationId": "OperationE",
                "responses": {
                    "200": {
                        "description": "OperationE response"
                    }
                },
                "security": [
                    {
                        "baz": []
                    }
                ]
            }
        },
        "/f": {
            "get": {
                "operationId": "OperationF",
                "responses": {
                    "200": {
                        "description": "OperationF response"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "baz": {
                "type": "apiKey",
                "description": "AWS Signature Version 4 authentication",
                "name": "Authorization",
                "in": "header",
                "x-amazon-apigateway-authorizer": {
                    "type": "aws",
                    "authorizerUri": "arn:foo"
                },
                "x-amazon-apigateway-authtype": "awsSigv4"
            },
            "foo": {
                "type": "apiKey",
                "description": "AWS Signature Version 4 authentication",
                "name": "Authorization",
                "in": "header",
                "x-amazon-apigateway-authorizer": {
                    "type": "aws",
                    "authorizerUri": "arn:foo"
                },
                "x-amazon-apigateway-authtype": "awsSigv4"
            }
        }
    },
    "security": [
        {
            "foo": []
        }
    ]
}

AWS CloudFormation substitutions#

OpenAPI specifications used with Amazon API Gateway are commonly deployed through AWS CloudFormation. Values within an OpenAPI specification for things like the region a service is deployed and resources used within the service are often unknown until deployment-time. CloudFormation offers the ability to use intrinsic functions in a JSON document to resolve, find, and replace this unknown data at deployment-time.

When the software.amazon.smithy:smithy-aws-apigateway-openapi library is loaded on the classpath, Smithy will treat specific, well-known parts of an OpenAPI specification as an Fn::Sub. This allows Smithy models to refer to variables that aren't available until a stack is created using the format of ${x} where "x" is the variable name.

Smithy will automatically wrap the following locations of an OpenAPI specification in an Fn::Sub if the value contained in the location uses the Fn::Sub variable syntax (* means any value):

  • components/securitySchemes/*/x-amazon-apigateway-authorizer/providerARNs/*
  • components/securitySchemes/*/x-amazon-apigateway-authorizer/authorizerCredentials
  • components/securitySchemes/*/x-amazon-apigateway-authorizer/authorizerUri
  • paths/*/*/x-amazon-apigateway-integration/connectionId
  • paths/*/*/x-amazon-apigateway-integration/credentials
  • paths/*/*/x-amazon-apigateway-integration/uri

Note

This functionality can be disabled by setting the disableCloudFormationSubstitution configuration property to true.

Amazon Cognito User Pools#

Smithy adds Cognito User Pool based authentication to the OpenAPI model when the aws.auth#cognitoUserPools trait is added to a service shape. When this trait is present, Smithy will add a securitySchemes components entry:

{
    "aws.auth#cognitoUserPools": {
        "type": "apiKey",
        "description": "Amazon Cognito User Pools authentication",
        "name": "Authorization",
        "in": "header",
        "x-amazon-apigateway-authtype": "cognito_user_pools",
        "x-amazon-apigateway-authorizer": {
            "type": "cognito_user_pools",
            "providerARNs": [
                "arn:aws:cognito-idp:us-east-1:123:userpool/123"
            ]
        }
    }
}

In the entry, providerARNs will be populated from the providerArns list from the trait.

Amazon API Gateway API key usage plans#

Smithy enables API Gateway's API key usage plans when a scheme based on the httpApiKeyAuth trait is set and configured as an authorizer with no type property set.

The following Smithy model enables API Gateway's API key usage plans on the OperationA operation:

$version: "2"
namespace smithy.example

use aws.apigateway#authorizer
use aws.apigateway#authorizers
use aws.protocols#restJson1

@restJson1
@httpApiKeyAuth(name: "x-api-key", in: "header")
@authorizer("api_key")
@authorizers(api_key: {scheme: "smithy.api#httpApiKeyAuth"})
service Example {
  version: "2019-06-17"
  operations: [OperationA]
}

operation OperationA {}

Other traits that influence API Gateway#

aws.apigateway#apiKeySource
Specifies the source of the caller identifier that will be used to throttle API methods that require a key. This trait is converted into the x-amazon-apigateway-api-key-source OpenAPI extension.
aws.apigateway#authorizers

Lambda authorizers to attach to the authentication schemes defined on this service.

See also

See Authorizers

Amazon API Gateway limitations#

The default property in OpenAPI is not currently supported by Amazon API Gateway. The default property is automatically removed from OpenAPI models when they are generated for Amazon API Gateway. Additionally, default values will not be set on $ref pointers or wrapped in an allOf as described in disableDefaultValues.

Converting to OpenAPI with code#

Developers that need more advanced control over the Smithy to OpenAPI conversion can use the software.amazon.smithy:smithy-openapi Java library to perform the conversion.

First, you'll need to get a copy of the library. The following example shows how to install software.amazon.smithy:smithy-openapi through Gradle:

build.gradle.kts#
dependencies {
    implementation("software.amazon.smithy:smithy-openapi:1.48.0")
}
build.gradle#
dependencies {
    implementation 'software.amazon.smithy:smithy-openapi:1.48.0'
}

Next, you need to create and configure an OpenApiConverter:

import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.openapi.OpenApiConfig;
import software.amazon.smithy.openapi.fromsmithy.OpenApiConverter;
import software.amazon.smithy.openapi.model.OpenApi;

OpenApiConverter converter = OpenApiConverter.create();

// Add any necessary configuration settings.
OpenApiConfig config = new OpenApiConfig();
config.setService(ShapeId.from("example.weather#Weather"));
converter.config(config);

// Generate the OpenAPI schema.
OpenApi result = converter.convert(myModel);

The conversion process is highly extensible through software.amazon.smithy.openapi.fromsmithy.Smithy2OpenApiExtension service providers. See the Javadocs for more information.