From a282384599df1b4433957c7141c043e765e5cf44 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Tue, 12 Mar 2024 22:25:24 +0100 Subject: [PATCH 01/19] Generate types and type specs for all generated functions in aws-elixir and aws-erlang --- lib/aws_codegen.ex | 1 + lib/aws_codegen/post_service.ex | 55 ++++++++- lib/aws_codegen/rest_service.ex | 109 ++++++++++++++++- lib/aws_codegen/shapes.ex | 1 + lib/aws_codegen/types.ex | 203 ++++++++++++++++++++++++++++++++ lib/aws_codegen/util.ex | 178 ++++++++++++++++++++++++++++ priv/post.erl.eex | 15 +++ priv/post.ex.eex | 14 +++ priv/rest.erl.eex | 25 +++- priv/rest.ex.eex | 17 ++- 10 files changed, 612 insertions(+), 6 deletions(-) create mode 100644 lib/aws_codegen/types.ex diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index a614383..5be5a84 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -33,6 +33,7 @@ defmodule AWS.CodeGen do protocol: nil, signature_version: nil, service_id: nil, + shapes: %{}, signing_name: nil, target_prefix: nil end diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index c32e4bd..9a897c9 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -1,15 +1,31 @@ defmodule AWS.CodeGen.PostService do alias AWS.CodeGen.Docstring alias AWS.CodeGen.Service + alias AWS.CodeGen.Shapes + alias AWS.CodeGen.Name defmodule Action do defstruct arity: nil, docstring: nil, function_name: nil, + input: nil, + output: nil, + errors: %{}, host_prefix: nil, name: nil end + defmodule Shape do + defstruct name: nil, + type: nil, + members: [], + member: [], + enum: [], + min: nil, + required: [], + is_input: nil + end + @configuration %{ "ec2" => %{ content_type: "application/x-www-form-urlencoded", @@ -57,6 +73,7 @@ defmodule AWS.CodeGen.PostService do service = spec.api["shapes"][spec.shape_name] traits = service["traits"] actions = collect_actions(language, spec.api) + shapes = collect_shapes(language, spec.api) endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] endpoint_info = endpoints_spec["services"][endpoint_prefix] is_global = not is_nil(endpoint_info) and not Map.get(endpoint_info, "isRegionalized", true) @@ -89,6 +106,7 @@ defmodule AWS.CodeGen.PostService do language: language, module_name: spec.module_name, protocol: protocol |> to_string() |> String.replace("_", "-"), + shapes: shapes, signing_name: signing_name, signature_version: AWS.CodeGen.Util.get_signature_version(service), service_id: AWS.CodeGen.Util.get_service_id(service), @@ -137,10 +155,45 @@ defmodule AWS.CodeGen.PostService do ), function_name: AWS.CodeGen.Name.to_snake_case(operation), host_prefix: operation_spec["traits"]["smithy.api#endpoint"]["hostPrefix"], - name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, "") + name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, ""), + input: operation_spec["input"], + output: operation_spec["output"], + errors: operation_spec["errors"] } end) |> Enum.sort(fn a, b -> a.function_name < b.function_name end) |> Enum.uniq() end + + defp collect_shapes(_language, api_spec) do + api_spec["shapes"] + |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) + |> Enum.map(fn {name, shape} -> + {name, + %Shape{ + name: name, + type: shape["type"], + member: shape["member"], + members: shape["members"], + min: shape["min"], + enum: shape["enum"], + is_input: is_input?(shape) + }} + end) + |> Enum.into(%{}) + end + + defp is_input?(shape) do + if Map.has_key?(shape, "traits") do + traits = shape["traits"] + if Map.has_key?(traits, "smithy.api#input") do + true + else + false + end + else + true + end + end + end diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 1b91d69..5e7f9e2 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -23,7 +23,10 @@ defmodule AWS.CodeGen.RestService do send_body_as_binary?: false, receive_body_as_binary?: false, host_prefix: nil, - language: nil + language: nil, + input: nil, + output: nil, + errors: [] def method(action) do result = action.method |> String.downcase() |> String.to_atom() @@ -145,7 +148,8 @@ defmodule AWS.CodeGen.RestService do signing_name: signing_name, signature_version: AWS.CodeGen.Util.get_signature_version(service), service_id: AWS.CodeGen.Util.get_service_id(service), - target_prefix: nil, ##TODO: metadata["targetPrefix"] + target_prefix: nil, ##TODO: metadata["targetPrefix"], + shapes: collect_shapes(language, spec.api) } end @@ -157,6 +161,60 @@ defmodule AWS.CodeGen.RestService do function_parameters(action, true) end + def required_function_parameter_types(action) do + function_parameter_types(action, true) + end + + def required_query_map_types(action) do + function_parameter_types(action, true) + end + + def function_parameter_types(action, required_only \\ false) do + language = action.language + Enum.join([ + join_parameter_types(action.url_parameters, language) + | case action.method do + "GET" -> + case required_only do + false -> + [ + join_parameter_types(action.query_parameters, language), + join_parameter_types(action.request_header_parameters, language), + join_parameter_types(action.request_headers_parameters, language) + ] + + true -> + [ + join_parameter_types(action.required_query_parameters, language), + join_parameter_types(action.required_request_header_parameters, language) + ] + end + + _ -> + [] + end + ]) + end + + defp join_parameter_types(parameters, language) do + Enum.join( + Enum.map( + parameters, + fn parameter -> + if not parameter.required and language == :elixir do + ", String.t() | nil" + else + if language == :elixir do + ", String.t()" + else + ", binary() | list()" + end + end + end + ) + ) + end + @doc """ Render function parameters, if any, in a way that can be inserted directly into the code template. It can be asked to only return the required ones. @@ -275,7 +333,10 @@ defmodule AWS.CodeGen.RestService do send_body_as_binary?: Shapes.body_as_binary?(shapes, input_shape), receive_body_as_binary?: Shapes.body_as_binary?(shapes, output_shape), host_prefix: operation_spec["traits"]["smithy.api#endpoint"]["hostPrefix"], - language: language + language: language, + input: operation_spec["input"], + output: operation_spec["output"], + errors: operation_spec["errors"] } end) |> Enum.sort(fn a, b -> a.function_name < b.function_name end) @@ -363,4 +424,46 @@ defmodule AWS.CodeGen.RestService do } end + defmodule Shape do + defstruct name: nil, + type: nil, + members: [], + member: [], + enum: [], + min: nil, + required: [], + is_input: nil + end + + defp collect_shapes(_language, api_spec) do + api_spec["shapes"] + |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) + |> Enum.map(fn {name, shape} -> + {name, + %Shape{ + name: name, + type: shape["type"], + member: shape["member"], + members: shape["members"], + min: shape["min"], + enum: shape["enum"], + is_input: is_input?(shape) + }} + end) + |> Enum.into(%{}) + end + + defp is_input?(shape) do + if Map.has_key?(shape, "traits") do + traits = shape["traits"] + if Map.has_key?(traits, "smithy.api#input") do + true + else + false + end + else + true + end + end + end diff --git a/lib/aws_codegen/shapes.ex b/lib/aws_codegen/shapes.ex index 946f9cc..ab395d6 100644 --- a/lib/aws_codegen/shapes.ex +++ b/lib/aws_codegen/shapes.ex @@ -1,4 +1,5 @@ defmodule AWS.CodeGen.Shapes do + alias AWS.CodeGen.Name @moduledoc false def get_input_shape(operation_spec) do diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex new file mode 100644 index 0000000..06d0475 --- /dev/null +++ b/lib/aws_codegen/types.ex @@ -0,0 +1,203 @@ +defmodule AWS.CodeGen.Types do + + # Unfortunately, gotta patch over auto-defining types that already exist in Elixir + + def shape_to_type(:elixir, "String", _) do + "String.t()" + end + def shape_to_type(:erlang, "String", _) do + "string()" + end + + def shape_to_type(:elixir, "string", _) do + "String.t()" + end + def shape_to_type(:erlang, "string", _) do + "string()" + end + + def shape_to_type(:elixir, "Identifier", _) do + "String.t()" + end + def shape_to_type(:erlang, "Identifier", _) do + "string()" + end + + def shape_to_type(:elixir, "identifier", _) do + "String.t()" + end + def shape_to_type(:erlang, "identifier", _) do + "string()" + end + + def shape_to_type(:elixir, "XmlString" <> _rest, _) do + "String.t()" + end + def shape_to_type(:erlang, "XmlString" <> _rest, _) do + "string" + end + + def shape_to_type(:elixir, "NullablePositiveInteger", _) do + "nil | non_neg_integer()" + end + def shape_to_type(:erlang, "NullablePositiveInteger", _) do + "undefined | non_neg_integer()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: type}, _module_name) when type in ["float", "double", "long"] do + "float()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: type}, _module_name) when type in ["float", "double", "long"] do + "float()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "timestamp"}, _module_name) do + "non_neg_integer()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "timestamp"}, _module_name) do + "non_neg_integer()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "map"}, _module_name) do + "map()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "map"}, _module_name) do + "map()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "blob"}, _module_name) do + "binary()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "blob"}, _module_name) do + "binary()" + end + + def shape_to_type(:elixir, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name) do + "String.t()" + end + def shape_to_type(:erlang, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name) do + "string()" + end + def shape_to_type(:elixir, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name) do + "String.t()" + end + def shape_to_type(:erlang, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name) do + "string()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "integer"}, _module_name) do + "integer()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "integer"}, _module_name) do + "integer()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "boolean"}, _module_name) do + "boolean()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "boolean"}, _module_name) do + "boolean()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "enum"}, _module_name) do + "list(any())" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "enum"}, _module_name) do + "list(any())" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "union"}, _module_name) do + "list()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "union"}, _module_name) do + "list()" + end + + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "document"}, _module_name) do + "any()" + end + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "document"}, _module_name) do + "any()" + end + + def shape_to_type(context, shape_name, module_name, all_shapes) do + case shape_name do + "smithy.api#String" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "string"}, module_name)}]" + + "smithy.api#Integer" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "integer"}, module_name)}]" + + "smithy.api#Timestamp" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "timestamp"}, module_name)}]" + + "smithy.api#PrimitiveLong" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "long"}, module_name)}]" + + "smithy.api#Long" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "long"}, module_name)}]" + + "smithy.api#Boolean" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "boolean"}, module_name)}]" + + "smithy.api#PrimitiveBoolean" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "boolean"}, module_name)}]" + + "smithy.api#Double" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "double"}, module_name)}]" + + "smithy.api#Document" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "document"}, module_name)}]" + + "smithy.api#Unit" -> + "[]" + + "smithy.api#Float" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "float"}, module_name)}]" + + "smithy.api#Blob" -> + "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "blob"}, module_name)}]" + + _ -> + case all_shapes[shape_name] do + %AWS.CodeGen.PostService.Shape{type: "structure"} -> + type = "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" + if AWS.CodeGen.Util.reserved_type(type) do + "#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}()" + else + "#{type}()" + end + + %AWS.CodeGen.RestService.Shape{type: "structure"} -> + type = "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" + if AWS.CodeGen.Util.reserved_type(type) do + "#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}()" + else + "#{type}()" + end + + %AWS.CodeGen.PostService.Shape{type: "list", member: member} -> + type = "#{shape_to_type(context, member["target"], module_name, all_shapes)}" + if AWS.CodeGen.Util.reserved_type(type) do + "list(#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}())" + else + "list(#{type}())" + end + + %AWS.CodeGen.RestService.Shape{type: "list", member: member} -> + type = "#{shape_to_type(context, member["target"], module_name, all_shapes)}" + if AWS.CodeGen.Util.reserved_type(type) do + "list(#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}())" + else + "list(#{type}())" + end + + nil -> + raise "Tried to reference an undefined shape for #{shape_name}" + + shape -> + shape_to_type(context.language, shape, module_name) + end + end + end +end diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index 9c416fa..cac94f0 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -30,4 +30,182 @@ defmodule AWS.CodeGen.Util do service["traits"]["aws.api#service"]["sdkId"] end + def input_keys(action, context) do + shapes = context.shapes + input_shape = action.input["target"] + maybe_shape = Enum.filter(shapes, fn {name, _shape} -> input_shape == name end) + case maybe_shape do + [] -> + [] + [{_name, shape}] -> + Enum.reduce(shape.members, + [], + fn {name, %{"traits" => traits}}, acc -> + if Map.has_key?(traits, "smithy.api#required") do + [name <> " Required: true" | acc] + else + [name <> " Required: false" | acc] + end + {name, _shape}, acc -> + [name <> " Required: false" | acc] + end) + |> Enum.reverse() + end + end + + def types(context) do + Enum.reduce(context.shapes, + Map.new(), + fn {_name, shape}, acc -> + if shape.type == "structure" and not is_nil(shape.members) do + type = AWS.CodeGen.Name.to_snake_case(String.replace(shape.name, ~r/com\.amazonaws\.[^#]+#/, "")) + types = Enum.reduce(shape.members, + Map.new(), + fn {name, shape_member}, a -> + target = shape_member["target"] + if Map.has_key?(shape_member, "traits") do + traits = shape_member["traits"] + if Map.has_key?(traits, "smithy.api#httpLabel") do + a + else + shape_member_type = AWS.CodeGen.Types.shape_to_type(context, target, context.module_name, context.shapes) + Map.put(a, is_required(context.language, shape.is_input, shape_member, name), shape_member_type) + end + else + shape_member_type = AWS.CodeGen.Types.shape_to_type(context, target, context.module_name, context.shapes) + Map.put(a, is_required(context.language, shape.is_input, shape_member, name), shape_member_type) + end + end) + if reserved_type(type) do + Map.put(acc, "#{String.downcase(String.replace(context.module_name, "AWS.", ""))}_#{type}", types) + else + Map.put(acc, type, types) + end + else + acc + end + end) + end + + defp is_required(:elixir, is_input, shape, target) do + trimmed_name = String.replace(target, ~r/com\.amazonaws\.[^#]+#/, "") + if is_input do + if Map.has_key?(shape, "traits") do + if Map.has_key?(shape["traits"], "smithy.api#required") do + "required(\"#{trimmed_name}\") => " + else + "optional(\"#{trimmed_name}\") => " + end + else + "optional(\"#{trimmed_name}\") => " + end + else + "\"#{trimmed_name}\" => " + end + end + defp is_required(:erlang, is_input, shape, target) do + trimmed_name = String.replace(target, ~r/com\.amazonaws\.[^#]+#/, "") + if is_input do + if Map.has_key?(shape, "traits") do + if Map.has_key?(shape["traits"], "smithy.api#required") do + "<<\"#{trimmed_name}\">> := " + else + "<<\"#{trimmed_name}\">> => " + end + else + "<<\"#{trimmed_name}\">> => " + end + else + "<<\"#{trimmed_name}\">> => " + end + end + + def function_argument_type(:elixir, action) do + case Map.get(action.input, "target") do + "smithy.api#Unit" -> "%{}" + type -> + "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" + end + end + def function_argument_type(:erlang, action) do + case Map.get(action.input, "target") do + "smithy.api#Unit" -> "\#{}" + type -> + "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" + end + end + + def return_type(:elixir, action) do + case Map.get(action.output, "target") do + "smithy.api#Unit" -> + normal = "{:ok, nil, any()}" + errors = + if is_list(action.errors) do + Enum.map(action.errors, + fn %{"target" => error_type} -> + "{:error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}()}" + _ -> + "" + end ) + else + [] + end + Enum.join([normal, "{:error, {:unexpected_response, any()}}" | errors], " | \n") + type -> + normal = "{:ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), any()}" + errors = + if is_list(action.errors) do + Enum.map(action.errors, + fn %{"target" => error_type} -> + "{:error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}()}" + _ -> + "" + end ) + else + [] + end + Enum.join([normal, "{:error, {:unexpected_response, any()}}" | errors], " | \n") + end + end + def return_type(:erlang, action) do + case Map.get(action.output, "target") do + "smithy.api#Unit" -> + normal = "{ok, undefined, tuple()}" + errors = + if is_list(action.errors) do + Enum.map(action.errors, + fn %{"target" => error_type} -> + "{error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" + _ -> + "" + end ) + else + [] + end + Enum.join([normal, "{error, any()}" | errors], " |\n ") + type -> + normal = "{ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" + errors = + if is_list(action.errors) do + Enum.map(action.errors, + fn %{"target" => error_type} -> + "{error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" + _ -> + "" + end ) + else + [] + end + Enum.join([normal, "{error, any()}" | errors], " |\n ") + end + end + + def reserved_type(type) do + if type == "node" || type == "term" || type == "function" || type == "reference" do + true + else + false + end + end + end diff --git a/priv/post.erl.eex b/priv/post.erl.eex index 691b1f3..985a663 100644 --- a/priv/post.erl.eex +++ b/priv/post.erl.eex @@ -8,14 +8,29 @@ -include_lib("hackney/include/hackney_lib.hrl"). +<%= for {type_name, type_fields} <- AWS.CodeGen.Util.types(context) do %> +%% Example: +%% <%= type_name %>() :: #{ +<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> + ~s{%% #{field_name}#{field_type}} +end) %> +%% } +-type <%= "#{type_name}()" %> :: #{binary() => any()}. +<% end %> + %%==================================================================== %% API %%==================================================================== <%= for action <- context.actions do %> <%= action.docstring %> +-spec <%= action.function_name %>(map(), <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client, Input) when is_map(Client), is_map(Input) -> <%= action.function_name %>(Client, Input, []). + +-spec <%= action.function_name %>(map(), <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>, proplists:proplist()) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client, Input, Options) when is_map(Client), is_map(Input), is_list(Options) -> request(Client, <<"<%= action.name %>">>, Input, Options). diff --git a/priv/post.ex.eex b/priv/post.ex.eex index fc8bd43..872df37 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -11,6 +11,19 @@ defmodule <%= context.module_name %> do alias AWS.Client alias AWS.Request + <%= for {type_name, type_fields} <- AWS.CodeGen.Util.types(context) do %> +@typedoc """ + +## Example: +<%= type_name %>() :: %{ +<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> + ~s{ #{field_name}#{field_type}} +end) %> +} +""" +@type <%= "#{type_name}()" %> :: %{String.t => any()} +<% end %> + def metadata do %{ api_version: <%= inspect(context.api_version) %>, @@ -30,6 +43,7 @@ defmodule <%= context.module_name %> do @doc """ <%= action.docstring %> """<% end %> + @spec <%= action.function_name %>(map(), <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client, input, options \\ []) do meta = <%= if action.host_prefix do %> diff --git a/priv/rest.erl.eex b/priv/rest.erl.eex index cfa3a4d..5df994e 100644 --- a/priv/rest.erl.eex +++ b/priv/rest.erl.eex @@ -8,19 +8,37 @@ -include_lib("hackney/include/hackney_lib.hrl"). +<%= for {type_name, type_fields} <- AWS.CodeGen.Util.types(context) do %> +<%= if Map.keys(type_fields) == [] do %>%% Example: +%% <%= type_name %>() :: #{} +-type <%= "#{type_name}()" %> :: #{}.<% else %> +%% Example: +%% <%= type_name %>() :: #{ +<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> + ~s{%% #{field_name}#{field_type}} +end) %> +%% } +-type <%= "#{type_name}()" %> :: #{binary() => any()}.<% end %><% end %> + %%==================================================================== %% API %%==================================================================== <%= for action <- context.actions do %> <%= action.docstring %><%= if action.method == "GET" do %> +-spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.required_function_parameter_types(action) %>) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>) when is_map(Client) -> <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, #{}, #{}). +-spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.required_function_parameter_types(action) %>, map(), map()) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, QueryMap, HeadersMap) when is_map(Client), is_map(QueryMap), is_map(HeadersMap) -> <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, QueryMap, HeadersMap, []). +-spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.required_function_parameter_types(action) %>, map(), map(), proplists:proplist()) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, QueryMap, HeadersMap, Options0) when is_map(Client), is_map(QueryMap), is_map(HeadersMap), is_list(Options0) -> Path = ["<%= AWS.CodeGen.RestService.Action.url_path(action) %>"],<%= if AWS.CodeGen.RestService.Context.s3_context?(context) do %> @@ -70,8 +88,13 @@ end.<% else %> request(Client, get, Path, Query_, Headers, undefined, Options, SuccessStatusCode<%= if AWS.CodeGen.RestService.Context.s3_context?(context) do %>, Bucket<% end %>).<% end %> <% else %> +-spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.required_function_parameter_types(action) %>, <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.function_parameters(action) %>, Input) -> <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.function_parameters(action) %>, Input, []). + +-spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.required_function_parameter_types(action) %>, <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>, proplists:proplist()) -> + <%= AWS.CodeGen.Util.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.function_parameters(action) %>, Input0, Options0) -> Method = <%= AWS.CodeGen.RestService.Action.method(action) %>, Path = ["<%= AWS.CodeGen.RestService.Action.url_path(action) %>"],<%= if AWS.CodeGen.RestService.Context.s3_context?(context) do %> @@ -135,7 +158,7 @@ %% Internal functions %%==================================================================== --spec proplists_take(any(), proplists:proplists(), any()) -> {any(), proplists:proplists()}. +-spec proplists_take(any(), proplists:proplist(), any()) -> {any(), proplists:proplist()}. proplists_take(Key, Proplist, Default) -> Value = proplists:get_value(Key, Proplist, Default), {Value, proplists:delete(Key, Proplist)}. diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 003ad39..b4a0c50 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -11,6 +11,19 @@ defmodule <%= context.module_name %> do alias AWS.Client alias AWS.Request + <%= for {type_name, type_fields} <- AWS.CodeGen.Util.types(context) do %> +@typedoc """ + +## Example: +<%= type_name %>() :: %{ +<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> + ~s{ #{field_name}#{field_type}} +end) %> +} +""" +@type <%= "#{type_name}()" %> :: %{String.t => any()} +<% end %> + def metadata do %{ api_version: <%= inspect(context.api_version) %>, @@ -30,6 +43,7 @@ defmodule <%= context.module_name %> do @doc """ <%= action.docstring %> """<% end %><%= if action.method == "GET" do %> + @spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.function_parameter_types(action)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, options \\ []) do url_path = "<%= AWS.CodeGen.RestService.Action.url_path(action) %>" headers = []<%= for parameter <- action.request_header_parameters do %> @@ -74,7 +88,8 @@ defmodule <%= context.module_name %> do <% end %> Request.request_rest(client, meta, :get, url_path, query_params, headers, nil, options, <%= inspect(action.success_status_code) %>)<% else %> - def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, input, options \\ []) do +@spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.function_parameter_types(action)%>, <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> +def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, input, options \\ []) do url_path = "<%= AWS.CodeGen.RestService.Action.url_path(action) %>"<%= if length(action.request_header_parameters) > 0 do %> {headers, input} = [<%= for parameter <- action.request_header_parameters do %> From 3e36d0a47afcd934d8ccbb35c09d6804dba329f6 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:00:05 +0100 Subject: [PATCH 02/19] Update lib/aws_codegen/post_service.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/post_service.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index 9a897c9..48a9d30 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -168,7 +168,7 @@ defmodule AWS.CodeGen.PostService do defp collect_shapes(_language, api_spec) do api_spec["shapes"] |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) - |> Enum.map(fn {name, shape} -> + |> Map.new(fn {name, shape} -> {name, %Shape{ name: name, @@ -180,7 +180,6 @@ defmodule AWS.CodeGen.PostService do is_input: is_input?(shape) }} end) - |> Enum.into(%{}) end defp is_input?(shape) do From f5590a34137ed083df66687d4830e832b9d96d1c Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:00:13 +0100 Subject: [PATCH 03/19] Update lib/aws_codegen/post_service.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/post_service.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index 48a9d30..1cc13a7 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -184,12 +184,7 @@ defmodule AWS.CodeGen.PostService do defp is_input?(shape) do if Map.has_key?(shape, "traits") do - traits = shape["traits"] - if Map.has_key?(traits, "smithy.api#input") do - true - else - false - end + Map.has_key?(shape["traits"], "smithy.api#input") else true end From b8e80ddc5ffd17cc43c9352f59378f40aa294adb Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:00:23 +0100 Subject: [PATCH 04/19] Update lib/aws_codegen/rest_service.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/rest_service.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 5e7f9e2..3919669 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -438,7 +438,7 @@ defmodule AWS.CodeGen.RestService do defp collect_shapes(_language, api_spec) do api_spec["shapes"] |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) - |> Enum.map(fn {name, shape} -> + |> Map.new(fn {name, shape} -> {name, %Shape{ name: name, @@ -450,7 +450,6 @@ defmodule AWS.CodeGen.RestService do is_input: is_input?(shape) }} end) - |> Enum.into(%{}) end defp is_input?(shape) do From 264b09019697d7919aa2efc0dc6f76b56da3bd73 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:01:35 +0100 Subject: [PATCH 05/19] Update lib/aws_codegen/util.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/util.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index cac94f0..ec34e57 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -121,7 +121,7 @@ defmodule AWS.CodeGen.Util do end def function_argument_type(:elixir, action) do - case Map.get(action.input, "target") do + case Map.fetch!(action.input, "target") do "smithy.api#Unit" -> "%{}" type -> "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" From a5ca734548305f5905c4148ff7e07124fc2e9771 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:01:42 +0100 Subject: [PATCH 06/19] Update lib/aws_codegen/util.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/util.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index ec34e57..3f476ec 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -128,7 +128,7 @@ defmodule AWS.CodeGen.Util do end end def function_argument_type(:erlang, action) do - case Map.get(action.input, "target") do + case Map.fetch!(action.input, "target") do "smithy.api#Unit" -> "\#{}" type -> "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" From 584acac3d8cbf49f3954a02ed88e90f6967ee67f Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:01:47 +0100 Subject: [PATCH 07/19] Update lib/aws_codegen/util.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/util.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index 3f476ec..2db7aa5 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -136,7 +136,7 @@ defmodule AWS.CodeGen.Util do end def return_type(:elixir, action) do - case Map.get(action.output, "target") do + case Map.fetch!(action.output, "target") do "smithy.api#Unit" -> normal = "{:ok, nil, any()}" errors = From f9bb2b62b86e3583ba6639a47484538b6c2eedef Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:02:02 +0100 Subject: [PATCH 08/19] Update lib/aws_codegen/util.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/util.ex | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index 2db7aa5..98cba2a 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -201,11 +201,7 @@ defmodule AWS.CodeGen.Util do end def reserved_type(type) do - if type == "node" || type == "term" || type == "function" || type == "reference" do - true - else - false - end + type == "node" || type == "term" || type == "function" || type == "reference" end end From beeab6ada19a86361cfa145e67cc9292cc071c3c Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 06:02:10 +0100 Subject: [PATCH 09/19] Update lib/aws_codegen/rest_service.ex Co-authored-by: Philip Sampaio --- lib/aws_codegen/rest_service.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 3919669..4300307 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -454,12 +454,7 @@ defmodule AWS.CodeGen.RestService do defp is_input?(shape) do if Map.has_key?(shape, "traits") do - traits = shape["traits"] - if Map.has_key?(traits, "smithy.api#input") do - true - else - false - end + Map.has_key?(shape["traits"], "smithy.api#input") else true end From bbbe14c9bbb1b660487442fd60882bea7e467859 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 20:54:29 +0100 Subject: [PATCH 10/19] Increase Task.await/2 timeout --- lib/aws_codegen.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index 5be5a84..248d6ed 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -97,7 +97,7 @@ defmodule AWS.CodeGen do end ) - Enum.each(tasks, fn task -> Task.await(task, 60_000) end) + Enum.each(tasks, fn task -> Task.await(task, 120_000) end) end defp generate_code(spec, language, endpoints_spec, template_base_path, output_path) do From 90315af756683655bee35164f14878e63bfd1b89 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 20:55:57 +0100 Subject: [PATCH 11/19] Generate common errors in order to unify types and reduce noise --- lib/aws_codegen/util.ex | 28 ++++------------------------ priv/post.erl.eex | 22 ++++++++++++++++++++++ priv/post.ex.eex | 23 +++++++++++++++++++++++ priv/rest.erl.eex | 25 ++++++++++++++++++++++++- priv/rest.ex.eex | 23 +++++++++++++++++++++++ 5 files changed, 96 insertions(+), 25 deletions(-) diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index 98cba2a..137dad0 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -141,12 +141,7 @@ defmodule AWS.CodeGen.Util do normal = "{:ok, nil, any()}" errors = if is_list(action.errors) do - Enum.map(action.errors, - fn %{"target" => error_type} -> - "{:error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}()}" - _ -> - "" - end ) + ["{:error, #{action.function_name}_errors()}"] else [] end @@ -155,12 +150,7 @@ defmodule AWS.CodeGen.Util do normal = "{:ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), any()}" errors = if is_list(action.errors) do - Enum.map(action.errors, - fn %{"target" => error_type} -> - "{:error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}()}" - _ -> - "" - end ) + ["{:error, #{action.function_name}_errors()}"] else [] end @@ -173,12 +163,7 @@ defmodule AWS.CodeGen.Util do normal = "{ok, undefined, tuple()}" errors = if is_list(action.errors) do - Enum.map(action.errors, - fn %{"target" => error_type} -> - "{error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" - _ -> - "" - end ) + ["{error, #{action.function_name}_errors(), tuple()}"] else [] end @@ -187,12 +172,7 @@ defmodule AWS.CodeGen.Util do normal = "{ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" errors = if is_list(action.errors) do - Enum.map(action.errors, - fn %{"target" => error_type} -> - "{error, #{AWS.CodeGen.Name.to_snake_case(String.replace(error_type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" - _ -> - "" - end ) + ["{error, #{action.function_name}_errors(), tuple()}"] else [] end diff --git a/priv/post.erl.eex b/priv/post.erl.eex index 985a663..de51c49 100644 --- a/priv/post.erl.eex +++ b/priv/post.erl.eex @@ -17,6 +17,28 @@ end) %> %% } -type <%= "#{type_name}()" %> :: #{binary() => any()}. <% end %> +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Util.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "-type #{action.function_name}_errors() ::\n #{Enum.join(error_types, " | \n ")}." + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> %%==================================================================== %% API diff --git a/priv/post.ex.eex b/priv/post.ex.eex index 872df37..24ebd3e 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -24,6 +24,29 @@ end) %> @type <%= "#{type_name}()" %> :: %{String.t => any()} <% end %> +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Util.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "@type #{action.function_name}_errors() :: #{Enum.join(error_types, " | ")}" + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> + def metadata do %{ api_version: <%= inspect(context.api_version) %>, diff --git a/priv/rest.erl.eex b/priv/rest.erl.eex index 5df994e..aab847b 100644 --- a/priv/rest.erl.eex +++ b/priv/rest.erl.eex @@ -18,7 +18,30 @@ ~s{%% #{field_name}#{field_type}} end) %> %% } --type <%= "#{type_name}()" %> :: #{binary() => any()}.<% end %><% end %> +-type <%= "#{type_name}()" %> :: #{binary() => any()}.<% end %> +<% end %> +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Util.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "-type #{action.function_name}_errors() ::\n #{Enum.join(error_types, " | \n ")}." + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> %%==================================================================== %% API diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index b4a0c50..a7585ed 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -24,6 +24,29 @@ end) %> @type <%= "#{type_name}()" %> :: %{String.t => any()} <% end %> +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Util.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "@type #{action.function_name}_errors() :: #{Enum.join(error_types, " | ")}" + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> + def metadata do %{ api_version: <%= inspect(context.api_version) %>, From fa8043de3e08c3d3c89468f2a5db5be4b7960e27 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 20:57:19 +0100 Subject: [PATCH 12/19] Ensure that for Elixir, typedoc examples are considered code blocks --- priv/post.ex.eex | 14 +++++++++----- priv/rest.ex.eex | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/priv/post.ex.eex b/priv/post.ex.eex index 24ebd3e..9333883 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -15,11 +15,15 @@ defmodule <%= context.module_name %> do @typedoc """ ## Example: -<%= type_name %>() :: %{ -<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> - ~s{ #{field_name}#{field_type}} -end) %> -} + <%= if map_size(type_fields) == 0 do %> + <%= "#{type_name}() :: %{}" %> + <% else %> + <%= "#{type_name}() :: %{" %> + <%= Enum.map_join(type_fields, ",\n ", fn {field_name, field_type} -> + ~s{ #{field_name}#{field_type}} + end) %> + } + <% end %> """ @type <%= "#{type_name}()" %> :: %{String.t => any()} <% end %> diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index a7585ed..9c5756c 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -15,11 +15,15 @@ defmodule <%= context.module_name %> do @typedoc """ ## Example: -<%= type_name %>() :: %{ -<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> - ~s{ #{field_name}#{field_type}} -end) %> -} + <%= if map_size(type_fields) == 0 do %> + <%= "#{type_name}() :: %{}" %> + <% else %> + <%= "#{type_name}() :: %{" %> + <%= Enum.map_join(type_fields, ",\n ", fn {field_name, field_type} -> + ~s{ #{field_name}#{field_type}} + end) %> + } + <% end %> """ @type <%= "#{type_name}()" %> :: %{String.t => any()} <% end %> From f295d0a9b8f5f1f3e6810c3e397a33dde4f83c3f Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 20:57:43 +0100 Subject: [PATCH 13/19] Fix bug where empty maps were typed as %{String.t => any()} --- priv/post.ex.eex | 2 +- priv/rest.ex.eex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/priv/post.ex.eex b/priv/post.ex.eex index 9333883..01544a4 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -25,7 +25,7 @@ defmodule <%= context.module_name %> do } <% end %> """ -@type <%= "#{type_name}()" %> :: %{String.t => any()} +@type <%= if map_size(type_fields) == 0 do "#{type_name}() :: %{}" else "#{type_name}() :: %{String.t => any()}" end %> <% end %> <%= Enum.map(context.actions, diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 9c5756c..0bb535f 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -25,7 +25,7 @@ defmodule <%= context.module_name %> do } <% end %> """ -@type <%= "#{type_name}()" %> :: %{String.t => any()} +@type <%= if map_size(type_fields) == 0 do "#{type_name}() :: %{}" else "#{type_name}() :: %{String.t => any()}" end %> <% end %> <%= Enum.map(context.actions, From 6619bc586369a85163d0c5e94235050eff2f59d7 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Wed, 13 Mar 2024 21:17:06 +0100 Subject: [PATCH 14/19] Fix tests --- priv/rest.ex.eex | 6 +- test/aws_codegen/rest_service_test.exs | 12 +- test/aws_codegen_test.exs | 553 +++++++++++++++++++------ 3 files changed, 451 insertions(+), 120 deletions(-) diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 0bb535f..d186b92 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -15,15 +15,15 @@ defmodule <%= context.module_name %> do @typedoc """ ## Example: - <%= if map_size(type_fields) == 0 do %> +<%= if map_size(type_fields) == 0 do %> <%= "#{type_name}() :: %{}" %> - <% else %> +<% else %> <%= "#{type_name}() :: %{" %> <%= Enum.map_join(type_fields, ",\n ", fn {field_name, field_type} -> ~s{ #{field_name}#{field_type}} end) %> } - <% end %> +<% end %> """ @type <%= if map_size(type_fields) == 0 do "#{type_name}() :: %{}" else "#{type_name}() :: %{String.t => any()}" end %> <% end %> diff --git a/test/aws_codegen/rest_service_test.exs b/test/aws_codegen/rest_service_test.exs index ed8bd69..24944f3 100644 --- a/test/aws_codegen/rest_service_test.exs +++ b/test/aws_codegen/rest_service_test.exs @@ -59,10 +59,20 @@ defmodule AWS.CodeGen.RestServiceTest do %RestService.Action{ arity: 3, docstring: " Ingests your application events into CloudTrail Lake.\n\n A required parameter,\n `auditEvents`, accepts the JSON records (also called\n *payload*) of events that you want CloudTrail to ingest. You\n can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents`\n request.", - function_name: "put_audit_events", + errors: [ + %{"target" => "com.amazonaws.cloudtraildata#ChannelInsufficientPermission"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelNotFound"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema"}, + %{"target" => "com.amazonaws.cloudtraildata#DuplicatedAuditEventId"}, + %{"target" => "com.amazonaws.cloudtraildata#InvalidChannelARN"}, + %{"target" => "com.amazonaws.cloudtraildata#UnsupportedOperationException"} + ], + function_name: "put_audit_events", language: :elixir, method: "POST", name: "com.amazonaws.cloudtraildata#PutAuditEvents", + input: %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsRequest"}, + output: %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsResponse"}, query_parameters: [ %RestService.Parameter{ code_name: "channel_arn", diff --git a/test/aws_codegen_test.exs b/test/aws_codegen_test.exs index de43c64..5504e39 100644 --- a/test/aws_codegen_test.exs +++ b/test/aws_codegen_test.exs @@ -69,105 +69,154 @@ defmodule AWS.CodeGenTest do alias AWS.Client alias AWS.Request - def metadata do - %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", - credential_scope: nil, - endpoint_prefix: "cloudtrail-data", - global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", - target_prefix: nil - } - end + @typedoc \"\"\" - @doc \"\"\" - Ingests your application events into CloudTrail Lake. + ## Example: + + audit_event() :: %{ + \"eventData\" => [String.t()], + \"eventDataChecksum\" => [String.t()], + \"id\" => String.t() + } - A required parameter, - `auditEvents`, accepts the JSON records (also called - *payload*) of events that you want CloudTrail to ingest. You - can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` - request. \"\"\" - def put_audit_events(%Client{} = client, input, options \\\\ []) do - url_path = "/PutAuditEvents" - headers = [] + @type audit_event() :: %{String.t() => any()} - {query_params, input} = - [ - {"channelArn", "channelArn"}, - {"externalId", "externalId"} - ] - |> Request.build_params(input) + @typedoc \"\"\" - meta = metadata() + ## Example: - Request.request_rest( - client, - meta, - :post, - url_path, - query_params, - headers, - input, - options, - 200 - ) - end - end - """) - end + audit_event_result_entry() :: %{ + \"eventID\" => String.t(), + \"id\" => String.t() + } - test "renders POST action with options to send/receive binary", %{specs: specs} do - context = setup_context(:elixir, specs) + \"\"\" + @type audit_event_result_entry() :: %{String.t() => any()} - [action | _] = context.actions - action = %{action | send_body_as_binary?: true, receive_body_as_binary?: true} + @typedoc \"\"\" - result = - %{context | actions: [action]} - |> AWS.CodeGen.render("priv/rest.ex.eex") - |> IO.iodata_to_binary() + ## Example: - assert result == - String.trim_leading(""" - # WARNING: DO NOT EDIT, AUTO-GENERATED CODE! - # See https://github.com/aws-beam/aws-codegen for more details. + channel_insufficient_permission() :: %{ + \"message\" => [String.t()] + } - defmodule AWS.CloudTrailData do - @moduledoc \"\"\" - The CloudTrail Data Service lets you ingest events into CloudTrail from any - source in your - hybrid environments, such as in-house or SaaS applications hosted on-premises or - in the cloud, - virtual machines, or containers. + \"\"\" + @type channel_insufficient_permission() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_not_found() :: %{ + \"message\" => [String.t()] + } - You can store, access, analyze, troubleshoot and take action on - this data without maintaining multiple log aggregators and reporting tools. - After you run - `PutAuditEvents` to ingest your application activity into CloudTrail, you can - use CloudTrail Lake to search, query, and analyze the data that is logged - from your applications. \"\"\" + @type channel_not_found() :: %{String.t() => any()} - alias AWS.Client - alias AWS.Request + @typedoc \"\"\" + + ## Example: + + channel_unsupported_schema() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_unsupported_schema() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + duplicated_audit_event_id() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type duplicated_audit_event_id() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + invalid_channel_arn() :: %{ + \"message\" => [String.t()] + } + + \"\"" + @type invalid_channel_arn() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_request() :: %{ + optional(\"externalId\") => String.t(), + required(\"auditEvents\") => list(audit_event()()), + required(\"channelArn\") => String.t() + } + + \"\"\" + @type put_audit_events_request() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_response() :: %{ + required(\"failed\") => list(result_error_entry()()), + required(\"successful\") => list(audit_event_result_entry()()) + } + + \"\"\" + @type put_audit_events_response() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + result_error_entry() :: %{ + \"errorCode\" => String.t(), + \"errorMessage\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type result_error_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + unsupported_operation_exception() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type unsupported_operation_exception() :: %{String.t() => any()} + + @type put_audit_events_errors() :: + unsupported_operation_exception() + | invalid_channel_arn() + | duplicated_audit_event_id() + | channel_unsupported_schema() + | channel_not_found() + | channel_insufficient_permission() def metadata do %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", + api_version: \"2021-08-11\", + content_type: \"application/x-amz-json-1.1\", credential_scope: nil, - endpoint_prefix: "cloudtrail-data", + endpoint_prefix: \"cloudtrail-data\", global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", + protocol: \"rest-json\", + service_id: \"CloudTrail Data\", + signature_version: \"v4\", + signing_name: \"cloudtrail-data\", target_prefix: nil } end @@ -181,31 +230,21 @@ defmodule AWS.CodeGenTest do can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` request. \"\"\" + @spec put_audit_events(map(), put_audit_events_request(), list()) :: + {:ok, put_audit_events_response(), any()} + | {:error, {:unexpected_response, any()}} + | {:error, put_audit_events_errors()} def put_audit_events(%Client{} = client, input, options \\\\ []) do - url_path = "/PutAuditEvents" + url_path = \"/PutAuditEvents\" headers = [] {query_params, input} = [ - {"channelArn", "channelArn"}, - {"externalId", "externalId"} + {\"channelArn\", \"channelArn\"}, + {\"externalId\", \"externalId\"} ] |> Request.build_params(input) - options = - Keyword.put( - options, - :send_body_as_binary?, - true - ) - - options = - Keyword.put( - options, - :receive_body_as_binary?, - true - ) - meta = metadata() Request.request_rest( @@ -265,17 +304,154 @@ defmodule AWS.CodeGenTest do alias AWS.Client alias AWS.Request + @typedoc \"\"\" + + ## Example: + + audit_event() :: %{ + \"eventData\" => [String.t()], + \"eventDataChecksum\" => [String.t()], + \"id\" => String.t() + } + + \"\"\" + @type audit_event() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + audit_event_result_entry() :: %{ + \"eventID\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type audit_event_result_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_insufficient_permission() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_insufficient_permission() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_not_found() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_not_found() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_unsupported_schema() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_unsupported_schema() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + duplicated_audit_event_id() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type duplicated_audit_event_id() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + invalid_channel_arn() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type invalid_channel_arn() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_request() :: %{ + optional(\"externalId\") => String.t(), + required(\"auditEvents\") => list(audit_event()()), + required(\"channelArn\") => String.t() + } + + \"\"\" + @type put_audit_events_request() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_response() :: %{ + required(\"failed\") => list(result_error_entry()()), + required(\"successful\") => list(audit_event_result_entry()()) + } + + \"\"\" + @type put_audit_events_response() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + result_error_entry() :: %{ + \"errorCode\" => String.t(), + \"errorMessage\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type result_error_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + unsupported_operation_exception() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type unsupported_operation_exception() :: %{String.t() => any()} + + @type put_audit_events_errors() :: + unsupported_operation_exception() + | invalid_channel_arn() + | duplicated_audit_event_id() + | channel_unsupported_schema() + | channel_not_found() + | channel_insufficient_permission() + def metadata do %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", + api_version: \"2021-08-11\", + content_type: \"application/x-amz-json-1.1\", credential_scope: nil, - endpoint_prefix: "cloudtrail-data", + endpoint_prefix: \"cloudtrail-data\", global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", + protocol: \"rest-json\", + service_id: \"CloudTrail Data\", + signature_version: \"v4\", + signing_name: \"cloudtrail-data\", target_prefix: nil } end @@ -289,8 +465,12 @@ defmodule AWS.CodeGenTest do can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` request. \"\"\" + @spec put_audit_events(map(), String.t(), String.t() | nil, list()) :: + {:ok, put_audit_events_response(), any()} + | {:error, {:unexpected_response, any()}} + | {:error, put_audit_events_errors()} def put_audit_events(%Client{} = client, channel_arn, external_id \\\\ nil, options \\\\ []) do - url_path = "/PutAuditEvents" + url_path = \"/PutAuditEvents\" headers = [] query_params = [] @@ -366,17 +546,154 @@ defmodule AWS.CodeGenTest do alias AWS.Client alias AWS.Request + @typedoc \"\"\" + + ## Example: + + audit_event() :: %{ + \"eventData\" => [String.t()], + \"eventDataChecksum\" => [String.t()], + \"id\" => String.t() + } + + \"\"\" + @type audit_event() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + audit_event_result_entry() :: %{ + \"eventID\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type audit_event_result_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_insufficient_permission() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_insufficient_permission() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_not_found() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_not_found() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_unsupported_schema() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_unsupported_schema() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + duplicated_audit_event_id() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type duplicated_audit_event_id() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + invalid_channel_arn() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type invalid_channel_arn() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_request() :: %{ + optional(\"externalId\") => String.t(), + required(\"auditEvents\") => list(audit_event()()), + required(\"channelArn\") => String.t() + } + + \"\"\" + @type put_audit_events_request() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_response() :: %{ + required(\"failed\") => list(result_error_entry()()), + required(\"successful\") => list(audit_event_result_entry()()) + } + + \"\"\" + @type put_audit_events_response() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + result_error_entry() :: %{ + \"errorCode\" => String.t(), + \"errorMessage\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type result_error_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + unsupported_operation_exception() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type unsupported_operation_exception() :: %{String.t() => any()} + + @type put_audit_events_errors() :: + unsupported_operation_exception() + | invalid_channel_arn() + | duplicated_audit_event_id() + | channel_unsupported_schema() + | channel_not_found() + | channel_insufficient_permission() + def metadata do %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", + api_version: \"2021-08-11\", + content_type: \"application/x-amz-json-1.1\", credential_scope: nil, - endpoint_prefix: "cloudtrail-data", + endpoint_prefix: \"cloudtrail-data\", global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", + protocol: \"rest-json\", + service_id: \"CloudTrail Data\", + signature_version: \"v4\", + signing_name: \"cloudtrail-data\", target_prefix: nil } end @@ -390,18 +707,22 @@ defmodule AWS.CodeGenTest do can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` request. \"\"\" + @spec put_audit_events(map(), put_audit_events_request(), list()) :: + {:ok, put_audit_events_response(), any()} + | {:error, {:unexpected_response, any()}} + | {:error, put_audit_events_errors()} def put_audit_events(%Client{} = client, input, options \\\\ []) do - url_path = "/PutAuditEvents" + url_path = \"/PutAuditEvents\" headers = [] {query_params, input} = [ - {"channelArn", "channelArn"}, - {"externalId", "externalId"} + {\"channelArn\", \"channelArn\"}, + {\"externalId\", \"externalId\"} ] |> Request.build_params(input) - meta = metadata() |> Map.put_new(:host_prefix, "my-host-prefix.") + meta = metadata() |> Map.put_new(:host_prefix, \"my-host-prefix.\") Request.request_rest( client, From 79af0c2ac1aad2408d62b15c554e9dd7e1b99da7 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Thu, 14 Mar 2024 17:03:08 +0100 Subject: [PATCH 15/19] Shorthand shape_to_type/3 --- lib/aws_codegen/types.ex | 151 +++++++++------------------------------ 1 file changed, 34 insertions(+), 117 deletions(-) diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex index 06d0475..a22addc 100644 --- a/lib/aws_codegen/types.ex +++ b/lib/aws_codegen/types.ex @@ -2,123 +2,40 @@ defmodule AWS.CodeGen.Types do # Unfortunately, gotta patch over auto-defining types that already exist in Elixir - def shape_to_type(:elixir, "String", _) do - "String.t()" - end - def shape_to_type(:erlang, "String", _) do - "string()" - end - - def shape_to_type(:elixir, "string", _) do - "String.t()" - end - def shape_to_type(:erlang, "string", _) do - "string()" - end - - def shape_to_type(:elixir, "Identifier", _) do - "String.t()" - end - def shape_to_type(:erlang, "Identifier", _) do - "string()" - end - - def shape_to_type(:elixir, "identifier", _) do - "String.t()" - end - def shape_to_type(:erlang, "identifier", _) do - "string()" - end - - def shape_to_type(:elixir, "XmlString" <> _rest, _) do - "String.t()" - end - def shape_to_type(:erlang, "XmlString" <> _rest, _) do - "string" - end - - def shape_to_type(:elixir, "NullablePositiveInteger", _) do - "nil | non_neg_integer()" - end - def shape_to_type(:erlang, "NullablePositiveInteger", _) do - "undefined | non_neg_integer()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: type}, _module_name) when type in ["float", "double", "long"] do - "float()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: type}, _module_name) when type in ["float", "double", "long"] do - "float()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "timestamp"}, _module_name) do - "non_neg_integer()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "timestamp"}, _module_name) do - "non_neg_integer()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "map"}, _module_name) do - "map()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "map"}, _module_name) do - "map()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "blob"}, _module_name) do - "binary()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "blob"}, _module_name) do - "binary()" - end - - def shape_to_type(:elixir, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name) do - "String.t()" - end - def shape_to_type(:erlang, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name) do - "string()" - end - def shape_to_type(:elixir, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name) do - "String.t()" - end - def shape_to_type(:erlang, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name) do - "string()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "integer"}, _module_name) do - "integer()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "integer"}, _module_name) do - "integer()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "boolean"}, _module_name) do - "boolean()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "boolean"}, _module_name) do - "boolean()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "enum"}, _module_name) do - "list(any())" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "enum"}, _module_name) do - "list(any())" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "union"}, _module_name) do - "list()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "union"}, _module_name) do - "list()" - end - - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "document"}, _module_name) do - "any()" - end - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "document"}, _module_name) do - "any()" - end + def shape_to_type(:elixir, "String", _), do: "String.t()" + def shape_to_type(:erlang, "String", _), do: "string()" + def shape_to_type(:elixir, "string", _), do: "String.t()" + def shape_to_type(:erlang, "string", _), do: "string()" + def shape_to_type(:elixir, "Identifier", _), do: "String.t()" + def shape_to_type(:erlang, "Identifier", _), do: "string()" + def shape_to_type(:elixir, "identifier", _), do: "String.t()" + def shape_to_type(:erlang, "identifier", _), do: "string()" + def shape_to_type(:elixir, "XmlString" <> _rest, _), do: "String.t()" + def shape_to_type(:erlang, "XmlString" <> _rest, _), do: "string" + def shape_to_type(:elixir, "NullablePositiveInteger", _), do: "nil | non_neg_integer()" + def shape_to_type(:erlang, "NullablePositiveInteger", _), do: "undefined | non_neg_integer()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "map"}, _module_name), do: "map()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "map"}, _module_name), do: "map()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "blob"}, _module_name), do: "binary()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "blob"}, _module_name), do: "binary()" + def shape_to_type(:elixir, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name), do: "String.t()" + def shape_to_type(:erlang, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name), do: "string()" + def shape_to_type(:elixir, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name), do: "String.t()" + def shape_to_type(:erlang, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name), do: "string()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "integer"}, _module_name), do: "integer()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "integer"}, _module_name), do: "integer()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "boolean"}, _module_name), do: "boolean()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "boolean"}, _module_name), do: "boolean()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "enum"}, _module_name), do: "list(any())" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "enum"}, _module_name), do: "list(any())" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "union"}, _module_name), do: "list()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "union"}, _module_name), do: "list()" + def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "document"}, _module_name), do: "any()" + def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "document"}, _module_name), do: "any()" def shape_to_type(context, shape_name, module_name, all_shapes) do case shape_name do From 44a1cc4253c7a6b8c086e93df22369556bc915d4 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Thu, 14 Mar 2024 17:03:27 +0100 Subject: [PATCH 16/19] Update lib/aws_codegen/post_service.ex Co-authored-by: Benjamin Milde --- lib/aws_codegen/post_service.ex | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index 1cc13a7..af89568 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -183,11 +183,7 @@ defmodule AWS.CodeGen.PostService do end defp is_input?(shape) do - if Map.has_key?(shape, "traits") do - Map.has_key?(shape["traits"], "smithy.api#input") - else - true - end + !Map.has_key?(shape, "traits") or Map.has_key?(shape["traits"], "smithy.api#input") end end From 167bbb4de6fd9ebef0cd0538766cb78a090c50cd Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Thu, 14 Mar 2024 21:46:49 +0100 Subject: [PATCH 17/19] Simplify rest_service:function_parameter_types --- lib/aws_codegen/rest_service.ex | 47 +++++++++++++++------------------ priv/rest.ex.eex | 4 +-- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 4300307..1038fec 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -162,38 +162,33 @@ defmodule AWS.CodeGen.RestService do end def required_function_parameter_types(action) do - function_parameter_types(action, true) + function_parameter_types(action.method, action, true) end def required_query_map_types(action) do - function_parameter_types(action, true) + function_parameter_types(action.method, action, true) end - def function_parameter_types(action, required_only \\ false) do + def function_parameter_types("GET", action, false = _required_only) do language = action.language - Enum.join([ - join_parameter_types(action.url_parameters, language) - | case action.method do - "GET" -> - case required_only do - false -> - [ - join_parameter_types(action.query_parameters, language), - join_parameter_types(action.request_header_parameters, language), - join_parameter_types(action.request_headers_parameters, language) - ] - - true -> - [ - join_parameter_types(action.required_query_parameters, language), - join_parameter_types(action.required_request_header_parameters, language) - ] - end - - _ -> - [] - end - ]) + Enum.join( + [join_parameter_types(action.url_parameters, language), + join_parameter_types(action.query_parameters, language), + join_parameter_types(action.request_header_parameters, language), + join_parameter_types(action.request_headers_parameters, language) + ]) + end + def function_parameter_types("GET", action, true = _required_only) do + language = action.language + Enum.join( + [join_parameter_types(action.url_parameters, language), + join_parameter_types(action.required_query_parameters, language), + join_parameter_types(action.required_request_header_parameters, language) + ]) + end + def function_parameter_types(_method, action, _required_only) do + language = action.language + join_parameter_types(action.url_parameters, language) end defp join_parameter_types(parameters, language) do diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index d186b92..da450fc 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -70,7 +70,7 @@ defmodule <%= context.module_name %> do @doc """ <%= action.docstring %> """<% end %><%= if action.method == "GET" do %> - @spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.function_parameter_types(action)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> + @spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.function_parameter_types(action.method, action, false)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, options \\ []) do url_path = "<%= AWS.CodeGen.RestService.Action.url_path(action) %>" headers = []<%= for parameter <- action.request_header_parameters do %> @@ -115,7 +115,7 @@ defmodule <%= context.module_name %> do <% end %> Request.request_rest(client, meta, :get, url_path, query_params, headers, nil, options, <%= inspect(action.success_status_code) %>)<% else %> -@spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.function_parameter_types(action)%>, <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> +@spec <%= action.function_name %>(map()<%= AWS.CodeGen.RestService.function_parameter_types(action.method, action, false)%>, <%= AWS.CodeGen.Util.function_argument_type(context.language, action)%>, list()) :: <%= AWS.CodeGen.Util.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, input, options \\ []) do url_path = "<%= AWS.CodeGen.RestService.Action.url_path(action) %>"<%= if length(action.request_header_parameters) > 0 do %> {headers, input} = From 3423eff0d3419b73aac55e9c1d5427aaf592ea81 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Fri, 15 Mar 2024 08:44:34 +0100 Subject: [PATCH 18/19] Fix rest_service:join_parameter_types to not have as much nesting --- lib/aws_codegen/rest_service.ex | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 1038fec..f56bc9a 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -191,24 +191,23 @@ defmodule AWS.CodeGen.RestService do join_parameter_types(action.url_parameters, language) end - defp join_parameter_types(parameters, language) do + defp join_parameter_types(parameters, :elixir) do Enum.join( Enum.map( parameters, fn parameter -> - if not parameter.required and language == :elixir do + if not parameter.required do ", String.t() | nil" else - if language == :elixir do - ", String.t()" - else - ", binary() | list()" - end + ", String.t()" end end ) ) end + defp join_parameter_types(parameters, :erlang) do + Enum.join(Enum.map(parameters, fn _parameter -> ", binary() | list()" end)) + end @doc """ Render function parameters, if any, in a way that can be inserted directly From 10a77a4776d41c971dd76c15d98815715e2fb200 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Fri, 15 Mar 2024 08:55:40 +0100 Subject: [PATCH 19/19] Gather all %Shape related stuff inside shapes.ex --- lib/aws_codegen/post_service.ex | 35 +-------------- lib/aws_codegen/rest_service.ex | 38 +---------------- lib/aws_codegen/shapes.ex | 34 ++++++++++++++- lib/aws_codegen/types.ex | 76 +++++++++++---------------------- 4 files changed, 60 insertions(+), 123 deletions(-) diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index af89568..f735d5e 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -2,7 +2,6 @@ defmodule AWS.CodeGen.PostService do alias AWS.CodeGen.Docstring alias AWS.CodeGen.Service alias AWS.CodeGen.Shapes - alias AWS.CodeGen.Name defmodule Action do defstruct arity: nil, @@ -15,17 +14,6 @@ defmodule AWS.CodeGen.PostService do name: nil end - defmodule Shape do - defstruct name: nil, - type: nil, - members: [], - member: [], - enum: [], - min: nil, - required: [], - is_input: nil - end - @configuration %{ "ec2" => %{ content_type: "application/x-www-form-urlencoded", @@ -73,7 +61,7 @@ defmodule AWS.CodeGen.PostService do service = spec.api["shapes"][spec.shape_name] traits = service["traits"] actions = collect_actions(language, spec.api) - shapes = collect_shapes(language, spec.api) + shapes = Shapes.collect_shapes(language, spec.api) endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] endpoint_info = endpoints_spec["services"][endpoint_prefix] is_global = not is_nil(endpoint_info) and not Map.get(endpoint_info, "isRegionalized", true) @@ -165,25 +153,4 @@ defmodule AWS.CodeGen.PostService do |> Enum.uniq() end - defp collect_shapes(_language, api_spec) do - api_spec["shapes"] - |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) - |> Map.new(fn {name, shape} -> - {name, - %Shape{ - name: name, - type: shape["type"], - member: shape["member"], - members: shape["members"], - min: shape["min"], - enum: shape["enum"], - is_input: is_input?(shape) - }} - end) - end - - defp is_input?(shape) do - !Map.has_key?(shape, "traits") or Map.has_key?(shape["traits"], "smithy.api#input") - end - end diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index f56bc9a..2b99dcf 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -149,7 +149,7 @@ defmodule AWS.CodeGen.RestService do signature_version: AWS.CodeGen.Util.get_signature_version(service), service_id: AWS.CodeGen.Util.get_service_id(service), target_prefix: nil, ##TODO: metadata["targetPrefix"], - shapes: collect_shapes(language, spec.api) + shapes: Shapes.collect_shapes(language, spec.api) } end @@ -418,40 +418,4 @@ defmodule AWS.CodeGen.RestService do } end - defmodule Shape do - defstruct name: nil, - type: nil, - members: [], - member: [], - enum: [], - min: nil, - required: [], - is_input: nil - end - - defp collect_shapes(_language, api_spec) do - api_spec["shapes"] - |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) - |> Map.new(fn {name, shape} -> - {name, - %Shape{ - name: name, - type: shape["type"], - member: shape["member"], - members: shape["members"], - min: shape["min"], - enum: shape["enum"], - is_input: is_input?(shape) - }} - end) - end - - defp is_input?(shape) do - if Map.has_key?(shape, "traits") do - Map.has_key?(shape["traits"], "smithy.api#input") - else - true - end - end - end diff --git a/lib/aws_codegen/shapes.ex b/lib/aws_codegen/shapes.ex index ab395d6..a70269c 100644 --- a/lib/aws_codegen/shapes.ex +++ b/lib/aws_codegen/shapes.ex @@ -1,6 +1,15 @@ defmodule AWS.CodeGen.Shapes do - alias AWS.CodeGen.Name - @moduledoc false + + defmodule Shape do + defstruct name: nil, + type: nil, + members: [], + member: [], + enum: [], + min: nil, + required: [], + is_input: nil + end def get_input_shape(operation_spec) do get_in(operation_spec, ["input", "target"]) @@ -10,6 +19,23 @@ defmodule AWS.CodeGen.Shapes do get_in(operation_spec, ["output", "target"]) end + def collect_shapes(_language, api_spec) do + api_spec["shapes"] + |> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end) + |> Map.new(fn {name, shape} -> + {name, + %Shape{ + name: name, + type: shape["type"], + member: shape["member"], + members: shape["members"], + min: shape["min"], + enum: shape["enum"], + is_input: is_input?(shape) + }} + end) + end + def body_as_binary?(shapes, shape) do ## TODO: Should we validate or search for trait `smithy.api#httpPayload` rather than ## trust that the member is always named `Body`? @@ -24,4 +50,8 @@ defmodule AWS.CodeGen.Shapes do end end + def is_input?(shape) do + !Map.has_key?(shape, "traits") or Map.has_key?(shape["traits"], "smithy.api#input") + end + end diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex index a22addc..2bb7eae 100644 --- a/lib/aws_codegen/types.ex +++ b/lib/aws_codegen/types.ex @@ -1,5 +1,7 @@ defmodule AWS.CodeGen.Types do + alias AWS.CodeGen.Shapes.Shape + # Unfortunately, gotta patch over auto-defining types that already exist in Elixir def shape_to_type(:elixir, "String", _), do: "String.t()" @@ -14,78 +16,60 @@ defmodule AWS.CodeGen.Types do def shape_to_type(:erlang, "XmlString" <> _rest, _), do: "string" def shape_to_type(:elixir, "NullablePositiveInteger", _), do: "nil | non_neg_integer()" def shape_to_type(:erlang, "NullablePositiveInteger", _), do: "undefined | non_neg_integer()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "map"}, _module_name), do: "map()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "map"}, _module_name), do: "map()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "blob"}, _module_name), do: "binary()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "blob"}, _module_name), do: "binary()" - def shape_to_type(:elixir, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name), do: "String.t()" - def shape_to_type(:erlang, %AWS.CodeGen.PostService.Shape{type: "string"}, _module_name), do: "string()" - def shape_to_type(:elixir, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name), do: "String.t()" - def shape_to_type(:erlang, %AWS.CodeGen.RestService.Shape{type: "string"}, _module_name), do: "string()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "integer"}, _module_name), do: "integer()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "integer"}, _module_name), do: "integer()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "boolean"}, _module_name), do: "boolean()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "boolean"}, _module_name), do: "boolean()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "enum"}, _module_name), do: "list(any())" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "enum"}, _module_name), do: "list(any())" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "union"}, _module_name), do: "list()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "union"}, _module_name), do: "list()" - def shape_to_type(_, %AWS.CodeGen.PostService.Shape{type: "document"}, _module_name), do: "any()" - def shape_to_type(_, %AWS.CodeGen.RestService.Shape{type: "document"}, _module_name), do: "any()" + def shape_to_type(_, %Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" + def shape_to_type(_, %Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" + def shape_to_type(_, %Shape{type: "map"}, _module_name), do: "map()" + def shape_to_type(_, %Shape{type: "blob"}, _module_name), do: "binary()" + def shape_to_type(:elixir, %Shape{type: "string"}, _module_name), do: "String.t()" + def shape_to_type(:erlang, %Shape{type: "string"}, _module_name), do: "string()" + def shape_to_type(_, %Shape{type: "integer"}, _module_name), do: "integer()" + def shape_to_type(_, %Shape{type: "boolean"}, _module_name), do: "boolean()" + def shape_to_type(_, %Shape{type: "enum"}, _module_name), do: "list(any())" + def shape_to_type(_, %Shape{type: "union"}, _module_name), do: "list()" + def shape_to_type(_, %Shape{type: "document"}, _module_name), do: "any()" def shape_to_type(context, shape_name, module_name, all_shapes) do case shape_name do "smithy.api#String" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "string"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "string"}, module_name)}]" "smithy.api#Integer" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "integer"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "integer"}, module_name)}]" "smithy.api#Timestamp" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "timestamp"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "timestamp"}, module_name)}]" "smithy.api#PrimitiveLong" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "long"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "long"}, module_name)}]" "smithy.api#Long" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "long"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "long"}, module_name)}]" "smithy.api#Boolean" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "boolean"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "boolean"}, module_name)}]" "smithy.api#PrimitiveBoolean" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "boolean"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "boolean"}, module_name)}]" "smithy.api#Double" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "double"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "double"}, module_name)}]" "smithy.api#Document" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "document"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "document"}, module_name)}]" "smithy.api#Unit" -> "[]" "smithy.api#Float" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "float"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "float"}, module_name)}]" "smithy.api#Blob" -> - "[#{shape_to_type(context.language, %AWS.CodeGen.RestService.Shape{type: "blob"}, module_name)}]" + "[#{shape_to_type(context.language, %Shape{type: "blob"}, module_name)}]" _ -> case all_shapes[shape_name] do - %AWS.CodeGen.PostService.Shape{type: "structure"} -> - type = "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" - if AWS.CodeGen.Util.reserved_type(type) do - "#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}()" - else - "#{type}()" - end - %AWS.CodeGen.RestService.Shape{type: "structure"} -> + %Shape{type: "structure"} -> type = "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" if AWS.CodeGen.Util.reserved_type(type) do "#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}()" @@ -93,15 +77,7 @@ defmodule AWS.CodeGen.Types do "#{type}()" end - %AWS.CodeGen.PostService.Shape{type: "list", member: member} -> - type = "#{shape_to_type(context, member["target"], module_name, all_shapes)}" - if AWS.CodeGen.Util.reserved_type(type) do - "list(#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}())" - else - "list(#{type}())" - end - - %AWS.CodeGen.RestService.Shape{type: "list", member: member} -> + %Shape{type: "list", member: member} -> type = "#{shape_to_type(context, member["target"], module_name, all_shapes)}" if AWS.CodeGen.Util.reserved_type(type) do "list(#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}())"