Skip to content

Commit 30f206a

Browse files
authored
fix: Allow Implicit HTTP APIs to write when value of method is None (#2413)
SAM would crash if a the customer was using Implicit APIs that had a method whose value was None/Null. SAM assumed the value would be a dictonary and therefore would crash with a NoneType error when adding a key/value pair.
1 parent 0b2da73 commit 30f206a

10 files changed

+654
-280
lines changed

samtranslator/open_api/open_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ def add_lambda_integration(
222222

223223
for path_item in self.get_conditional_contents(self.paths.get(path)):
224224
# create as Py27Dict and insert key one by one to preserve input order
225+
if path_item[method] is None:
226+
path_item[method] = Py27Dict()
225227
path_item[method][self._X_APIGW_INTEGRATION] = Py27Dict()
226228
path_item[method][self._X_APIGW_INTEGRATION]["type"] = "aws_proxy"
227229
path_item[method][self._X_APIGW_INTEGRATION]["httpMethod"] = "POST"

tests/openapi/test_openapi.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,32 @@ def setUp(self):
203203
"paths": {
204204
"/foo": {"post": {"a": [1, 2, "b"], "responses": {"something": "is already here"}}},
205205
"/bar": {"get": {_X_INTEGRATION: {"a": "b"}}},
206+
"/nullmethod": {"get": None},
206207
},
207208
}
208209

209210
self.editor = OpenApiEditor(self.original_openapi)
210211

212+
def test_must_override_null_path(self):
213+
path = "/nullmethod"
214+
method = "get"
215+
integration_uri = "something"
216+
expected = {
217+
"responses": {},
218+
_X_INTEGRATION: {
219+
"type": "aws_proxy",
220+
"httpMethod": "POST",
221+
"payloadFormatVersion": "2.0",
222+
"uri": integration_uri,
223+
},
224+
}
225+
226+
self.editor.add_lambda_integration(path, method, integration_uri)
227+
228+
self.assertTrue(self.editor.has_path(path, method))
229+
actual = self.editor.openapi["paths"][path][method]
230+
self.assertEqual(expected, actual)
231+
211232
def test_must_add_new_integration_to_new_path(self):
212233
path = "/newpath"
213234
method = "get"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: 'AWS::Serverless-2016-10-31'
3+
Description: A template to test for API condition handling with a mix of explicit and implicit api events.
4+
5+
Resources:
6+
EXAMPLEPARTIALGateway:
7+
Type: AWS::Serverless::HttpApi
8+
Properties:
9+
DefinitionBody:
10+
openapi: 3.0.1
11+
paths:
12+
/my/route:
13+
post: null
14+
EXAMPLEPARTIALLambda:
15+
Type: AWS::Serverless::Function
16+
Properties:
17+
CodeUri: s3://sam-demo-bucket/hello.zip
18+
Runtime: python3.9
19+
Handler: hello.handler
20+
Events:
21+
PostRequest:
22+
Type: HttpApi
23+
Properties:
24+
Path: /my/route
25+
ApiId:
26+
Ref: EXAMPLEPARTIALGateway
27+
Method: POST
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"Parameters": {
3+
"AutoPublishCodeSha256": {
4+
"Type": "String",
5+
"Description": "Sha256 to uniquely identify creation of the lambda",
6+
"Default": "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"
7+
}
8+
},
9+
"Resources": {
10+
"MinimalFunction": {
11+
"Type": "AWS::Lambda::Function",
12+
"Properties": {
13+
"Code": {
14+
"S3Bucket": "sam-demo-bucket",
15+
"S3Key": "hello.zip"
16+
},
17+
"Handler": "hello.handler",
18+
"Role": {
19+
"Fn::GetAtt": [
20+
"MinimalFunctionRole",
21+
"Arn"
22+
]
23+
},
24+
"Runtime": "python2.7",
25+
"Tags": [
26+
{
27+
"Key": "lambda:createdBy",
28+
"Value": "SAM"
29+
}
30+
]
31+
}
32+
},
33+
"MinimalFunctionVersion6b86b273ff": {
34+
"Type": "AWS::Lambda::Version",
35+
"DeletionPolicy": "Retain",
36+
"Properties": {
37+
"Description": "sam-testing",
38+
"FunctionName": {
39+
"Ref": "MinimalFunction"
40+
}
41+
}
42+
},
43+
"MinimalFunctionAliaslive": {
44+
"Type": "AWS::Lambda::Alias",
45+
"Properties": {
46+
"Name": "live",
47+
"FunctionName": {
48+
"Ref": "MinimalFunction"
49+
},
50+
"FunctionVersion": {
51+
"Fn::GetAtt": [
52+
"MinimalFunctionVersion6b86b273ff",
53+
"Version"
54+
]
55+
}
56+
}
57+
},
58+
"MinimalFunctionRole": {
59+
"Type": "AWS::IAM::Role",
60+
"Properties": {
61+
"AssumeRolePolicyDocument": {
62+
"Version": "2012-10-17",
63+
"Statement": [
64+
{
65+
"Action": [
66+
"sts:AssumeRole"
67+
],
68+
"Effect": "Allow",
69+
"Principal": {
70+
"Service": [
71+
"lambda.amazonaws.com"
72+
]
73+
}
74+
}
75+
]
76+
},
77+
"ManagedPolicyArns": [
78+
"arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
79+
],
80+
"Tags": [
81+
{
82+
"Key": "lambda:createdBy",
83+
"Value": "SAM"
84+
}
85+
]
86+
}
87+
}
88+
}
89+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"AWSTemplateFormatVersion": "2010-09-09",
3+
"Description": "A template to test for API condition handling with a mix of explicit and implicit api events.",
4+
"Resources": {
5+
"EXAMPLEPARTIALLambda": {
6+
"Type": "AWS::Lambda::Function",
7+
"Properties": {
8+
"Code": {
9+
"S3Bucket": "sam-demo-bucket",
10+
"S3Key": "hello.zip"
11+
},
12+
"Handler": "hello.handler",
13+
"Role": {
14+
"Fn::GetAtt": [
15+
"EXAMPLEPARTIALLambdaRole",
16+
"Arn"
17+
]
18+
},
19+
"Runtime": "python3.9",
20+
"Tags": [
21+
{
22+
"Key": "lambda:createdBy",
23+
"Value": "SAM"
24+
}
25+
]
26+
}
27+
},
28+
"EXAMPLEPARTIALLambdaRole": {
29+
"Type": "AWS::IAM::Role",
30+
"Properties": {
31+
"AssumeRolePolicyDocument": {
32+
"Version": "2012-10-17",
33+
"Statement": [
34+
{
35+
"Action": [
36+
"sts:AssumeRole"
37+
],
38+
"Effect": "Allow",
39+
"Principal": {
40+
"Service": [
41+
"lambda.amazonaws.com"
42+
]
43+
}
44+
}
45+
]
46+
},
47+
"ManagedPolicyArns": [
48+
"arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
49+
],
50+
"Tags": [
51+
{
52+
"Key": "lambda:createdBy",
53+
"Value": "SAM"
54+
}
55+
]
56+
}
57+
},
58+
"EXAMPLEPARTIALLambdaPostRequestPermission": {
59+
"Type": "AWS::Lambda::Permission",
60+
"Properties": {
61+
"Action": "lambda:InvokeFunction",
62+
"FunctionName": {
63+
"Ref": "EXAMPLEPARTIALLambda"
64+
},
65+
"Principal": "apigateway.amazonaws.com",
66+
"SourceArn": {
67+
"Fn::Sub": [
68+
"arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/my/route",
69+
{
70+
"__ApiId__": {
71+
"Ref": "EXAMPLEPARTIALGateway"
72+
},
73+
"__Stage__": "*"
74+
}
75+
]
76+
}
77+
}
78+
},
79+
"EXAMPLEPARTIALGateway": {
80+
"Type": "AWS::ApiGatewayV2::Api",
81+
"Properties": {
82+
"Body": {
83+
"openapi": "3.0.1",
84+
"paths": {
85+
"/my/route": {
86+
"post": {
87+
"x-amazon-apigateway-integration": {
88+
"httpMethod": "POST",
89+
"type": "aws_proxy",
90+
"uri": {
91+
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EXAMPLEPARTIALLambda.Arn}/invocations"
92+
},
93+
"payloadFormatVersion": "2.0"
94+
},
95+
"responses": {}
96+
}
97+
}
98+
},
99+
"tags": [
100+
{
101+
"name": "httpapi:createdBy",
102+
"x-amazon-apigateway-tag-value": "SAM"
103+
}
104+
]
105+
}
106+
}
107+
},
108+
"EXAMPLEPARTIALGatewayApiGatewayDefaultStage": {
109+
"Type": "AWS::ApiGatewayV2::Stage",
110+
"Properties": {
111+
"ApiId": {
112+
"Ref": "EXAMPLEPARTIALGateway"
113+
},
114+
"StageName": "$default",
115+
"Tags": {
116+
"httpapi:createdBy": "SAM"
117+
},
118+
"AutoDeploy": true
119+
}
120+
}
121+
}
122+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"Parameters": {
3+
"AutoPublishCodeSha256": {
4+
"Type": "String",
5+
"Description": "Sha256 to uniquely identify creation of the lambda",
6+
"Default": "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"
7+
}
8+
},
9+
"Resources": {
10+
"MinimalFunction": {
11+
"Type": "AWS::Lambda::Function",
12+
"Properties": {
13+
"Code": {
14+
"S3Bucket": "sam-demo-bucket",
15+
"S3Key": "hello.zip"
16+
},
17+
"Handler": "hello.handler",
18+
"Role": {
19+
"Fn::GetAtt": [
20+
"MinimalFunctionRole",
21+
"Arn"
22+
]
23+
},
24+
"Runtime": "python2.7",
25+
"Tags": [
26+
{
27+
"Key": "lambda:createdBy",
28+
"Value": "SAM"
29+
}
30+
]
31+
}
32+
},
33+
"MinimalFunctionVersion6b86b273ff": {
34+
"Type": "AWS::Lambda::Version",
35+
"DeletionPolicy": "Retain",
36+
"Properties": {
37+
"Description": "sam-testing",
38+
"FunctionName": {
39+
"Ref": "MinimalFunction"
40+
}
41+
}
42+
},
43+
"MinimalFunctionAliaslive": {
44+
"Type": "AWS::Lambda::Alias",
45+
"Properties": {
46+
"Name": "live",
47+
"FunctionName": {
48+
"Ref": "MinimalFunction"
49+
},
50+
"FunctionVersion": {
51+
"Fn::GetAtt": [
52+
"MinimalFunctionVersion6b86b273ff",
53+
"Version"
54+
]
55+
}
56+
}
57+
},
58+
"MinimalFunctionRole": {
59+
"Type": "AWS::IAM::Role",
60+
"Properties": {
61+
"AssumeRolePolicyDocument": {
62+
"Version": "2012-10-17",
63+
"Statement": [
64+
{
65+
"Action": [
66+
"sts:AssumeRole"
67+
],
68+
"Effect": "Allow",
69+
"Principal": {
70+
"Service": [
71+
"lambda.amazonaws.com"
72+
]
73+
}
74+
}
75+
]
76+
},
77+
"ManagedPolicyArns": [
78+
"arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
79+
],
80+
"Tags": [
81+
{
82+
"Key": "lambda:createdBy",
83+
"Value": "SAM"
84+
}
85+
]
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)