How to document a response comprised of a list of resources using OpenAPI - rest

I am trying to create OpenAPI yml documentation file (via swagger). One of my API calls returns a list of resources. Each resources has properties, a self link and a link to an additional link which will retrieve additional "stuff" which relate to the resource.
Please see the following example:
[
{
"name": "object-01",
"links": [
{
"rel": "self",
"href": "http://localhost:8800/foo/object-01"
},
{
"rel": "Supported stuff",
"href": "http://localhost:8800/foo/object-01/stuff"
}
]
}, {
"name": "object-02",
"links": [
{
"rel": "self",
"href": "http://localhost:8800/foo/object-02"
},
{
"rel": "Supported stuff",
"href": "http://localhost:8800/foo/object-02/stuff"
}
]
}, {
"name": "object-03",
"links": [
{
"rel": "self",
"href": "http://localhost:8800/foo/object-03"
},
{
"rel": "Supported stuff",
"href": "http://localhost:8800/foo/object-03/stuff"
}
]
}
]
I am not sure what is the right way to document this, this is what I have in place right now.
paths:
/foo/objects:
get:
operationId: getObject
responses:
'200':
description: Respresentation of objects
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/object'
links:
self:
$ref: '#/components/links/object'
components:
links:
object:
operationId: getSObject
stuff:
operationId: getStuff
schemas:
object:
type: object
properties:
name:
type: string
But I do not believe this is adequately represents my API.
Thanks for your help

Links that are included in the actual response need to be described as part of the response body schema:
paths:
/foo/objects:
get:
operationId: getObject
responses:
'200':
description: Respresentation of objects
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/object'
components:
schemas:
object:
type: object
properties:
name:
type: string
links: # <-------------
type: array
items:
$ref: '#/components/schemas/link'
link:
type: object
properties:
rel:
type: string
href:
type: string
format: uri
OpenAPI 3.0 links concept is similar to HATEOAS, but not really. These links are used to describe how the values returned from one operation can be used as input in other operations. For example, the create user operation returns the user ID, and this ID can be used to update or delete the user. This page has some more info about the links keyword: https://swagger.io/docs/specification/links

Related

Adding lambda integration to HttpApi routes with SAM

I am currently attempting to have a AWS::Serverless::HttpApi integrate with a group of AWS::Serverless::Function's. The goal is to define these resources within a SAM template, and define the actual API using a swagger file.
I have my SAM template defined as so:
Resources:
apiPing:
Type: AWS::Serverless::Function
Properties:
Description: 'Ping'
CodeUri: ../bin/cmd-api-ping.zip
Handler: cmd-api-ping
Runtime: go1.x
Role:
Fn::GetAtt: apiLambdaRole.Arn
Events:
PingEvent:
Type: HttpApi
Properties:
ApiId: !Ref api
Path: /ping
Method: post
api:
Type: AWS::Serverless::HttpApi
Properties:
StageName: prod
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: swagger.yaml
AccessLogSettings:
DestinationArn: !GetAtt accessLogs.Arn
Format: $context.requestId
And my swagger file:
openapi: 3.0.1
info:
title: 'API'
version: 2019-10-13
paths:
/ping:
post:
summary: 'invoke ping'
operationId: 'apiPing'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PingRequest'
required: true
responses:
'200':
description: 'Successful'
content:
application/json:
schema:
$ref: '#/components/schemas/PongResponse'
x-amazon-apigateway-integration:
httpMethod: "POST"
type: aws_proxy
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${apiPing.Arn}/invocations
responses:
default:
statusCode: "200"
contentHandling: "CONVERT_TO_TEXT"
passthroughBehavior: "when_no_match"
components:
schemas:
PingRequest:
description: 'a ping request'
type: object
properties:
ping:
description: 'some text'
type: string
PongResponse:
description: 'a pong response'
type: object
properties:
pong:
description: 'some text'
type: string
This template deploys without any errors, however there is no integration attached to the /ping POST route.
The transformed template in CloudFormation does show a loaded swagger file:
"api": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"Body": {
"info": {
"version": 1570924800000,
"title": "API"
},
"paths": {
"/ping": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PingRequest"
}
}
},
"required": true
},
"x-amazon-apigateway-integration": {
"contentHandling": "CONVERT_TO_TEXT",
"responses": {
"default": {
"statusCode": "200"
}
},
"uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${apiPing.Arn}/invocations"
},
"httpMethod": "POST",
"passthroughBehavior": "when_no_match",
"type": "aws_proxy"
},
"summary": "invoke ping",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PongResponse"
}
}
},
"description": "Successful"
}
},
"operationId": "apiPing"
}
}
},
"openapi": "3.0.1",
"components": {
"schemas": {
"PingRequest": {
"type": "object",
"description": "a ping request",
"properties": {
"ping": {
"type": "string",
"description": "some text"
}
}
},
"PongResponse": {
"type": "object",
"description": "a pong response",
"properties": {
"pong": {
"type": "string",
"description": "some text"
}
}
}
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
]
}
}
}
I'm trying to understand what I may need to change or add to add the integration to the http api. I can't find any clear explanation in the aws documentation.
I have managed to resolve this. aws::serverless::httpapi creates a AWS::ApiGatewayV2::Api resource. This requires a different integration than the previous versioned ApiGateway.
x-amazon-apigateway-integration has a key defined, payloadFormatVersion. Despite documentation suggesting both 1.0 and 2.0 are supported, it seems 2.0 must be used. As such, my x-amazon-apigateway-integration has become the following (I did clean it up a bit):
x-amazon-apigateway-integration:
payloadFormatVersion: "2.0"
httpMethod: "POST"
type: "aws_proxy"
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${apiPing.Arn}/invocations
responses:
default:
statusCode: "200"
connectionType: "INTERNET"
And with this, integration is applied upon deployment.

Swagger API specs Request object design

I have written an api specs following OpenAPI/Swagger Specification -
{
"post": {
"tags": [
"UserController"
],
"operationId": "getUsers",
"parameters": [
{
"name": "accountID",
"in": "path",
"required": true,
"schema": {
"type": "number"
}
},
{
"name": "sortKey",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "sortOrder",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "default response",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/UserResponse"
}
}
}
}
}
}
}
The API Request takes accountId, sortKey and sortOrder. Should they should be wrapped in a Top level request object (getUsersRequest) ? What is the best practice?
{
"GetUsersRequest": {
"accountID": "String",
"sortKey": "String",
"sortOrder": "String"
}
}
vs
{
"accountID": "String",
"sortKey": "String",
"sortOrder": "String"
}
Usually just use the properties. Using a "wrapper" object can be useful if the parameters belong to multiple groups.
For example if you have an api with paging:
/query?filter=findme&page=5&size=5
I see two groups of parameters.
the filter to limit the query result, that is the main purpose of the api.
the page & size parameters, which are more a technical help to limit the amount of results.
you can use an (wrapper) object to easily communicate that two of the three parameters belong together and are used for paging.
as yaml:
/query:
get:
description: ...
parameters:
- name: filter
description: filters the data by the given value
in: query
schema:
type: string
- name: paging
description: page selection
in: query
required: false
schema:
$ref: '#/components/schemas/Paging'
components:
schemas:
Paging:
type: object
properties:
page:
type: integer
size:
type: integer
So in your example you could group sortKey & sortOrder as a view group while accountId is the main parameter of the api.

Adding parameters to api using cloudformation

I tried the cloudformation template that I found here...
https://bl.ocks.org/magnetikonline/c314952045eee8e8375b82bc7ec68e88
It works as expected. But I will like to provide parameters to the post request. My Curl command should look something like this...
curl -d "mynumber=12345" -X POST https://tyin2sswj2.execute-api.us-east-1.amazonaws.com/mycall
How do I handle it at API gateway in the cloudformation template? I have already set the environment variable at lambda function level.
The template that does not work is this...
https://raw.githubusercontent.com/shantanuo/cloudformation/master/updated/lambda_api.tpl.txt
As it is clear that I am not able to pass the "mnumber" variable through the gateway.
I have updated my template and now it deploys function and gateway corretly. And still the URL generated does not work and shows "internal server error" message.
https://raw.githubusercontent.com/shantanuo/cloudformation/master/testapi.tpl.txt
You should change to using HTTP proxy integration.
Here is some info from AWS on proxy integration: https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-http-integrations.html
Try changing your RequestParameters from:
RequestParameters:
method.request.querystring.mnumber: false
to
RequestParameters:
method.request.path.proxy: true
and under integration from:
RequestParameters:
integration.request.querystring.mnumber: "method.request.querystring.mnumber"
to
RequestParameters:
integration.request.path.proxy: 'method.request.path.proxy'
This is a good tutorial on proxy integration with API Gateway:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html
There are two ways you can access mynumber
Method 1 which works best with the SAM serverless API template. Define "API Gateway" and "Lambda". In Lambda definitions, call Events of type API:
This makes it where query strings are automatically picked up due to the event property. The parameters can be found in the event response that is passed into all lambda functions. It can be accessed with multiValueQueryStringParameters or queryStringParameters from the event object.
exports.getByDateHandler = async (event) => {
console.info(event.queryStringParameters);
console.info(event.multiValueQueryStringParameters);
}
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Description",
"Transform": ["AWS::Serverless-2016-10-31"],
"Resources": {
"getByDateFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "src/handlers/getByDate/get-by-date.getByIdHandler",
"Runtime": "nodejs14.x",
"Architectures": ["x86_64"],
"MemorySize": 128,
"Timeout": 100,
"Events": {
"Api": {
"Type": "Api",
"Properties": {
"Path": "/date",
"Method": "GET"
}
}
}
}
}
},
"Outputs": {
"WebEndpoint": {
"Description": "API Gateway endpoint URL for Prod stage",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}
Method 2 which I havent tested is by defining the "Lambda", "API Gateway", "API Resource" and "API Methods". Linking the Lambda using the URI statement under "API Method".
for this method I only have a yaml example
MyLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Description: "Node.js Express REST API"
FunctionName: "get_list_function" (The name in AWS console)
Handler: lambda.handler
Runtime: nodejs12
MemorySize: 128
Role: <ROLE ARN>
Timeout: 60
apiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "example-api-gw"
Description: "Example API"
ProxyResource:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId: !GetAtt apiGateway.RootResourceId
RestApiId: !Ref apiGateway
PathPart: '{proxy+}' OR "a simple string like "PetStore"
apiGatewayRootMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: NONE
HttpMethod: ANY
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
IntegrationResponses:
- StatusCode: 200
Uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations
ResourceId: !Ref ProxyResource
RestApiId: !Ref "apiGateway"

How to define an ECR Lifecycle Policy with CloudFormation

In order to limit the number of images in a repository, I'd like to define a Lifecycle policy. Since all the stack is defined with CloudFormation, I'd like to define this policy too.
For example, my policy could be "keep only the most recent 8 images, no matter if tagged or not".
The solution was pretty easy, but since I could not find any example or similar questions (ECR is not mainstream, I know), let me post here the easy solution that I found, which simply requires to insert the policy as JSON into the CloudFormation definition:
MyRepository:
Type: AWS::ECR::Repository
Properties:
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 1,
"description": "Only keep 8 images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 8
},
"action": { "type": "expire" }
}]
}
Of course this is very simplistic, but it's the starting point that I was looking for
You can also define a reference to your PolicyText and later on your parameters.json stringify your policy.
It would look like something like this:
template.yml
Parameters:
lifecyclePolicyText:
Description: Lifecycle policy content (JSON), the policy content the pre-fixes for the microservices and the kind of policy (CountMoreThan).
Type: String
repositoryName:
Description: ECR Repository Name to which we will apply the lifecycle policies.
Type: String
registryId:
Description: AWS account identification number (12 digits)
Type: String
Default: xxxxx
Resources:
Repository:
Type: AWS::ECR::Repository
Properties:
LifecyclePolicy:
LifecyclePolicyText: !Ref lifecyclePolicyText
RegistryId: !Ref registryId
RepositoryName: !Ref repositoryName
Outputs:
Arn:
Value: !GetAtt Repository.Arn
parameters.json
[
{
"ParameterKey": "lifecyclePolicyText",
"ParameterValue": "{'rules':[{'rulePriority':1,'description':'Only keep 8 images','selection':{'tagStatus':'any','countType':'imageCountMoreThan','countNumber':8},'action':{'type':'expire'}}]}"
},
{
"ParameterKey": "repositoryName",
"ParameterValue": "xxxx"
}
]
| will allow you to add text inline.
AWSTemplateFormatVersion: "2010-09-09"
Resources:
ECRRepo:
Type: AWS::ECR::Repository
Properties:
RepositoryName: "images"
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 2,
"description": "Keep only one untagged image, expire all others",
"selection": {
"tagStatus": "untagged",
"countType": "imageCountMoreThan",
"countNumber": 1
},
"action": {
"type": "expire"
}
}
]
}

How should a server respond to a GET request for a resource that is an unbounded collection in a REST API?

Consider the following URIs where the server returns a representation corresponding to the nth Fibonacci number.
GET /fib/0 ==> { value: 0, _links: { next: { href: '/fib/1' } } }
GET /fib/1 ==> { value: 1, _links: { next: { href: '/fib/2' } } }
GET /fib/2 ==> { value: 1, _links: { next: { href: '/fib/3' } } }
...
GET /fib/73 ==> { value: 806515533049393, _links: { next: { href: '/fib/74' } } }
... etc ...
Given the constraints of a strict interpretation of REST, what should the server return when it receives the following request?
GET /fib
Wikipedia says to return the collection members as links, optionally with details of each member. Obviously you cannot return the whole collection in this case, because it is unbounded. The RESTful APIs we have built with large collections will return the collection paginated. I don't know if this would be a useful thing to return in your use case, but I see you are using HAL, so if you were to, it could look like this for linked data:
{
"_links": {
"self": { "href": "/fib?page=2" },
"next": { "href": "/fib?page=3" },
"prev": { "href": "/fib?page=1" },
"fibs": [
{"href": "/fib/4" },
{"href": "/fib/5" },
{"href": "/fib/6" }
},
}
or this for embedded:
{
"_links": {
"self": { "href": "/fib?page=2" },
"next": { "href": "/fib?page=3" },
"prev": { "href": "/fib?page=1" },
},
_embedded: {
"fibs": [
{
"_links": {"self": "/fib/4" },
"value": 2
},
{
"_links": {"self": "/fib/5" },
"value": 3
},
{
"_links": {"self": "/fib/6" },
"value": 5
}
}
}
You can see a similar example to this in the HAL spec: https://datatracker.ietf.org/doc/html/draft-kelly-json-hal-00#section-6
If the user doesn't specify the page query string param, we return the first page.
If returning the collection (paginated or in it's entirety) doesn't make sense, then I would recommend returning a 405 Method Not Allowed HTTP response code. If you cannot insert items (POST) into the collection either, then maybe return the same 405 response for that too.
The server should return a resource that is represented by /fib or 404 if no such resource is defined/exists. What the resource actually IS depends on the domain and requirements of your application.
According to W3's Dereferencing HTTP URIs, because I cannot return an infinite sequence, it looks like I should return an HTTP 303 - See Other redirect to another URI (e.g. /fib/Information) which contains additional related information about the Fibonacci sequence such as a human-readable description, an algorithm to compute it, or an RDF description of the aggregation. I should support content negotiation to allow consumers to select an appropriate representation.