diff --git a/samtranslator/model/naming.py b/samtranslator/model/naming.py deleted file mode 100644 index 750410059..000000000 --- a/samtranslator/model/naming.py +++ /dev/null @@ -1,14 +0,0 @@ -class GeneratedLogicalId(object): - """ - Class to generate LogicalIDs for various scenarios. SAM generates LogicalIds for new resources based on code - that is spread across the translator codebase. It becomes to difficult to audit them and to standardize - the process. This class will generate LogicalIds for various use cases. - """ - - @staticmethod - def implicit_api() -> str: - return "ServerlessRestApi" - - @staticmethod - def implicit_http_api() -> str: - return "ServerlessHttpApi" diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index 742e82c91..86173048c 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -1,7 +1,7 @@ import copy from abc import ABCMeta, abstractmethod -from typing import Any, Dict, Optional, Type, Union +from typing import Any, Dict, Generic, Optional, Tuple, Type, TypeVar, Union from samtranslator.metrics.method_decorator import cw_timer from samtranslator.model.intrinsics import make_combined_condition @@ -13,9 +13,13 @@ from samtranslator.public.sdk.template import SamTemplate from samtranslator.swagger.swagger import SwaggerEditor from samtranslator.utils.py27hash_fix import Py27Dict +from samtranslator.validator.value_validator import sam_expect -class ImplicitApiPlugin(BasePlugin, metaclass=ABCMeta): +T = TypeVar("T", bound=Union[Type[OpenApiEditor], Type[SwaggerEditor]]) + + +class ImplicitApiPlugin(BasePlugin, Generic[T], metaclass=ABCMeta): """ This plugin provides Implicit API shorthand syntax in the SAM Spec. https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api @@ -37,18 +41,20 @@ class ImplicitApiPlugin(BasePlugin, metaclass=ABCMeta): """ - implicit_api_logical_id: str # "ServerlessRestApi" or "ServerlessHttpApi" - implicit_api_condition: str # "ServerlessHttpApiCondition" or "ServerlessRestApiCondition" - api_event_type: str # "HttpApi" or "Api" - api_type: str # SamResourceType - api_id_property: str # "ApiId" or "RestApiId" - editor: Union[Type[OpenApiEditor], Type[SwaggerEditor]] + # Name of the event property name to referring api id in the event source. + API_ID_EVENT_PROPERTY: str + # The logical id of the implicit API resource + IMPLICIT_API_LOGICAL_ID: str + IMPLICIT_API_CONDITION: str + API_EVENT_TYPE: str + SERVERLESS_API_RESOURCE_TYPE: str + EDITOR_CLASS: T - def __init__(self, name: str) -> None: + def __init__(self) -> None: """ - Initialize the plugin + Initialize the plugin. """ - super(ImplicitApiPlugin, self).__init__(name) + super().__init__(self._name()) self.existing_implicit_api_resource: Optional[SamResource] = None # dict containing condition (or None) for each resource path+method for all APIs. dict format: @@ -56,12 +62,41 @@ def __init__(self, name: str) -> None: self.api_conditions: Dict[str, Any] = {} self.api_deletion_policies: Dict[str, Any] = {} self.api_update_replace_policies: Dict[str, Any] = {} - self._setup_api_properties() + + @classmethod + def _name(cls) -> str: + return cls.__name__ @abstractmethod - def _setup_api_properties(self) -> None: + def _process_api_events( + self, + function: SamResource, + api_events: Dict[str, Dict[str, Any]], + template: SamTemplate, + condition: Optional[str] = None, + deletion_policy: Optional[str] = None, + update_replace_policy: Optional[str] = None, + ) -> None: """ - Abatract method to set up properties that are distinct to this plugin + Actually process given API events. Iteratively adds the APIs to Swagger JSON in the respective Serverless::Api + resource from the template + + :param SamResource function: SAM function containing the API events to be processed + :param dict api_events: API Events extracted from the function. These events will be processed + :param SamTemplate template: SAM Template where Serverless::Api resources can be found + :param str condition: optional; this is the condition that is on the resource with the API event + """ + + @abstractmethod + def _get_api_definition_from_editor(self, editor): # type: ignore[no-untyped-def] + """ + Required function that returns the api body from the respective editor + """ + + @abstractmethod + def _generate_implicit_api_resource(self) -> Dict[str, Any]: + """ + Helper function implemented by child classes that create a new implicit API resource """ @cw_timer(prefix="Plugin-ImplicitApi") @@ -83,9 +118,9 @@ def on_before_transform_template(self, template_dict): # type: ignore[no-untype # If the customer has explicitly defined a resource with the id of "ServerlessRestApi", # capture it. If the template ends up not defining any implicit api's, instead of just # removing the "ServerlessRestApi" resource, we just restore what the author defined. - self.existing_implicit_api_resource = copy.deepcopy(template.get(self.implicit_api_logical_id)) + self.existing_implicit_api_resource = copy.deepcopy(template.get(self.IMPLICIT_API_LOGICAL_ID)) - template.set(self.implicit_api_logical_id, self._generate_implicit_api_resource()) + template.set(self.IMPLICIT_API_LOGICAL_ID, self._generate_implicit_api_resource()) errors = [] for logicalId, resource in template.iterate( @@ -100,7 +135,7 @@ def on_before_transform_template(self, template_dict): # type: ignore[no-untype continue try: - self._process_api_events( # type: ignore[no-untyped-call] + self._process_api_events( resource, api_events, template, condition, deletion_policy, update_replace_policy ) @@ -116,6 +151,17 @@ def on_before_transform_template(self, template_dict): # type: ignore[no-untype if len(errors) > 0: raise InvalidDocumentException(errors) + def _add_implicit_api_id_if_necessary(self, event_properties): # type: ignore[no-untyped-def] + """ + Events for implicit APIs will *not* have the RestApiId property. Absence of this property means this event + is associated with the Serverless::Api ImplicitAPI resource. This method solifies this assumption by adding + RestApiId property to events that don't have them. + + :param dict event_properties: Dictionary of event properties + """ + if self.API_ID_EVENT_PROPERTY not in event_properties: + event_properties[self.API_ID_EVENT_PROPERTY] = {"Ref": self.IMPLICIT_API_LOGICAL_ID} + def _get_api_events(self, resource): # type: ignore[no-untyped-def] """ Method to return a dictionary of API Events on the resource @@ -141,35 +187,11 @@ def _get_api_events(self, resource): # type: ignore[no-untyped-def] api_events = Py27Dict() for event_id, event in resource.properties["Events"].items(): - if event and isinstance(event, dict) and event.get("Type") == self.api_event_type: + if event and isinstance(event, dict) and event.get("Type") == self.API_EVENT_TYPE: api_events[event_id] = event return api_events - @abstractmethod - def _process_api_events( # type: ignore[no-untyped-def] - self, function, api_events, template, condition=None, deletion_policy=None, update_replace_policy=None - ): - """ - Actually process given API events. Iteratively adds the APIs to Swagger JSON in the respective Serverless::Api - resource from the template - - :param SamResource function: SAM function containing the API events to be processed - :param dict api_events: API Events extracted from the function. These events will be processed - :param SamTemplate template: SAM Template where Serverless::Api resources can be found - :param str condition: optional; this is the condition that is on the resource with the API event - """ - - @abstractmethod - def _add_implicit_api_id_if_necessary(self, event_properties): # type: ignore[no-untyped-def] - """ - Events for implicit APIs will *not* have the RestApiId property. Absence of this property means this event - is associated with the Serverless::Api ImplicitAPI resource. This method solifies this assumption by adding - RestApiId property to events that don't have them. - - :param dict event_properties: Dictionary of event properties - """ - def _add_api_to_swagger(self, event_id, event_properties, template): # type: ignore[no-untyped-def] """ Adds the API path/method from the given event to the Swagger JSON of Serverless::Api resource this event @@ -181,7 +203,7 @@ def _add_api_to_swagger(self, event_id, event_properties, template): # type: ig """ # Need to grab the AWS::Serverless::Api resource for this API event and update its Swagger definition - api_id = self._get_api_id(event_properties) # type: ignore[no-untyped-call] + api_id = self._get_api_id(event_properties) # As of right now, this is for backwards compatability. SAM fails if you have an event type "Api" but that # references "AWS::Serverless::HttpApi". If you do the opposite, SAM still outputs a valid template. Example of that @@ -191,14 +213,14 @@ def _add_api_to_swagger(self, event_id, event_properties, template): # type: ig is_referencing_http_from_api_event = ( not template.get(api_id) or template.get(api_id).type == "AWS::Serverless::HttpApi" - and not template.get(api_id).type == self.api_type + and not template.get(api_id).type == self.SERVERLESS_API_RESOURCE_TYPE ) # RestApiId is not pointing to a valid API resource if isinstance(api_id, dict) or is_referencing_http_from_api_event: raise InvalidEventException( event_id, - f"{self.api_id_property} must be a valid reference to an '{self._get_api_resource_type_name()}'" + f"{self.API_ID_EVENT_PROPERTY} must be a valid reference to an '{self.SERVERLESS_API_RESOURCE_TYPE}'" " resource in same template.", ) @@ -207,7 +229,7 @@ def _add_api_to_swagger(self, event_id, event_properties, template): # type: ig if not ( resource and isinstance(resource.properties, dict) - and self.editor.is_valid(resource.properties.get("DefinitionBody")) + and self.EDITOR_CLASS.is_valid(resource.properties.get("DefinitionBody")) ): # This does not have an inline Swagger. Nothing can be done about it. return @@ -227,19 +249,19 @@ def _add_api_to_swagger(self, event_id, event_properties, template): # type: ig path = event_properties["Path"] method = event_properties["Method"] - editor = self.editor(swagger) + editor = self.EDITOR_CLASS(swagger) editor.add_path(path, method) resource.properties["DefinitionBody"] = self._get_api_definition_from_editor(editor) # type: ignore[no-untyped-call] template.set(api_id, resource) - def _get_api_id(self, event_properties): # type: ignore[no-untyped-def] + def _get_api_id(self, event_properties: Dict[str, Any]) -> Any: """ Get API logical id from API event properties. Handles case where API id is not specified or is a reference to a logical id. """ - api_id = event_properties.get(self.api_id_property) + api_id = event_properties.get(self.API_ID_EVENT_PROPERTY) return Api.get_rest_api_id_string(api_id) def _maybe_add_condition_to_implicit_api(self, template_dict): # type: ignore[no-untyped-def] @@ -248,12 +270,12 @@ def _maybe_add_condition_to_implicit_api(self, template_dict): # type: ignore[n :param dict template_dict: SAM template dictionary """ # Short-circuit if template doesn't have any functions with implicit API events - if not self.api_conditions.get(self.implicit_api_logical_id, {}): + if not self.api_conditions.get(self.IMPLICIT_API_LOGICAL_ID, {}): return # Add a condition to the API resource IFF all of its resource+methods are associated with serverless functions # containing conditions. - implicit_api_conditions = self.api_conditions[self.implicit_api_logical_id] + implicit_api_conditions = self.api_conditions[self.IMPLICIT_API_LOGICAL_ID] all_resource_method_conditions = { condition for _, method_conditions in implicit_api_conditions.items() @@ -262,7 +284,7 @@ def _maybe_add_condition_to_implicit_api(self, template_dict): # type: ignore[n at_least_one_resource_method = len(all_resource_method_conditions) > 0 all_resource_methods_contain_conditions = None not in all_resource_method_conditions if at_least_one_resource_method and all_resource_methods_contain_conditions: - implicit_api_resource = template_dict.get("Resources").get(self.implicit_api_logical_id) + implicit_api_resource = template_dict.get("Resources").get(self.IMPLICIT_API_LOGICAL_ID) if len(all_resource_method_conditions) == 1: condition = all_resource_method_conditions.pop() implicit_api_resource["Condition"] = condition @@ -270,9 +292,9 @@ def _maybe_add_condition_to_implicit_api(self, template_dict): # type: ignore[n # If multiple functions with multiple different conditions reference the Implicit Api, we need to # aggregate those conditions in order to conditionally create the Implicit Api. See RFC: # https://github.com/awslabs/serverless-application-model/issues/758 - implicit_api_resource["Condition"] = self.implicit_api_condition + implicit_api_resource["Condition"] = self.IMPLICIT_API_CONDITION self._add_combined_condition_to_template( # type: ignore[no-untyped-call] - template_dict, self.implicit_api_condition, all_resource_method_conditions + template_dict, self.IMPLICIT_API_CONDITION, all_resource_method_conditions ) def _maybe_add_deletion_policy_to_implicit_api(self, template_dict): # type: ignore[no-untyped-def] @@ -281,7 +303,7 @@ def _maybe_add_deletion_policy_to_implicit_api(self, template_dict): # type: ig :param dict template_dict: SAM template dictionary """ # Short-circuit if template doesn't have any functions with implicit API events - implicit_api_deletion_policies = self.api_deletion_policies.get(self.implicit_api_logical_id) + implicit_api_deletion_policies = self.api_deletion_policies.get(self.IMPLICIT_API_LOGICAL_ID) if not implicit_api_deletion_policies: return @@ -301,7 +323,7 @@ def _maybe_add_deletion_policy_to_implicit_api(self, template_dict): # type: ig if iterated_policy == "Delete": contains_delete = True if at_least_one_resource_method and one_resource_method_contains_deletion_policy: - implicit_api_resource = template_dict.get("Resources").get(self.implicit_api_logical_id) + implicit_api_resource = template_dict.get("Resources").get(self.IMPLICIT_API_LOGICAL_ID) if contains_retain: implicit_api_resource["DeletionPolicy"] = "Retain" elif contains_delete: @@ -313,7 +335,7 @@ def _maybe_add_update_replace_policy_to_implicit_api(self, template_dict): # ty :param dict template_dict: SAM template dictionary """ # Short-circuit if template doesn't have any functions with implicit API events - implicit_api_update_replace_policies = self.api_update_replace_policies.get(self.implicit_api_logical_id) + implicit_api_update_replace_policies = self.api_update_replace_policies.get(self.IMPLICIT_API_LOGICAL_ID) if not implicit_api_update_replace_policies: return @@ -336,7 +358,7 @@ def _maybe_add_update_replace_policy_to_implicit_api(self, template_dict): # ty if iterated_policy == "Delete": contains_delete = True if at_least_one_resource_method and one_resource_method_contains_update_replace_policy: - implicit_api_resource = template_dict.get("Resources").get(self.implicit_api_logical_id) + implicit_api_resource = template_dict.get("Resources").get(self.IMPLICIT_API_LOGICAL_ID) if contains_retain: implicit_api_resource["UpdateReplacePolicy"] = "Retain" elif contains_snapshot: @@ -377,12 +399,12 @@ def _maybe_add_conditions_to_implicit_api_paths(self, template): # type: ignore that composite condition is added to the resource path. """ - for api_id, api in template.iterate({self.api_type}): + for api_id, api in template.iterate({self.SERVERLESS_API_RESOURCE_TYPE}): if not api.properties.get("__MANAGE_SWAGGER"): continue swagger = api.properties.get("DefinitionBody") - editor = self.editor(swagger) + editor = self.EDITOR_CLASS(swagger) for path in editor.iter_on_path(): all_method_conditions = {condition for _, condition in self.api_conditions[api_id][path].items()} @@ -401,12 +423,6 @@ def _maybe_add_conditions_to_implicit_api_paths(self, template): # type: ignore api.properties["DefinitionBody"] = self._get_api_definition_from_editor(editor) # type: ignore[no-untyped-call] # TODO make static method template.set(api_id, api) - @abstractmethod - def _get_api_definition_from_editor(self, editor): # type: ignore[no-untyped-def] - """ - Required function that returns the api body from the respective editor - """ - def _path_condition_name(self, api_id, path): # type: ignore[no-untyped-def] """ Generate valid condition logical id from the given API logical id and swagger resource path. @@ -427,22 +443,47 @@ def _maybe_remove_implicit_api(self, template): # type: ignore[no-untyped-def] """ # Remove Implicit API resource if no paths got added - implicit_api_resource = template.get(self.implicit_api_logical_id) + implicit_api_resource = template.get(self.IMPLICIT_API_LOGICAL_ID) if implicit_api_resource and len(implicit_api_resource.properties["DefinitionBody"]["paths"]) == 0: # If there's no implicit api and the author defined a "ServerlessRestApi" # resource, restore it if self.existing_implicit_api_resource: - template.set(self.implicit_api_logical_id, self.existing_implicit_api_resource) + template.set(self.IMPLICIT_API_LOGICAL_ID, self.existing_implicit_api_resource) else: - template.delete(self.implicit_api_logical_id) - - def _generate_implicit_api_resource(self) -> Dict[str, Any]: - """ - Helper function implemented by child classes that create a new implicit API resource - """ + template.delete(self.IMPLICIT_API_LOGICAL_ID) + + def _validate_api_event(self, event_id: str, event_properties: Dict[str, Any]) -> Tuple[str, str, str]: + """Validate and return api_id, path, method.""" + api_id = self._get_api_id(event_properties) + path = event_properties.get("Path") + method = event_properties.get("Method") + + sam_expect(path, event_id, "Path", is_sam_event=True).to_not_be_none() + sam_expect(method, event_id, "Method", is_sam_event=True).to_not_be_none() + + return ( + # !Ref is resolved by this time. If it is not a string, we can't parse/use this Api. + sam_expect(api_id, event_id, self.API_ID_EVENT_PROPERTY, is_sam_event=True).to_be_a_string(), + sam_expect(path, event_id, "Path", is_sam_event=True).to_be_a_string(), + sam_expect(method, event_id, "Method", is_sam_event=True).to_be_a_string(), + ) - def _get_api_resource_type_name(self) -> str: - """ - Returns the type of API resource - """ + def _update_resource_attributes_from_api_event( + self, + api_id: str, + path: str, + method: str, + condition: Optional[str], + deletion_policy: Optional[str], + update_replace_policy: Optional[str], + ) -> None: + api_dict_condition = self.api_conditions.setdefault(api_id, {}) + method_conditions = api_dict_condition.setdefault(path, {}) + method_conditions[method] = condition + + api_dict_deletion = self.api_deletion_policies.setdefault(api_id, set()) + api_dict_deletion.add(deletion_policy) + + api_dict_update_replace = self.api_update_replace_policies.setdefault(api_id, set()) + api_dict_update_replace.add(update_replace_policy) diff --git a/samtranslator/plugins/api/implicit_http_api_plugin.py b/samtranslator/plugins/api/implicit_http_api_plugin.py index cb3e7c880..995f50221 100644 --- a/samtranslator/plugins/api/implicit_http_api_plugin.py +++ b/samtranslator/plugins/api/implicit_http_api_plugin.py @@ -1,16 +1,14 @@ -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, Optional, Type, 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): +class ImplicitHttpApiPlugin(ImplicitApiPlugin[Type[OpenApiEditor]]): """ This plugin provides Implicit Http API shorthand syntax in the SAM Spec. @@ -28,26 +26,22 @@ class ImplicitHttpApiPlugin(ImplicitApiPlugin): in OpenApi. Does **not** configure the API by any means. """ - def __init__(self) -> None: - """ - Initializes the plugin - """ - super(ImplicitHttpApiPlugin, self).__init__(ImplicitHttpApiPlugin.__name__) - - def _setup_api_properties(self) -> None: - """ - Sets up properties that are distinct to this plugin - """ - self.implicit_api_logical_id = GeneratedLogicalId.implicit_http_api() - self.implicit_api_condition = "ServerlessHttpApiCondition" - self.api_event_type = "HttpApi" - self.api_type = SamResourceType.HttpApi.value - self.api_id_property = "ApiId" - self.editor = OpenApiEditor - - def _process_api_events( # type: ignore[no-untyped-def] - self, function, api_events, template, condition=None, deletion_policy=None, update_replace_policy=None - ): + API_ID_EVENT_PROPERTY = "ApiId" + IMPLICIT_API_LOGICAL_ID = "ServerlessHttpApi" + IMPLICIT_API_CONDITION = "ServerlessHttpApiCondition" + API_EVENT_TYPE = "HttpApi" + SERVERLESS_API_RESOURCE_TYPE = SamResourceType.HttpApi.value + EDITOR_CLASS = OpenApiEditor + + def _process_api_events( + self, + function: SamResource, + api_events: Dict[str, Dict[str, Any]], + template: SamTemplate, + condition: Optional[str] = None, + deletion_policy: Optional[str] = None, + update_replace_policy: Optional[str] = None, + ) -> None: """ Actually process given HTTP API events. Iteratively adds the APIs to OpenApi JSON in the respective AWS::Serverless::HttpApi resource from the template @@ -58,21 +52,16 @@ def _process_api_events( # type: ignore[no-untyped-def] :param str condition: optional; this is the condition that is on the function with the API event """ - for logicalId, event in api_events.items(): + for event_id, event in api_events.items(): # api_events only contains HttpApi events event_properties = event.get("Properties", {}) - if not isinstance(event_properties, dict): - raise InvalidEventException( - logicalId, - "Event 'Properties' must be an Object. If you're using YAML, this may be an indentation issue.", - ) - + sam_expect(event_properties, event_id, "", is_sam_event=True).to_be_a_map("Properties should be a map.") if not event_properties: - event["Properties"] = event_properties + event["Properties"] = event_properties # We are updating its Properties + self._add_implicit_api_id_if_necessary(event_properties) # type: ignore[no-untyped-call] - api_id = self._get_api_id(event_properties) # type: ignore[no-untyped-call] path = event_properties.get("Path", "") method = event_properties.get("Method", "") # If no path and method specified, add the $default path and ANY method @@ -81,49 +70,20 @@ def _process_api_events( # type: ignore[no-untyped-def] method = "x-amazon-apigateway-any-method" event_properties["Path"] = path event_properties["Method"] = method - elif not path or not method: - key = "Path" if not path else "Method" - raise InvalidEventException(logicalId, "Event is missing key '{}'.".format(key)) - - if not isinstance(path, str) or not isinstance(method, str): - key = "Path" if not isinstance(path, str) else "Method" - raise InvalidEventException(logicalId, "Api Event must have a String specified for '{}'.".format(key)) - # !Ref is resolved by this time. If it is not a string, we can't parse/use this Api. - if api_id and not isinstance(api_id, str): - raise InvalidEventException( - logicalId, "Api Event's ApiId must be a string referencing an Api in the same template." - ) + api_id, path, method = self._validate_api_event(event_id, event_properties) + self._update_resource_attributes_from_api_event( + api_id, path, method, condition, deletion_policy, update_replace_policy + ) - api_dict_condition = self.api_conditions.setdefault(api_id, {}) - method_conditions = api_dict_condition.setdefault(path, {}) - method_conditions[method] = condition - - api_dict_deletion = self.api_deletion_policies.setdefault(api_id, set()) - api_dict_deletion.add(deletion_policy) - - api_dict_update_replace = self.api_update_replace_policies.setdefault(api_id, set()) - api_dict_update_replace.add(update_replace_policy) - - self._add_api_to_swagger(logicalId, event_properties, template) # type: ignore[no-untyped-call] + self._add_api_to_swagger(event_id, event_properties, template) # type: ignore[no-untyped-call] if "RouteSettings" in event_properties: - self._add_route_settings_to_api(logicalId, event_properties, template, condition) - api_events[logicalId] = event + self._add_route_settings_to_api(event_id, event_properties, template, condition) + api_events[event_id] = event # We could have made changes to the Events structure. Write it back to function function.properties["Events"].update(api_events) - def _add_implicit_api_id_if_necessary(self, event_properties): # type: ignore[no-untyped-def] - """ - Events for implicit APIs will *not* have the RestApiId property. Absence of this property means this event - is associated with the AWS::Serverless::Api ImplicitAPI resource. - This method solidifies this assumption by adding RestApiId property to events that don't have them. - - :param dict event_properties: Dictionary of event properties - """ - if "ApiId" not in event_properties: - event_properties["ApiId"] = {"Ref": self.implicit_api_logical_id} - def _generate_implicit_api_resource(self) -> Dict[str, Any]: """ Uses the implicit API in this file to generate an Implicit API resource @@ -136,12 +96,6 @@ def _get_api_definition_from_editor(self, editor: OpenApiEditor) -> Dict[str, An """ return editor.openapi - 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: str, event_properties: Dict[str, Any], template: SamTemplate, condition: Optional[str] ) -> None: @@ -155,7 +109,7 @@ def _add_route_settings_to_api( :param string condition: Condition on this HttpApi event (if any) """ - api_id = self._get_api_id(event_properties) # type: ignore[no-untyped-call] + api_id = self._get_api_id(event_properties) resource = cast(SamResource, template.get(api_id)) # TODO: make this not an assumption path = event_properties["Path"] diff --git a/samtranslator/plugins/api/implicit_rest_api_plugin.py b/samtranslator/plugins/api/implicit_rest_api_plugin.py index cba19d068..2455ea6f5 100644 --- a/samtranslator/plugins/api/implicit_rest_api_plugin.py +++ b/samtranslator/plugins/api/implicit_rest_api_plugin.py @@ -1,13 +1,13 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional, Type -from samtranslator.model.naming import GeneratedLogicalId from samtranslator.plugins.api.implicit_api_plugin import ImplicitApiPlugin from samtranslator.public.swagger import SwaggerEditor -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 ImplicitRestApiPlugin(ImplicitApiPlugin): +class ImplicitRestApiPlugin(ImplicitApiPlugin[Type[SwaggerEditor]]): """ This plugin provides Implicit API shorthand syntax in the SAM Spec. https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api @@ -26,29 +26,24 @@ class ImplicitRestApiPlugin(ImplicitApiPlugin): * API Event Source (In Core Translator): ONLY adds the Lambda Integration ARN to appropriate method/path in Swagger. Does **not** configure the API by any means. - """ - def __init__(self) -> None: - """ - Initialize the plugin - """ - super(ImplicitRestApiPlugin, self).__init__(ImplicitRestApiPlugin.__name__) - - def _setup_api_properties(self) -> None: - """ - Sets up properties that are distinct to this plugin - """ - self.implicit_api_logical_id = GeneratedLogicalId.implicit_api() - self.implicit_api_condition = "ServerlessRestApiCondition" - self.api_event_type = "Api" - self.api_type = SamResourceType.Api.value - self.api_id_property = "RestApiId" - self.editor = SwaggerEditor - - def _process_api_events( # type: ignore[no-untyped-def] - self, function, api_events, template, condition=None, deletion_policy=None, update_replace_policy=None - ): + API_ID_EVENT_PROPERTY = "RestApiId" + IMPLICIT_API_LOGICAL_ID = "ServerlessRestApi" + IMPLICIT_API_CONDITION = "ServerlessRestApiCondition" + API_EVENT_TYPE = "Api" + SERVERLESS_API_RESOURCE_TYPE = SamResourceType.Api.value + EDITOR_CLASS = SwaggerEditor + + def _process_api_events( + self, + function: SamResource, + api_events: Dict[str, Dict[str, Any]], + template: SamTemplate, + condition: Optional[str] = None, + deletion_policy: Optional[str] = None, + update_replace_policy: Optional[str] = None, + ) -> None: """ Actually process given API events. Iteratively adds the APIs to Swagger JSON in the respective Serverless::Api resource from the template @@ -59,84 +54,40 @@ def _process_api_events( # type: ignore[no-untyped-def] :param str condition: optional; this is the condition that is on the function with the API event """ - for logicalId, event in api_events.items(): + for event_id, event in api_events.items(): event_properties = event.get("Properties", {}) if not event_properties: continue - if not isinstance(event_properties, dict): - raise InvalidEventException( - logicalId, - "Event 'Properties' must be an Object. If you're using YAML, this may be an indentation issue.", - ) + sam_expect(event_properties, event_id, "", is_sam_event=True).to_be_a_map("Properties should be a map.") self._add_implicit_api_id_if_necessary(event_properties) # type: ignore[no-untyped-call] - api_id = self._get_api_id(event_properties) # type: ignore[no-untyped-call] - try: - path = event_properties["Path"] - method = event_properties["Method"] - except KeyError as e: - raise InvalidEventException(logicalId, "Event is missing key {}.".format(e)) - - if not isinstance(path, str): - raise InvalidEventException(logicalId, "Api Event must have a String specified for 'Path'.") - if not isinstance(method, str): - raise InvalidEventException(logicalId, "Api Event must have a String specified for 'Method'.") - - # !Ref is resolved by this time. If it is not a string, we can't parse/use this Api. - if api_id and not isinstance(api_id, str): - raise InvalidEventException( - logicalId, "Api Event's RestApiId must be a string referencing an Api in the same template." - ) - - api_dict_condition = self.api_conditions.setdefault(api_id, {}) - method_conditions = api_dict_condition.setdefault(path, {}) - method_conditions[method] = condition + api_id, path, method = self._validate_api_event(event_id, event_properties) + self._update_resource_attributes_from_api_event( + api_id, path, method, condition, deletion_policy, update_replace_policy + ) - api_dict_deletion = self.api_deletion_policies.setdefault(api_id, set()) - api_dict_deletion.add(deletion_policy) + self._add_api_to_swagger(event_id, event_properties, template) # type: ignore[no-untyped-call] - api_dict_update_replace = self.api_update_replace_policies.setdefault(api_id, set()) - api_dict_update_replace.add(update_replace_policy) - - self._add_api_to_swagger(logicalId, event_properties, template) # type: ignore[no-untyped-call] - - api_events[logicalId] = event + api_events[event_id] = event # We could have made changes to the Events structure. Write it back to function function.properties["Events"].update(api_events) - def _add_implicit_api_id_if_necessary(self, event_properties): # type: ignore[no-untyped-def] - """ - Events for implicit APIs will *not* have the RestApiId property. Absence of this property means this event - is associated with the Serverless::Api ImplicitAPI resource. This method solifies this assumption by adding - RestApiId property to events that don't have them. - - :param dict event_properties: Dictionary of event properties - """ - if "RestApiId" not in event_properties: - event_properties["RestApiId"] = {"Ref": self.implicit_api_logical_id} - def _generate_implicit_api_resource(self) -> Dict[str, Any]: """ Uses the implicit API in this file to generate an Implicit API resource """ return ImplicitApiResource().to_dict() - def _get_api_definition_from_editor(self, editor): # type: ignore[no-untyped-def] + def _get_api_definition_from_editor(self, editor: SwaggerEditor) -> Dict[str, Any]: """ Helper function to return the OAS definition from the editor """ return editor.swagger - def _get_api_resource_type_name(self) -> str: - """ - Returns the type of API resource - """ - return "AWS::Serverless::Api" - class ImplicitApiResource(SamResource): """ diff --git a/samtranslator/validator/value_validator.py b/samtranslator/validator/value_validator.py index e090251fe..f9a435c4b 100644 --- a/samtranslator/validator/value_validator.py +++ b/samtranslator/validator/value_validator.py @@ -73,12 +73,20 @@ def to_not_be_none(self, message: Optional[str] = "") -> T: # alias methods: # def to_be_a_map(self, message: Optional[str] = "") -> Dict[str, Any]: + """ + Return the value with type hint "Dict[str, Any]". + Raise InvalidResourceException/InvalidEventException if the value is not. + """ return cast(Dict[str, Any], self.to_be_a(ExpectedType.MAP, message)) def to_be_a_list(self, message: Optional[str] = "") -> T: return self.to_be_a(ExpectedType.LIST, message) def to_be_a_list_of(self, expected_type: ExpectedType, message: Optional[str] = "") -> T: + """ + Return the value with type hint "List[T]". + Raise InvalidResourceException/InvalidEventException if the value is not. + """ value = self.to_be_a(ExpectedType.LIST, message) for index, item in enumerate(value): # type: ignore sam_expect( @@ -86,11 +94,19 @@ def to_be_a_list_of(self, expected_type: ExpectedType, message: Optional[str] = ).to_be_a(expected_type, message) return value - def to_be_a_string(self, message: Optional[str] = "") -> T: - return self.to_be_a(ExpectedType.STRING, message) + def to_be_a_string(self, message: Optional[str] = "") -> str: + """ + Return the value with type hint "str". + Raise InvalidResourceException/InvalidEventException if the value is not. + """ + return cast(str, self.to_be_a(ExpectedType.STRING, message)) - def to_be_an_integer(self, message: Optional[str] = "") -> T: - return self.to_be_a(ExpectedType.INTEGER, message) + def to_be_an_integer(self, message: Optional[str] = "") -> int: + """ + Return the value with type hint "int". + Raise InvalidResourceException/InvalidEventException if the value is not. + """ + return cast(int, self.to_be_a(ExpectedType.INTEGER, message)) sam_expect = _ResourcePropertyValueValidator diff --git a/tests/plugins/api/test_implicit_api_plugin.py b/tests/plugins/api/test_implicit_api_plugin.py index e3e27fabd..679241f21 100644 --- a/tests/plugins/api/test_implicit_api_plugin.py +++ b/tests/plugins/api/test_implicit_api_plugin.py @@ -355,8 +355,8 @@ def setUp(self): def test_must_work_with_api_events(self): api_events = { - "Api1": {"Type": "Api", "Properties": {"Path": "/", "Method": "GET"}}, - "Api2": {"Type": "Api", "Properties": {"Path": "/foo", "Method": "POST"}}, + "Api1": {"Type": "Api", "Properties": {"Path": "/", "Method": "GET", "RestApiId": "RestApi"}}, + "Api2": {"Type": "Api", "Properties": {"Path": "/foo", "Method": "POST", "RestApiId": "RestApi"}}, } template = Mock() @@ -367,13 +367,16 @@ def test_must_work_with_api_events(self): self.plugin._process_api_events(function, api_events, template) self.plugin._add_implicit_api_id_if_necessary.assert_has_calls( - [call({"Path": "/", "Method": "GET"}), call({"Path": "/foo", "Method": "POST"})] + [ + call({"Path": "/", "Method": "GET", "RestApiId": "RestApi"}), + call({"Path": "/foo", "Method": "POST", "RestApiId": "RestApi"}), + ] ) self.plugin._add_api_to_swagger.assert_has_calls( [ - call("Api1", {"Path": "/", "Method": "GET"}, template), - call("Api2", {"Path": "/foo", "Method": "POST"}, template), + call("Api1", {"Path": "/", "Method": "GET", "RestApiId": "RestApi"}, template), + call("Api2", {"Path": "/foo", "Method": "POST", "RestApiId": "RestApi"}, template), ] ) @@ -435,16 +438,23 @@ def test_must_verify_path_is_string(self): def test_must_skip_events_without_properties(self): - api_events = {"Api1": {"Type": "Api"}, "Api2": {"Type": "Api", "Properties": {"Path": "/", "Method": "GET"}}} + api_events = { + "Api1": {"Type": "Api", "RestApiId": "RestApi"}, + "Api2": {"Type": "Api", "Properties": {"RestApiId": "RestApi", "Path": "/", "Method": "GET"}}, + } template = Mock() function = SamResource({"Type": SamResourceType.Function.value, "Properties": {"Events": api_events}}) self.plugin._process_api_events(function, api_events, template) - self.plugin._add_implicit_api_id_if_necessary.assert_has_calls([call({"Path": "/", "Method": "GET"})]) + self.plugin._add_implicit_api_id_if_necessary.assert_has_calls( + [call({"Path": "/", "Method": "GET", "RestApiId": "RestApi"})] + ) - self.plugin._add_api_to_swagger.assert_has_calls([call("Api2", {"Path": "/", "Method": "GET"}, template)]) + self.plugin._add_api_to_swagger.assert_has_calls( + [call("Api2", {"Path": "/", "Method": "GET", "RestApiId": "RestApi"}, template)] + ) def test_must_retain_side_effect_of_modifying_events(self): """ @@ -452,8 +462,22 @@ def test_must_retain_side_effect_of_modifying_events(self): """ api_events = { - "Api1": {"Type": "Api", "Properties": {"Path": "/", "Method": "get"}}, - "Api2": {"Type": "Api", "Properties": {"Path": "/foo", "Method": "post"}}, + "Api1": { + "Type": "Api", + "Properties": { + "Path": "/", + "Method": "get", + "RestApiId": "RestApi", + }, + }, + "Api2": { + "Type": "Api", + "Properties": { + "Path": "/foo", + "Method": "post", + "RestApiId": "RestApi", + }, + }, } template = Mock() @@ -465,7 +489,7 @@ def test_must_retain_side_effect_of_modifying_events(self): "Api1": "Intentionally setting this value to a string for testing. " "This should be replaced by API Event after processing", "Api2": "must be replaced", - } + }, }, } ) @@ -479,15 +503,21 @@ def add_key_to_event(event_properties): self.plugin._process_api_events(function, api_events, template) # Side effect must be visible after call returns on the input object - self.assertEqual(api_events["Api1"]["Properties"], {"Path": "/", "Method": "get", "Key": "Value"}) - self.assertEqual(api_events["Api2"]["Properties"], {"Path": "/foo", "Method": "post", "Key": "Value"}) + self.assertEqual( + api_events["Api1"]["Properties"], {"Path": "/", "Method": "get", "Key": "Value", "RestApiId": "RestApi"} + ) + self.assertEqual( + api_events["Api2"]["Properties"], {"Path": "/foo", "Method": "post", "Key": "Value", "RestApiId": "RestApi"} + ) # Every Event object inside the SamResource class must be entirely replaced by input api_events with side effect self.assertEqual( - function.properties["Events"]["Api1"]["Properties"], {"Path": "/", "Method": "get", "Key": "Value"} + function.properties["Events"]["Api1"]["Properties"], + {"Path": "/", "Method": "get", "Key": "Value", "RestApiId": "RestApi"}, ) self.assertEqual( - function.properties["Events"]["Api2"]["Properties"], {"Path": "/foo", "Method": "post", "Key": "Value"} + function.properties["Events"]["Api2"]["Properties"], + {"Path": "/foo", "Method": "post", "Key": "Value", "RestApiId": "RestApi"}, ) # Subsequent calls must be made with the side effect. This is important. @@ -496,13 +526,13 @@ def add_key_to_event(event_properties): call( "Api1", # Side effects should be visible here - {"Path": "/", "Method": "get", "Key": "Value"}, + {"Path": "/", "Method": "get", "Key": "Value", "RestApiId": "RestApi"}, template, ), call( "Api2", # Side effects should be visible here - {"Path": "/foo", "Method": "post", "Key": "Value"}, + {"Path": "/foo", "Method": "post", "Key": "Value", "RestApiId": "RestApi"}, template, ), ] @@ -554,7 +584,7 @@ def test_must_add_path_method_to_swagger_of_api_resource(self, SwaggerEditorMock editor_mock = Mock() SwaggerEditorMock.return_value = editor_mock editor_mock.swagger = updated_swagger - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() @@ -592,7 +622,7 @@ def test_must_work_with_rest_api_id_as_string(self, SwaggerEditorMock): editor_mock = Mock() SwaggerEditorMock.return_value = editor_mock editor_mock.swagger = updated_swagger - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() @@ -645,7 +675,7 @@ def test_must_skip_invalid_swagger(self, SwaggerEditorMock): SwaggerEditorMock.is_valid = Mock() SwaggerEditorMock.is_valid.return_value = False - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() @@ -668,7 +698,7 @@ def test_must_skip_if_definition_body_is_not_present(self, SwaggerEditorMock): SwaggerEditorMock.is_valid = Mock() SwaggerEditorMock.is_valid.return_value = False - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() @@ -690,7 +720,7 @@ def test_must_skip_if_api_resource_properties_are_invalid(self, SwaggerEditorMoc mock_api = SamResource({"Type": "AWS::Serverless::Api", "Properties": "this is not a valid property"}) SwaggerEditorMock.is_valid = Mock() - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() @@ -723,7 +753,7 @@ def test_must_skip_if_api_manage_swagger_flag_is_false(self, SwaggerEditorMock): ) SwaggerEditorMock.is_valid = Mock() - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() @@ -755,7 +785,7 @@ def test_must_skip_if_api_manage_swagger_flag_is_not_present(self, SwaggerEditor ) SwaggerEditorMock.is_valid = Mock() - self.plugin.editor = SwaggerEditorMock + self.plugin.EDITOR_CLASS = SwaggerEditorMock template_mock = Mock() template_mock.get = Mock() diff --git a/tests/translator/output/error_api_event_import_vaule_reference.json b/tests/translator/output/error_api_event_import_vaule_reference.json index c15d26e6c..5199cc9bd 100644 --- a/tests/translator/output/error_api_event_import_vaule_reference.json +++ b/tests/translator/output/error_api_event_import_vaule_reference.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_condition_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_condition_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_condition_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_condition_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_find_in_map_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_find_in_map_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_find_in_map_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_find_in_map_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_getatt_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_getatt_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_getatt_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_getatt_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_join_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_join_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_join_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_join_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_select_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_select_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_select_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_select_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_sub_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_sub_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_sub_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_sub_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_api_swagger_integration_with_transform_intrinsic_api_id.json b/tests/translator/output/error_api_swagger_integration_with_transform_intrinsic_api_id.json index 5acf3e408..217216708 100644 --- a/tests/translator/output/error_api_swagger_integration_with_transform_intrinsic_api_id.json +++ b/tests/translator/output/error_api_swagger_integration_with_transform_intrinsic_api_id.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_function_api_invalid_properties.json b/tests/translator/output/error_function_api_invalid_properties.json index 9850aad2f..a2b221708 100644 --- a/tests/translator/output/error_function_api_invalid_properties.json +++ b/tests/translator/output/error_function_api_invalid_properties.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Event with id [Api] is invalid. Event 'Properties' must be an Object. If you're using YAML, this may be an indentation issue." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Event with id [Api] is invalid. Properties should be a map." } diff --git a/tests/translator/output/error_function_invalid_api_event.json b/tests/translator/output/error_function_invalid_api_event.json index a76e1e7c9..f609c3699 100644 --- a/tests/translator/output/error_function_invalid_api_event.json +++ b/tests/translator/output/error_function_invalid_api_event.json @@ -1,8 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [FunctionApiInvalidProperties] is invalid. Event with id [ApiEvent] is invalid. Event 'Properties' must be an Object. If you're using YAML, this may be an indentation issue. Resource with id [FunctionApiMethodArray] is invalid. Event with id [ApiEvent] is invalid. Api Event must have a String specified for 'Method'. Resource with id [FunctionApiNoMethod] is invalid. Event with id [ApiEvent] is invalid. Event is missing key 'Method'. Resource with id [FunctionApiNoPath] is invalid. Event with id [ApiEvent] is invalid. Event is missing key 'Path'. Resource with id [FunctionApiPathArray] is invalid. Event with id [ApiEvent] is invalid. Api Event must have a String specified for 'Path'.", - "errors": [ - { - "errorMessage": "Resource with id [FunctionApiNoMethod] is invalid. Event with id [ApiEvent] is invalid. Event is missing key 'Path'." - } - ] + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [FunctionApiInvalidProperties] is invalid. Event with id [ApiEvent] is invalid. Properties should be a map. Resource with id [FunctionApiMethodArray] is invalid. Event with id [ApiEvent] is invalid. Property 'Method' should be a string. Resource with id [FunctionApiNoMethod] is invalid. Event with id [ApiEvent] is invalid. Property 'Method' is required. Resource with id [FunctionApiNoPath] is invalid. Event with id [ApiEvent] is invalid. Property 'Path' is required. Resource with id [FunctionApiPathArray] is invalid. Event with id [ApiEvent] is invalid. Property 'Path' should be a string." } diff --git a/tests/translator/output/error_function_invalid_event_api_ref.json b/tests/translator/output/error_function_invalid_event_api_ref.json index d2327116d..75e36b80b 100644 --- a/tests/translator/output/error_function_invalid_event_api_ref.json +++ b/tests/translator/output/error_function_invalid_event_api_ref.json @@ -1,8 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionApiRestApiRefError] is invalid. Event with id [ApiEvent] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template.", - "errors": [ - { - "errorMessage": "Resource with id [FunctionApiRestApiRefError] is invalid. Event with id [ApiEvent] is invalid. Api Event's RestApiId must be a string referencing an Api in the same template." - } - ] + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionApiRestApiRefError] is invalid. Event with id [ApiEvent] is invalid. Property 'RestApiId' should be a string." } diff --git a/tests/translator/output/error_function_invalid_event_http_api_ref.json b/tests/translator/output/error_function_invalid_event_http_api_ref.json index 37ac0153a..b188a47cb 100644 --- a/tests/translator/output/error_function_invalid_event_http_api_ref.json +++ b/tests/translator/output/error_function_invalid_event_http_api_ref.json @@ -1,8 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionApiHttpApiRefError] is invalid. Event with id [ApiEvent] is invalid. Api Event's ApiId must be a string referencing an Api in the same template.", - "errors": [ - { - "errorMessage": "Resource with id [FunctionApiHttpApiRefError] is invalid. Event with id [ApiEvent] is invalid. Api Event's ApiId must be a string referencing an Api in the same template." - } - ] + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionApiHttpApiRefError] is invalid. Event with id [ApiEvent] is invalid. Property 'ApiId' should be a string." } diff --git a/tests/translator/output/error_implicit_http_api_method.json b/tests/translator/output/error_implicit_http_api_method.json index cad68a31d..197d480dc 100644 --- a/tests/translator/output/error_implicit_http_api_method.json +++ b/tests/translator/output/error_implicit_http_api_method.json @@ -1,8 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Event is missing key 'Method'.", - "errors": [ - { - "errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Event is missing key 'Method'." - } - ] + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Property 'Method' is required." } diff --git a/tests/translator/output/error_implicit_http_api_path.json b/tests/translator/output/error_implicit_http_api_path.json index 17341fef8..c2a0e9699 100644 --- a/tests/translator/output/error_implicit_http_api_path.json +++ b/tests/translator/output/error_implicit_http_api_path.json @@ -1,8 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Api Event must have a String specified for 'Path'.", - "errors": [ - { - "errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Api Event must have a String specified for 'Path'." - } - ] + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Property 'Path' should be a string." } diff --git a/tests/translator/output/error_implicit_http_api_properties.json b/tests/translator/output/error_implicit_http_api_properties.json index 26ab9f276..21c8827a1 100644 --- a/tests/translator/output/error_implicit_http_api_properties.json +++ b/tests/translator/output/error_implicit_http_api_properties.json @@ -1,8 +1,8 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Event 'Properties' must be an Object. If you're using YAML, this may be an indentation issue.", + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Properties should be a map.", "errors": [ { - "errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Event 'Properties' must be an Object. If you're using YAML, this may be an indentation issue." + "errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Basic] is invalid. Properties should be a map." } ] }