Defining API Gateway Endpoints with OpenAPI
This tutorial references the code in the aws-connectedcar-dotnet-serverless repository. If you're new to this course, see the introduction for information about setting up your workstation and getting the sample code.
As you’ve seen from the setup documentation, the sample code has two sets of deployment files, one that uses SAM templates only, and one that also incorporates OpenAPI documents. Here’s a VS Code screen shot to show where these OpenAPI documents can be found in the sample code:
Looking at the VS Code “Explorer” panel on the left, you can see a set deployment files in the “sam” folder, including scripts and CloudFormation templates. Above the “sam” folder, there’s an equivalent “openapi” folder that contains a similar set of template files but also includes four OpenAPI files in the “specifications” folder. These four files correspond to the three APIs that need to be defined, plus one shared file that contains data schemas.
Note that OpenAPI has a mechanism for referencing items in external documents, but this mechanism is not compatible with how we have to import these documents into CloudFormation templates. As a work-around, the content of the schemas.openapi.yaml file is appended to that of the three other API files by the deployment scripts before they’re each uploaded to S3.
Importing API Definitions
Now, let’s look at the Admin API resource in the admin.yaml template, which corresponds to the resource that we looked at in the previous tutorial, but this time for the OpenAPI deployment:
By design, all the API properties that we looked at previously are present here as well. This includes the Name, StageName and TracingEnabled properties, and the Auth and MethodSettings elements. What’s different in this resource is the DefinitionBody, which you can see on lines 72-76. With this element CloudFormation will, when executed, load the file identified on line 76, and transform the template to include this OpenAPI file’s content. Because the OpenAPI file gets added inline in this way, when writing it you can use intrinsic functions and can reference parameter and resource names, just like you would in the containing CloudFormation template.
Before we look at the content of these OpenAPI specifications in detail, let’s look at an example Lambda resource from the OpenAPI version of the admin.yaml template. Here’s the CreateDealer Lambda, which you’ll note has the same Lambda properties, but unlike what we saw in the previous lesson it does not include any Event element with its corresponding endpoint properties:
Enabling Request Validation
Let’s turn now to the OpenAPI document that defines the endpoints for the Admin API. To start, we’ll look at the header section in the admin.openapi.yaml file:
What’s of note in the header shown above are the API Gateway extension elements on lines 5-9. These elements enable full request validation for the API, including request parameters and request bodies.
Defining Endpoints & Lambda Integrations
Continuing with the same file, here’s the first endpoint path and HTTP method, found in the “paths” section of this OpenAPI document:
There are several interesting things to point out in this block of code. In a nutshell, this code defines an endpoint and its associated Lambda integration, just like the Lambda and path properties do in the SAM templates, but with the addition of request validation. Let’s step through it.
First, lines 15-20 define the request body, including whether it’s mandatory (line 16), how the content is to be formatted (line 18), and what the data schema for this content should be (line 20). Skipping over the security element, which we’ll cover in a later lesson, we have the “x-amazon-api-gateway-integration” extension element. The “aws_proxy” property on line 29 sets the integration type. The “httpMethod” property for Lambdas will always be set to “POST” because that’s how API Gateway calls the Lambda invoker. The “uri” property uses the required long-form syntax of the !Sub function to interpolate values from the admin.yaml template into the Lambda invocation URL. Lastly, invoking Lambdas in this way requires that we specify an additional invocation role, which is what’s referenced on line 34.
Here’s the GET method for this path which defines a query parameter, a response schema, and a different Lambda target:
Continuing on, here’s the path and GET method for the “get dealer” operation, which in this case uses a path parameter instead of the query parameter in the code above:
Defining Data Schemas
In the samples we’ve looked at above, we’ve seen references to data schemas for parameters, and for request and response bodies. There’s a “components” section in OpenAPI documents within which there is a “schemas” element. (As we noted above, we’re defining all our schemas for the three APIs and their respective OpenAPI documents in a fourth file, called “schemas.openapi.yaml” that’s appended to the three API documents during deployment.)
In this schemas file, we’re first defining all our field-level schemas, as shown below. (Note that the “schemas” element at the top has to be indented two spaces to ensure that the YAML is well-formed when appended to the API files):
In the cases where these field-level schemas are string types they also have value constraints of some kind. In some cases this is simply an OpenAPI-defined string format, such as “uuid” or “date-time”. In other cases there is a regex pattern, a maximum length, or there are enumerated values. There are also some integer types shown above that use the int32 or int64 format. The important point with these field-level schemas is that they’re all defined in a way that limits the maximum data size.
There are also object schemas in this file. Here are a couple of basic examples:
You’ll note that all the properties for these object schemas reference the field-level schemas we just looked at above. As a result, there is an aggregate maximum data size that will apply to these schemas as well.
The Dealer schema shown below is an example of a compound object schema, with the “address” property referencing the Address object schema:
Lastly, here’s an example of an array schema that’s a collection of “Dealer” objects. This schema is used only for responses, so the fact it has no size limit doesn’t impact request validation:
API Gateway Models & Request Validation
Let’s close out this lesson by going back to the AWS console to look at an OpenAPI deployment of the Admin API. First, here’s the “Method Request” page for the “/admin/dealers” POST method:
Previously, when looking at this page for the SAM deployment, the “Request Validator” property had a value of “NONE”. Here, because of the request validation elements in the OpenAPI headers, this property is set to “all”, meaning it applies both to the request parameters and the request body.
Further down on this page, under the “Request Body” heading, you can see a row with a “Dealer” item under the “Model Name” column. This is how API Gateway presents the associated data models for this API. All the field- and object-level data schemas that we defined in OpenAPI can be found on the “Models” page by following the link in the menu on the left.
Here’s what the “Dealer” model looks like on this page:
The format of the API Gateway schema language is obviously different, but you’ll find the same data structures and value constraints in these models as were originally defined in the OpenAPI schemas.