Skip to content
Open
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
14 changes: 8 additions & 6 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,24 +1041,26 @@ def _get_merged_definitions(
"""
merged_definition_body = source_definition_body.copy()
source_body_paths = merged_definition_body.get("paths") or {}
merged_definition_body["paths"] = source_body_paths

# Normalize API Gateway specific methods such as ANY for both lookup and write-back.
method = editor._normalize_method_name(self.Method)

try:
path_method_body = dict_deep_get(source_body_paths, [self.Path, self.Method]) or {}
path_method_body = dict_deep_get(source_body_paths, [self.Path, method]) or {}
except InvalidValueType as e:
raise InvalidResourceException(api_id, f"Property 'DefinitionBody' is invalid: {e!s}") from e

sam_expect(path_method_body, api_id, f"DefinitionBody.paths.{self.Path}.{self.Method}").to_be_a_map()
sam_expect(path_method_body, api_id, f"DefinitionBody.paths.{self.Path}.{method}").to_be_a_map()

# Normalized version of HTTP Method. It also handle API Gateway specific methods like "ANY"
method = editor._normalize_method_name(self.Method)
dest_definition_body = editor.swagger
generated_path_method_body = dest_definition_body["paths"][self.Path][method]
# this guarantees that the merged definition use SAM generated value for a conflicting key
merged_path_method_body = {**path_method_body, **generated_path_method_body}

if self.Path not in source_body_paths:
source_body_paths[self.Path] = {self.Method: merged_path_method_body}
source_body_paths[self.Path][self.Method] = merged_path_method_body
source_body_paths[self.Path] = {}
source_body_paths[self.Path][method] = merged_path_method_body

return merged_definition_body

Expand Down
55 changes: 55 additions & 0 deletions tests/model/eventsources/test_api_event_source.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from unittest import TestCase
from unittest.mock import Mock, patch

from parameterized import parameterized
from samtranslator.intrinsics.resolver import IntrinsicsResolver
from samtranslator.model.eventsources.push import Api
from samtranslator.model.exceptions import InvalidResourceException
from samtranslator.model.lambda_ import LambdaFunction, LambdaPermission
from samtranslator.swagger.swagger import SwaggerEditor


class ApiEventSource(TestCase):
Expand Down Expand Up @@ -124,6 +127,58 @@ def test_get_permission_with_just_slash(self):

self.assertEqual(arn, "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/")

@parameterized.expand(
[(method, paths) for method in ("get", "any") for paths in ({}, {"paths": None}, {"paths": {}})]
)
def test_merge_definitions_keeps_generated_path(self, method, paths):
self.api_event_source.Method = method
editor = SwaggerEditor(SwaggerEditor.gen_skeleton())
editor.add_lambda_integration("/foo", method, "lambda-uri", {}, {})

merged = self.api_event_source._get_merged_definitions("RestApi", {"swagger": "2.0", **paths}, editor)

self.assertEqual(merged["paths"], editor.swagger["paths"])

@parameterized.expand([("get", "get"), ("any", "x-amazon-apigateway-any-method")])
def test_merge_definitions_preserves_inline_method_fields(self, method, method_key):
self.api_event_source.Method = method
editor = SwaggerEditor(SwaggerEditor.gen_skeleton())
editor.add_lambda_integration("/foo", method, "lambda-uri", {}, {})
source = {
"swagger": "2.0",
"paths": {
"/foo": {
method_key: {
"summary": "Inline operation",
"x-amazon-apigateway-integration": {"type": "http_proxy", "uri": "https://example.com"},
},
"post": {"summary": "Other method"},
},
"/other": {"get": {"summary": "Other path"}},
},
}

merged = self.api_event_source._get_merged_definitions("RestApi", source, editor)

self.assertEqual(set(merged["paths"]["/foo"]), {method_key, "post"})
self.assertEqual(
merged["paths"]["/foo"][method_key],
{"summary": "Inline operation", **editor.swagger["paths"]["/foo"][method_key]},
)
self.assertEqual(merged["paths"]["/foo"]["post"], {"summary": "Other method"})
self.assertEqual(merged["paths"]["/other"], {"get": {"summary": "Other path"}})

def test_merge_definitions_rejects_invalid_inline_any_method(self):
self.api_event_source.Method = "any"
editor = SwaggerEditor(SwaggerEditor.gen_skeleton())
editor.add_lambda_integration("/foo", "any", "lambda-uri", {}, {})
source = {"swagger": "2.0", "paths": {"/foo": {"x-amazon-apigateway-any-method": "invalid"}}}

with self.assertRaisesRegex(
InvalidResourceException, r"DefinitionBody.paths./foo.x-amazon-apigateway-any-method"
):
self.api_event_source._get_merged_definitions("RestApi", source, editor)

def _extract_path_from_arn(self, logical_id, perm):
arn = perm.to_dict().get(logical_id, {}).get("Properties", {}).get("SourceArn", {}).get("Fn::Sub", [])[0]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Transform: AWS::Serverless-2016-10-31

Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
MergeDefinitions: true
Cors: "'*'"
DefinitionBody:
openapi: 3.0.1
info:
title: Example
version: '1'
paths:
/test:
x-amazon-apigateway-any-method:
summary: Inline operation
x-amazon-apigateway-integration:
httpMethod: ANY
type: http_proxy
uri: https://example.com
/other:
get:
responses:
'200':
description: Success
x-amazon-apigateway-integration:
httpMethod: GET
type: http_proxy
uri: https://example.com

MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: python3.11
InlineCode: |
def handler(event, context):
return {"statusCode": 200, "body": "Success"}
Events:
AnyEvent:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /test
Method: ANY
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
"WebhooksApi": {
"Properties": {
"Body": {
"paths": {
"/proxy": {
"x-amazon-apigateway-any-method": {
"responses": {},
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"type": "aws_proxy",
"uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${WebhooksReceiver.Arn}/invocations"
}
}
}
}
},
"swagger": 2,
"x-amazon-apigateway-policy": {
"Version": "2012-10-17"
Expand All @@ -11,9 +25,9 @@
},
"Type": "AWS::ApiGateway::RestApi"
},
"WebhooksApiDeployment472b94a845": {
"WebhooksApiDeploymenta4764457ec": {
"Properties": {
"Description": "RestApi deployment id: 472b94a845dac11081ca2890e848230a4e30a2aa",
"Description": "RestApi deployment id: a4764457ec88823a60b2b3741f89cbdca314b6b1",
"RestApiId": {
"Ref": "WebhooksApi"
},
Expand All @@ -24,7 +38,7 @@
"WebhooksApiliveStage": {
"Properties": {
"DeploymentId": {
"Ref": "WebhooksApiDeployment472b94a845"
"Ref": "WebhooksApiDeploymenta4764457ec"
},
"RestApiId": {
"Ref": "WebhooksApi"
Expand Down
Loading