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
2 changes: 1 addition & 1 deletion samtranslator/open_api/open_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ def has_api_gateway_cors(self): # type: ignore[no-untyped-def]
return False

@property
def openapi(self): # type: ignore[no-untyped-def]
def openapi(self) -> Dict[str, Any]:
"""
Returns a **copy** of the OpenApi specification as a dictionary.

Expand Down
21 changes: 14 additions & 7 deletions samtranslator/plugins/api/implicit_http_api_plugin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Any, Dict, Optional, cast

from samtranslator.model.intrinsics import make_conditional
from samtranslator.model.naming import GeneratedLogicalId
from samtranslator.plugins.api.implicit_api_plugin import ImplicitApiPlugin
from samtranslator.public.open_api import OpenApiEditor
from samtranslator.public.exceptions import InvalidEventException
from samtranslator.public.sdk.resource import SamResourceType, SamResource
from samtranslator.sdk.template import SamTemplate
from samtranslator.validator.value_validator import sam_expect


class ImplicitHttpApiPlugin(ImplicitApiPlugin):
Expand Down Expand Up @@ -103,7 +107,7 @@ def _process_api_events( # type: ignore[no-untyped-def]

self._add_api_to_swagger(logicalId, event_properties, template) # type: ignore[no-untyped-call]
if "RouteSettings" in event_properties:
self._add_route_settings_to_api(logicalId, event_properties, template, condition) # type: ignore[no-untyped-call]
self._add_route_settings_to_api(logicalId, event_properties, template, condition)
api_events[logicalId] = event

# We could have made changes to the Events structure. Write it back to function
Expand All @@ -120,25 +124,27 @@ def _add_implicit_api_id_if_necessary(self, event_properties): # type: ignore[n
if "ApiId" not in event_properties:
event_properties["ApiId"] = {"Ref": self.implicit_api_logical_id}

def _generate_implicit_api_resource(self): # type: ignore[no-untyped-def]
def _generate_implicit_api_resource(self) -> Dict[str, Any]:
"""
Uses the implicit API in this file to generate an Implicit API resource
"""
return ImplicitHttpApiResource().to_dict() # type: ignore[no-untyped-call]
return ImplicitHttpApiResource().to_dict()

def _get_api_definition_from_editor(self, editor): # type: ignore[no-untyped-def]
def _get_api_definition_from_editor(self, editor: OpenApiEditor) -> Dict[str, Any]:
"""
Helper function to return the OAS definition from the editor
"""
return editor.openapi

def _get_api_resource_type_name(self): # type: ignore[no-untyped-def]
def _get_api_resource_type_name(self) -> str:
"""
Returns the type of API resource
"""
return "AWS::Serverless::HttpApi"

def _add_route_settings_to_api(self, event_id, event_properties, template, condition): # type: ignore[no-untyped-def]
def _add_route_settings_to_api(
self, event_id: str, event_properties: Dict[str, Any], template: SamTemplate, condition: Optional[str]
) -> None:
"""
Adds the RouteSettings for this path/method from the given event to the RouteSettings configuration
on the AWS::Serverless::HttpApi that this refers to.
Expand All @@ -150,7 +156,7 @@ def _add_route_settings_to_api(self, event_id, event_properties, template, condi
"""

api_id = self._get_api_id(event_properties) # type: ignore[no-untyped-call]
resource = template.get(api_id)
resource = cast(SamResource, template.get(api_id)) # TODO: make this not an assumption

path = event_properties["Path"]
method = event_properties["Method"]
Expand All @@ -165,6 +171,7 @@ def _add_route_settings_to_api(self, event_id, event_properties, template, condi
event_route_settings = event_properties.get("RouteSettings", {})
if condition:
event_route_settings = make_conditional(condition, event_properties.get("RouteSettings", {}))
sam_expect(event_route_settings, event_id, "RouteSettings", is_sam_event=True).to_be_a_map()

# Merge event-level and api-level RouteSettings properties
api_route_settings.setdefault(route, {})
Expand Down
2 changes: 1 addition & 1 deletion samtranslator/plugins/api/implicit_rest_api_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def _generate_implicit_api_resource(self): # type: ignore[no-untyped-def]
"""
Uses the implicit API in this file to generate an Implicit API resource
"""
return ImplicitApiResource().to_dict() # type: ignore[no-untyped-call]
return ImplicitApiResource().to_dict()

def _get_api_definition_from_editor(self, editor): # type: ignore[no-untyped-def]
"""
Expand Down
2 changes: 1 addition & 1 deletion samtranslator/sdk/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def valid(self): # type: ignore[no-untyped-def]

return SamResourceType.has_value(self.type) # type: ignore[no-untyped-call]

def to_dict(self): # type: ignore[no-untyped-def]
def to_dict(self) -> Dict[str, Any]:

if self.valid(): # type: ignore[no-untyped-call]
# Touch a resource dictionary ONLY if it is valid
Expand Down
2 changes: 1 addition & 1 deletion samtranslator/sdk/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def set(self, logical_id: str, resource: Union[SamResource, Dict[str, Any]]) ->

resource_dict = resource
if isinstance(resource, SamResource):
resource_dict = resource.to_dict() # type: ignore[no-untyped-call]
resource_dict = resource.to_dict()

self.resources[logical_id] = resource_dict

Expand Down
27 changes: 21 additions & 6 deletions samtranslator/validator/value_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from enum import Enum
from typing import Generic, Optional, TypeVar

from samtranslator.model.exceptions import InvalidResourceException
from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException


class ExpectedType(Enum):
Expand All @@ -17,13 +17,20 @@ class ExpectedType(Enum):

class _ResourcePropertyValueValidator(Generic[T]):
value: Optional[T]
resource_logical_id: str
resource_logical_id: Optional[str]
event_id: Optional[str]
property_identifier: str

def __init__(self, value: Optional[T], resource_logical_id: str, property_identifier: str) -> None:
def __init__(
self, value: Optional[T], resource_id: str, property_identifier: str, is_sam_event: bool = False
) -> None:
self.value = value
self.resource_logical_id = resource_logical_id
self.property_identifier = property_identifier
self.resource_logical_id, self.event_id = (None, None)
if is_sam_event:
self.event_id = resource_id
else:
self.resource_logical_id = resource_id

def to_be_a(self, expected_type: ExpectedType, message: Optional[str] = "") -> Optional[T]:
"""
Expand All @@ -35,7 +42,11 @@ def to_be_a(self, expected_type: ExpectedType, message: Optional[str] = "") -> O
if not isinstance(self.value, type_class):
if not message:
message = f"Property '{self.property_identifier}' should be a {type_description}."
raise InvalidResourceException(self.resource_logical_id, message)
if self.event_id:
raise InvalidEventException(self.event_id, message)
if self.resource_logical_id:
raise InvalidResourceException(self.resource_logical_id, message)
raise RuntimeError("event_id and resource_logical_id are both None")
# mypy is not smart to derive class from expected_type.value[1], ignore types:
return self.value # type: ignore

Expand All @@ -48,7 +59,11 @@ def to_not_be_none(self, message: Optional[str] = "") -> T:
if self.value is None:
if not message:
message = f"Property '{self.property_identifier}' is required."
raise InvalidResourceException(self.resource_logical_id, message)
if self.event_id:
raise InvalidEventException(self.event_id, message)
if self.resource_logical_id:
raise InvalidResourceException(self.resource_logical_id, message)
raise RuntimeError("event_id and resource_logical_id are both None")
return self.value

#
Expand Down
28 changes: 28 additions & 0 deletions tests/translator/input/error_http_api_event_invalid_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,31 @@ Resources:
Type: HttpApi
Properties:
ApiId: !Ref SomeApi

HttpApiFunctionInvalidRouteSettings:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://bucket/key
Handler: index.handler
Runtime: python3.7
Events:
ApiNullRouteSettings:
Type: HttpApi
Properties:
RouteSettings:
Path: /path
Method: POST

HttpApiFunctionInvalidRouteSettings2:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://bucket/key
Handler: index.handler
Runtime: python3.7
Events:
ApiRouteSettingsNotMap:
Type: HttpApi
Properties:
RouteSettings: this should be a map
Path: /path2
Method: POST
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. ApiId must be a valid reference to an 'AWS::Serverless::HttpApi' resource in same template.",
"errors": [
{
"errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. ApiId must be a valid reference to an 'AWS::Serverless::HttpApi' resource in same template."
}
]
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 3. Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. ApiId must be a valid reference to an 'AWS::Serverless::HttpApi' resource in same template. Resource with id [HttpApiFunctionInvalidRouteSettings] is invalid. Event with id [ApiNullRouteSettings] is invalid. Property 'RouteSettings' should be a map. Resource with id [HttpApiFunctionInvalidRouteSettings2] is invalid. Event with id [ApiRouteSettingsNotMap] is invalid. Property 'RouteSettings' should be a map."
}