Skip to content

Commit c30ca97

Browse files
committed
Fix: Description in AWS::Serverless::HttpApi (aws#1884)
* Fix: Description in AWS::Serverless::HttpApi * Update _set to _add
1 parent 76306b1 commit c30ca97

File tree

8 files changed

+202
-26
lines changed

8 files changed

+202
-26
lines changed

samtranslator/model/api/http_api_generator.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ def _construct_http_api(self):
112112
if self.disable_execute_api_endpoint is not None:
113113
self._add_endpoint_configuration()
114114

115+
self._add_description()
116+
115117
if self.definition_uri:
116118
http_api.BodyS3Location = self._construct_body_s3_dict()
117119
elif self.definition_body:
@@ -124,9 +126,6 @@ def _construct_http_api(self):
124126
"add a 'HttpApi' event to an 'AWS::Serverless::Function'.",
125127
)
126128

127-
if self.description:
128-
http_api.Description = self.description
129-
130129
return http_api
131130

132131
def _add_endpoint_configuration(self):
@@ -586,6 +585,27 @@ def _construct_stage(self):
586585

587586
return stage
588587

588+
def _add_description(self):
589+
"""Add description to DefinitionBody if Description property is set in SAM"""
590+
if not self.description:
591+
return
592+
593+
if not self.definition_body:
594+
raise InvalidResourceException(
595+
self.logical_id,
596+
"Description works only with inline OpenApi specified in the 'DefinitionBody' property.",
597+
)
598+
if self.definition_body.get("info", {}).get("description"):
599+
raise InvalidResourceException(
600+
self.logical_id,
601+
"Unable to set Description because it is already defined within inline OpenAPI specified in the "
602+
"'DefinitionBody' property.",
603+
)
604+
605+
open_api_editor = OpenApiEditor(self.definition_body)
606+
open_api_editor.add_description(self.description)
607+
self.definition_body = open_api_editor.openapi
608+
589609
def to_cloudformation(self):
590610
"""Generates CloudFormation resources from a SAM HTTP API resource
591611

samtranslator/open_api/open_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def __init__(self, doc):
4646
self.security_schemes = self._doc.get("components", {}).get("securitySchemes", {})
4747
self.definitions = self._doc.get("definitions", {})
4848
self.tags = self._doc.get("tags", [])
49+
self.info = self._doc.get("info", {})
4950

5051
def get_path(self, path):
5152
"""
@@ -521,6 +522,15 @@ def add_cors(
521522

522523
self._doc[self._X_APIGW_CORS] = cors_configuration
523524

525+
def add_description(self, description):
526+
"""Add description in open api definition, if it is not already defined
527+
528+
:param string description: Description of the API
529+
"""
530+
if self.info.get("description"):
531+
return
532+
self.info["description"] = description
533+
524534
def has_api_gateway_cors(self):
525535
if self._doc.get(self._X_APIGW_CORS):
526536
return True
@@ -544,6 +554,9 @@ def openapi(self):
544554
self._doc.setdefault("components", {})
545555
self._doc["components"]["securitySchemes"] = self.security_schemes
546556

557+
if self.info:
558+
self._doc["info"] = self.info
559+
547560
return copy.deepcopy(self._doc)
548561

549562
@staticmethod

tests/model/test_sam_resources.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -281,18 +281,54 @@ class TestHttpApiDescription(TestCase):
281281
@patch("boto3.session.Session.region_name", "eu-central-1")
282282
def test_with_no_description(self):
283283
sam_http_api = SamHttpApi("foo")
284-
sam_http_api.DefinitionUri = "s3://foobar/foo.zip"
284+
sam_http_api.DefinitionBody = {
285+
"openapi": "3.0.1",
286+
"paths": {"/foo": {}, "/bar": {}},
287+
"info": {"description": "existing description"},
288+
}
285289

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

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

299+
with self.assertRaises(InvalidResourceException) as context:
300+
sam_http_api.to_cloudformation(**self.kwargs)
301+
self.assertEqual(
302+
context.exception.message,
303+
"Resource with id [foo] is invalid. "
304+
"Description works only with inline OpenApi specified in the 'DefinitionBody' property.",
305+
)
306+
307+
@patch("boto3.session.Session.region_name", "eu-central-1")
308+
def test_with_description_defined_in_definition_body(self):
309+
sam_http_api = SamHttpApi("foo")
310+
sam_http_api.DefinitionBody = {
311+
"openapi": "3.0.1",
312+
"paths": {"/foo": {}, "/bar": {}},
313+
"info": {"description": "existing description"},
314+
}
315+
sam_http_api.Description = "new description"
316+
317+
with self.assertRaises(InvalidResourceException) as context:
318+
sam_http_api.to_cloudformation(**self.kwargs)
319+
self.assertEqual(
320+
context.exception.message,
321+
"Resource with id [foo] is invalid. "
322+
"Unable to set Description because it is already defined within inline OpenAPI specified in the "
323+
"'DefinitionBody' property.",
324+
)
325+
326+
@patch("boto3.session.Session.region_name", "eu-central-1")
327+
def test_with_description_not_defined_in_definition_body(self):
328+
sam_http_api = SamHttpApi("foo")
329+
sam_http_api.DefinitionBody = {"openapi": "3.0.1", "paths": {"/foo": {}}, "info": {}}
330+
sam_http_api.Description = "new description"
331+
296332
resources = sam_http_api.to_cloudformation(**self.kwargs)
297-
rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
298-
self.assertEqual(rest_api[0].Description, "my description")
333+
http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
334+
self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "new description")

tests/openapi/test_openapi.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,26 @@ def test_must_get_integration_function_if_exists(self):
414414
"HttpApiFunction",
415415
)
416416
self.assertFalse(self.editor.get_integration_function_logical_id("/bar", "get"))
417+
418+
419+
class TestOpenApiEdit_add_description(TestCase):
420+
def setUp(self):
421+
self.original_openapi_with_description = {
422+
"openapi": "3.0.1",
423+
"paths": {},
424+
"info": {"description": "Existing Description"},
425+
}
426+
self.original_openapi_without_description = {
427+
"openapi": "3.0.1",
428+
"paths": {},
429+
}
430+
431+
def test_must_add_description_if_not_defined(self):
432+
editor = OpenApiEditor(self.original_openapi_without_description)
433+
editor.add_description("New Description")
434+
self.assertEqual(editor.openapi["info"]["description"], "New Description")
435+
436+
def test_must_not_add_description_if_already_defined(self):
437+
editor = OpenApiEditor(self.original_openapi_with_description)
438+
editor.add_description("New Description")
439+
self.assertEqual(editor.openapi["info"]["description"], "Existing Description")

tests/translator/input/http_api_description.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ Resources:
22
HttpApi:
33
Type: AWS::Serverless::HttpApi
44
Properties:
5-
DefinitionUri: s3://bucket/key
5+
DefinitionBody:
6+
openapi: "3.0.1"
7+
paths:
8+
"/foo": {}
69
Description: my description
710

811
Function:

tests/translator/output/aws-cn/http_api_description.json

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,44 @@
7979
"ApiId": {
8080
"Ref": "HttpApi"
8181
},
82+
"Tags": {
83+
"httpapi:createdBy": "SAM"
84+
},
8285
"StageName": "$default"
8386
}
8487
},
8588
"HttpApi": {
8689
"Type": "AWS::ApiGatewayV2::Api",
8790
"Properties": {
88-
"BodyS3Location": {
89-
"Bucket": "bucket",
90-
"Key": "key"
91-
},
92-
"Description": "my description"
91+
"Body": {
92+
"openapi": "3.0.1",
93+
"paths": {
94+
"/foo": {},
95+
"$default": {
96+
"x-amazon-apigateway-any-method": {
97+
"x-amazon-apigateway-integration": {
98+
"type": "aws_proxy",
99+
"httpMethod": "POST",
100+
"payloadFormatVersion": "2.0",
101+
"uri": {
102+
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
103+
}
104+
},
105+
"isDefaultRoute": true,
106+
"responses": {}
107+
}
108+
}
109+
},
110+
"tags": [
111+
{
112+
"name": "httpapi:createdBy",
113+
"x-amazon-apigateway-tag-value": "SAM"
114+
}
115+
],
116+
"info": {
117+
"description": "my description"
118+
}
119+
}
93120
}
94121
}
95122
}

tests/translator/output/aws-us-gov/http_api_description.json

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,44 @@
7979
"ApiId": {
8080
"Ref": "HttpApi"
8181
},
82+
"Tags": {
83+
"httpapi:createdBy": "SAM"
84+
},
8285
"StageName": "$default"
8386
}
8487
},
8588
"HttpApi": {
8689
"Type": "AWS::ApiGatewayV2::Api",
8790
"Properties": {
88-
"BodyS3Location": {
89-
"Bucket": "bucket",
90-
"Key": "key"
91-
},
92-
"Description": "my description"
91+
"Body": {
92+
"openapi": "3.0.1",
93+
"paths": {
94+
"/foo": {},
95+
"$default": {
96+
"x-amazon-apigateway-any-method": {
97+
"x-amazon-apigateway-integration": {
98+
"type": "aws_proxy",
99+
"httpMethod": "POST",
100+
"payloadFormatVersion": "2.0",
101+
"uri": {
102+
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
103+
}
104+
},
105+
"isDefaultRoute": true,
106+
"responses": {}
107+
}
108+
}
109+
},
110+
"tags": [
111+
{
112+
"name": "httpapi:createdBy",
113+
"x-amazon-apigateway-tag-value": "SAM"
114+
}
115+
],
116+
"info": {
117+
"description": "my description"
118+
}
119+
}
93120
}
94121
}
95122
}

tests/translator/output/http_api_description.json

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,44 @@
7979
"ApiId": {
8080
"Ref": "HttpApi"
8181
},
82+
"Tags": {
83+
"httpapi:createdBy": "SAM"
84+
},
8285
"StageName": "$default"
8386
}
8487
},
8588
"HttpApi": {
8689
"Type": "AWS::ApiGatewayV2::Api",
8790
"Properties": {
88-
"BodyS3Location": {
89-
"Bucket": "bucket",
90-
"Key": "key"
91-
},
92-
"Description": "my description"
91+
"Body": {
92+
"openapi": "3.0.1",
93+
"paths": {
94+
"/foo": {},
95+
"$default": {
96+
"x-amazon-apigateway-any-method": {
97+
"x-amazon-apigateway-integration": {
98+
"type": "aws_proxy",
99+
"httpMethod": "POST",
100+
"payloadFormatVersion": "2.0",
101+
"uri": {
102+
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
103+
}
104+
},
105+
"isDefaultRoute": true,
106+
"responses": {}
107+
}
108+
}
109+
},
110+
"tags": [
111+
{
112+
"name": "httpapi:createdBy",
113+
"x-amazon-apigateway-tag-value": "SAM"
114+
}
115+
],
116+
"info": {
117+
"description": "my description"
118+
}
119+
}
93120
}
94121
}
95122
}

0 commit comments

Comments
 (0)