Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions samtranslator/model/api/http_api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def _construct_http_api(self):
if self.disable_execute_api_endpoint is not None:
self._add_endpoint_configuration()

self._add_description()

if self.definition_uri:
http_api.BodyS3Location = self._construct_body_s3_dict()
elif self.definition_body:
Expand All @@ -124,9 +126,6 @@ def _construct_http_api(self):
"add a 'HttpApi' event to an 'AWS::Serverless::Function'.",
)

if self.description:
http_api.Description = self.description

return http_api

def _add_endpoint_configuration(self):
Expand Down Expand Up @@ -586,6 +585,27 @@ def _construct_stage(self):

return stage

def _add_description(self):
"""Add description to DefinitionBody if Description property is set in SAM"""
if not self.description:
return

if not self.definition_body:
raise InvalidResourceException(
self.logical_id,
"Description works only with inline OpenApi specified in the 'DefinitionBody' property.",
)
if self.definition_body.get("info", {}).get("description"):
raise InvalidResourceException(
self.logical_id,
"Unable to set Description because it is already defined within inline OpenAPI specified in the "
"'DefinitionBody' property.",
)

open_api_editor = OpenApiEditor(self.definition_body)
open_api_editor.add_description(self.description)
self.definition_body = open_api_editor.openapi

def to_cloudformation(self):
"""Generates CloudFormation resources from a SAM HTTP API resource

Expand Down
13 changes: 13 additions & 0 deletions samtranslator/open_api/open_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, doc):
self.security_schemes = self._doc.get("components", {}).get("securitySchemes", {})
self.definitions = self._doc.get("definitions", {})
self.tags = self._doc.get("tags", [])
self.info = self._doc.get("info", {})

def get_path(self, path):
"""
Expand Down Expand Up @@ -521,6 +522,15 @@ def add_cors(

self._doc[self._X_APIGW_CORS] = cors_configuration

def add_description(self, description):
"""Add description in open api definition, if it is not already defined

:param string description: Description of the API
"""
if self.info.get("description"):
return
self.info["description"] = description

def has_api_gateway_cors(self):
if self._doc.get(self._X_APIGW_CORS):
return True
Expand All @@ -544,6 +554,9 @@ def openapi(self):
self._doc.setdefault("components", {})
self._doc["components"]["securitySchemes"] = self.security_schemes

if self.info:
self._doc["info"] = self.info

return copy.deepcopy(self._doc)

@staticmethod
Expand Down
50 changes: 43 additions & 7 deletions tests/model/test_sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,18 +281,54 @@ class TestHttpApiDescription(TestCase):
@patch("boto3.session.Session.region_name", "eu-central-1")
def test_with_no_description(self):
sam_http_api = SamHttpApi("foo")
sam_http_api.DefinitionUri = "s3://foobar/foo.zip"
sam_http_api.DefinitionBody = {
"openapi": "3.0.1",
"paths": {"/foo": {}, "/bar": {}},
"info": {"description": "existing description"},
}

resources = sam_http_api.to_cloudformation(**self.kwargs)
rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
self.assertEqual(rest_api[0].Description, None)
http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "existing description")

@patch("boto3.session.Session.region_name", "eu-central-1")
def test_with_description(self):
def test_with_no_definition_body(self):
sam_http_api = SamHttpApi("foo")
sam_http_api.DefinitionUri = "s3://foobar/foo.zip"
sam_http_api.Description = "my description"

with self.assertRaises(InvalidResourceException) as context:
sam_http_api.to_cloudformation(**self.kwargs)
self.assertEqual(
context.exception.message,
"Resource with id [foo] is invalid. "
"Description works only with inline OpenApi specified in the 'DefinitionBody' property.",
)

@patch("boto3.session.Session.region_name", "eu-central-1")
def test_with_description_defined_in_definition_body(self):
sam_http_api = SamHttpApi("foo")
sam_http_api.DefinitionBody = {
"openapi": "3.0.1",
"paths": {"/foo": {}, "/bar": {}},
"info": {"description": "existing description"},
}
sam_http_api.Description = "new description"

with self.assertRaises(InvalidResourceException) as context:
sam_http_api.to_cloudformation(**self.kwargs)
self.assertEqual(
context.exception.message,
"Resource with id [foo] is invalid. "
"Unable to set Description because it is already defined within inline OpenAPI specified in the "
"'DefinitionBody' property.",
)

@patch("boto3.session.Session.region_name", "eu-central-1")
def test_with_description_not_defined_in_definition_body(self):
sam_http_api = SamHttpApi("foo")
sam_http_api.DefinitionBody = {"openapi": "3.0.1", "paths": {"/foo": {}}, "info": {}}
sam_http_api.Description = "new description"

resources = sam_http_api.to_cloudformation(**self.kwargs)
rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
self.assertEqual(rest_api[0].Description, "my description")
http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "new description")
23 changes: 23 additions & 0 deletions tests/openapi/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,26 @@ def test_must_get_integration_function_if_exists(self):
"HttpApiFunction",
)
self.assertFalse(self.editor.get_integration_function_logical_id("/bar", "get"))


class TestOpenApiEdit_add_description(TestCase):
def setUp(self):
self.original_openapi_with_description = {
"openapi": "3.0.1",
"paths": {},
"info": {"description": "Existing Description"},
}
self.original_openapi_without_description = {
"openapi": "3.0.1",
"paths": {},
}

def test_must_add_description_if_not_defined(self):
editor = OpenApiEditor(self.original_openapi_without_description)
editor.add_description("New Description")
self.assertEqual(editor.openapi["info"]["description"], "New Description")

def test_must_not_add_description_if_already_defined(self):
editor = OpenApiEditor(self.original_openapi_with_description)
editor.add_description("New Description")
self.assertEqual(editor.openapi["info"]["description"], "Existing Description")
5 changes: 4 additions & 1 deletion tests/translator/input/http_api_description.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ Resources:
HttpApi:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionUri: s3://bucket/key
DefinitionBody:
openapi: "3.0.1"
paths:
"/foo": {}
Description: my description

Function:
Expand Down
37 changes: 32 additions & 5 deletions tests/translator/output/aws-cn/http_api_description.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,44 @@
"ApiId": {
"Ref": "HttpApi"
},
"Tags": {
"httpapi:createdBy": "SAM"
},
"StageName": "$default"
}
},
"HttpApi": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"BodyS3Location": {
"Bucket": "bucket",
"Key": "key"
},
"Description": "my description"
"Body": {
"openapi": "3.0.1",
"paths": {
"/foo": {},
"$default": {
"x-amazon-apigateway-any-method": {
"x-amazon-apigateway-integration": {
"type": "aws_proxy",
"httpMethod": "POST",
"payloadFormatVersion": "2.0",
"uri": {
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
}
},
"isDefaultRoute": true,
"responses": {}
}
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"info": {
"description": "my description"
}
}
}
}
}
Expand Down
37 changes: 32 additions & 5 deletions tests/translator/output/aws-us-gov/http_api_description.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,44 @@
"ApiId": {
"Ref": "HttpApi"
},
"Tags": {
"httpapi:createdBy": "SAM"
},
"StageName": "$default"
}
},
"HttpApi": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"BodyS3Location": {
"Bucket": "bucket",
"Key": "key"
},
"Description": "my description"
"Body": {
"openapi": "3.0.1",
"paths": {
"/foo": {},
"$default": {
"x-amazon-apigateway-any-method": {
"x-amazon-apigateway-integration": {
"type": "aws_proxy",
"httpMethod": "POST",
"payloadFormatVersion": "2.0",
"uri": {
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
}
},
"isDefaultRoute": true,
"responses": {}
}
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"info": {
"description": "my description"
}
}
}
}
}
Expand Down
37 changes: 32 additions & 5 deletions tests/translator/output/http_api_description.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,44 @@
"ApiId": {
"Ref": "HttpApi"
},
"Tags": {
"httpapi:createdBy": "SAM"
},
"StageName": "$default"
}
},
"HttpApi": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"BodyS3Location": {
"Bucket": "bucket",
"Key": "key"
},
"Description": "my description"
"Body": {
"openapi": "3.0.1",
"paths": {
"/foo": {},
"$default": {
"x-amazon-apigateway-any-method": {
"x-amazon-apigateway-integration": {
"type": "aws_proxy",
"httpMethod": "POST",
"payloadFormatVersion": "2.0",
"uri": {
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
}
},
"isDefaultRoute": true,
"responses": {}
}
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"info": {
"description": "my description"
}
}
}
}
}
Expand Down