From a282384599df1b4433957c7141c043e765e5cf44 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Tue, 12 Mar 2024 22:25:24 +0100 Subject: [PATCH] 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 %>