From 75cdac4bc2dd08ef05e0bc8353238ea62d1df956 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Thu, 30 May 2024 14:14:25 +0200 Subject: [PATCH 01/22] Fix link to aws-sdk-go-v2. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f253526..a0a3d89 100644 --- a/README.md +++ b/README.md @@ -75,5 +75,5 @@ Alternatively you can install XCode's Command Line Developer Tools package: $ xcode-select --install -[aws-sdk-go]: https://github.com/aws/aws-sdk-go +[aws-sdk-go]: https://github.com/aws/aws-sdk-go-v2 [brew]: https://brew.sh/ From fcf9e75466d4bd73505c7520367a5d34d2d06792 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Thu, 30 May 2024 14:19:54 +0200 Subject: [PATCH 02/22] Fix SPEC_PATH in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a0a3d89..6ed84dd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Code is generated by running the `generate.exs` script. It requires Elixir 1.8.4 ### Elixir ```bash -export SPEC_PATH=../../aws/aws-sdk-go/models/apis +export SPEC_PATH=../aws-sdk-go-v2/codegen/sdk-codegen/aws-models export TEMPLATE_PATH=priv export ELIXIR_OUTPUT_PATH=../aws-elixir/lib/aws/generated mix run generate.exs elixir $SPEC_PATH $TEMPLATE_PATH $ELIXIR_OUTPUT_PATH @@ -21,7 +21,7 @@ mix run generate.exs elixir $SPEC_PATH $TEMPLATE_PATH $ELIXIR_OUTPUT_PATH ### Erlang ```bash -export SPEC_PATH=../../aws/aws-sdk-go/models/apis +export SPEC_PATH=../aws-sdk-go-v2/codegen/sdk-codegen/aws-models export TEMPLATE_PATH=priv export ERLANG_OUTPUT_PATH=../aws-erlang/src mix run generate.exs erlang $SPEC_PATH $TEMPLATE_PATH $ERLANG_OUTPUT_PATH From 58a5ed2269ff7db6852c522d42ae079fb8e631e5 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 31 May 2024 14:36:43 +0200 Subject: [PATCH 03/22] Move all optional action parameters into Keyword lists, and add type info to Action and improve elixir docs with it. --- lib/aws_codegen/rest_service.ex | 56 +++++++++++++++++++++++++++++---- priv/post.ex.eex | 2 +- priv/rest.ex.eex | 27 +++++++++++++--- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 02d2e50..7acf758 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -76,7 +76,8 @@ defmodule AWS.CodeGen.RestService do defstruct code_name: nil, name: nil, location_name: nil, - required: false + required: false, + type: nil def multi_segment?(parameter, request_uri) do {:ok, re} = Regex.compile("{#{parameter.location_name}\\+}") @@ -360,7 +361,10 @@ defmodule AWS.CodeGen.RestService do |> Enum.filter(filter_fn(param_type)) |> Enum.map(fn {name, x} -> required = Enum.member?(required_members, name) - build_parameter(language, {name, x["traits"][param_type]}, required) + + tynfo = get_type_info(x, api_spec) + + build_parameter(language, {name, x["traits"][param_type]}, required, tynfo) end) end else @@ -368,13 +372,51 @@ defmodule AWS.CodeGen.RestService do end end + def get_type_info(x, api_spec) do + t = x["target"] + type = api_spec["shapes"][t] + + build_type_details(type, api_spec) + end + + def build_type_details(type, _api_spec) when is_binary(type) do + type + end + + # If the timestamp has traits such as `http-date`, or `date-time` include them. + def build_type_details(%{"type" => "timestamp", "traits" => _} = type, _api_spec) do + fmt = type["traits"]["smithy.api#timestampFormat"] + + "timestamp[#{fmt}]" + end + + def build_type_details(%{"type" => "enum"} = type, _api_spec) do + keys = + type["members"] + |> Map.keys() + + ~s/enum["#{Enum.join(keys, "|")}"]/ + end + + def build_type_details(%{"type" => "list"} = type, api_spec) do + deets = + type["member"]["target"] + |> build_type_details(api_spec) + + "list[#{deets}]" + end + + def build_type_details(type, api_spec) do + type["type"] + end + defp filter_fn(location) do fn {_name, member_spec} -> not is_nil(member_spec["traits"][location]) end end - defp build_parameter(language, {name, %{}}, required) do + defp build_parameter(language, {name, %{}}, required, type) do %Parameter{ code_name: if language == :elixir do @@ -384,11 +426,12 @@ defmodule AWS.CodeGen.RestService do end, name: name, location_name: name, - required: required + required: required, + type: type } end - defp build_parameter(language, {name, data}, required) do + defp build_parameter(language, {name, data}, required, type) do %Parameter{ code_name: if language == :elixir do @@ -398,7 +441,8 @@ defmodule AWS.CodeGen.RestService do end, name: name, location_name: data, - required: required + required: required, + type: type } end end diff --git a/priv/post.ex.eex b/priv/post.ex.eex index f839641..6c4ecf1 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -71,7 +71,7 @@ defmodule <%= context.module_name %> do @doc """ <%= action.docstring %> """<% end %> - @spec <%= action.function_name %>(map(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, list()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> + @spec <%= action.function_name %>(AWS.Client.t(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.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.ex.eex b/priv/rest.ex.eex index 0bf88e3..b0d92ac 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -70,17 +70,36 @@ defmodule <%= context.module_name %> do <%= if String.trim(action.docstring) != "" do %> @doc """ <%= action.docstring %> + + ## Required positional parameters:<%= for parameter <- action.url_parameters do %> + • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end %> + + ## Optional parameters:<%= for parameter <- action.query_parameters do %> + • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end + %><%= for parameter <- action.request_header_parameters do %> + • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end + %><%= for parameter <- action.request_headers_parameters do %> + • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end %> """<% end %><%= if action.method == "GET" do %> - @spec <%= action.function_name %>(map()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.function_parameter_types(action.method, action, false)%>, list()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> - def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.function_parameters(action) %>, options \\ []) do + @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> + def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" + + # NOTE: We can't use validate!/2 here because the user might pass options to the client too... + # options = Keyword.validate!(options, [<%= Enum.map(action.query_parameters ++ action.request_header_parameters ++ action.request_headers_parameters, &(&1.code_name <> ": nil")) |> Enum.join(", ") %> + # ]) + headers = []<%= for parameter <- action.request_header_parameters do %> + + {<%= parameter.code_name %>, options} = Keyword.pop(options, :<%= parameter.code_name %>, nil) headers = if !is_nil(<%= parameter.code_name %>) do [{"<%= parameter.location_name %>", <%= parameter.code_name %>} | headers] else headers end<% end %> query_params = []<%= for parameter <- Enum.reverse(action.query_parameters) do %> + + {<%= parameter.code_name %>, options} = Keyword.pop(options, :<%= parameter.code_name %>, nil) query_params = if !is_nil(<%= parameter.code_name %>) do [{"<%= parameter.location_name %>", <%= parameter.code_name %>} | query_params] else @@ -116,8 +135,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 %> -@spec <%= action.function_name %>(map()<%= AWS.CodeGen.Types.function_parameter_types(action.method, action, false)%>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> String.t(), <% end %><%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, list()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> -def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end %>input, options \\ []) do +@spec <%= action.function_name %>(AWS.Client.t()<%= AWS.CodeGen.Types.required_function_parameter_types(action)%>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> String.t(), <% end %><%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> +def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end %>input, options \\ []) do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= 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 4a9292f0c6fd55ec591c062fc948bee60791e51b Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 31 May 2024 15:59:02 +0200 Subject: [PATCH 04/22] Add the first part of the parameter documentation. E.g. the inner part of a

-tag. --- lib/aws_codegen/rest_service.ex | 57 +++++++++++++++++++++++++++++---- priv/rest.ex.eex | 8 ++--- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 7acf758..c2c8e6b 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -77,7 +77,8 @@ defmodule AWS.CodeGen.RestService do name: nil, location_name: nil, required: false, - type: nil + type: nil, + docs: nil def multi_segment?(parameter, request_uri) do {:ok, re} = Regex.compile("{#{parameter.location_name}\\+}") @@ -364,7 +365,16 @@ defmodule AWS.CodeGen.RestService do tynfo = get_type_info(x, api_spec) - build_parameter(language, {name, x["traits"][param_type]}, required, tynfo) + docs = get_in(x, ["traits", "smithy.api#documentation"]) + + docs = + if is_nil(docs) do + "" + else + extract_param_docs_snippet(docs) + end + + build_parameter(language, {name, x["traits"][param_type]}, required, tynfo, docs) end) end else @@ -372,6 +382,39 @@ defmodule AWS.CodeGen.RestService do end end + def extract_param_docs_snippet(docs) do + case Floki.parse_fragment(docs) do + {:ok, [{"p", _attrs, inner_content} | _rest]} -> + inner_content |> sanitize_html() |> Floki.raw_html() + + {:ok, [first_node | _rest]} -> + first_node |> sanitize_html() |> Floki.raw_html() + + {:error, _} -> + "" + end + end + + def sanitize_html(tree) do + tree + # NOTE: This doesn't work, because it only updates the inner part of the tag. + # |> Floki.find_and_update("code", fn + # {"code", inner} -> + # "`#{inner}`" + # + # other -> + # IO.inspect(other) + # other + # end) + |> Floki.find_and_update("p", fn + {"p", inner} -> + inner + + other -> + other + end) + end + def get_type_info(x, api_spec) do t = x["target"] type = api_spec["shapes"][t] @@ -416,7 +459,7 @@ defmodule AWS.CodeGen.RestService do end end - defp build_parameter(language, {name, %{}}, required, type) do + defp build_parameter(language, {name, %{}}, required, type, docs) do %Parameter{ code_name: if language == :elixir do @@ -427,11 +470,12 @@ defmodule AWS.CodeGen.RestService do name: name, location_name: name, required: required, - type: type + type: type, + docs: docs } end - defp build_parameter(language, {name, data}, required, type) do + defp build_parameter(language, {name, data}, required, type, docs) do %Parameter{ code_name: if language == :elixir do @@ -442,7 +486,8 @@ defmodule AWS.CodeGen.RestService do name: name, location_name: data, required: required, - type: type + type: type, + docs: docs } end end diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index b0d92ac..7ea20d8 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -72,14 +72,14 @@ defmodule <%= context.module_name %> do <%= action.docstring %> ## Required positional parameters:<%= for parameter <- action.url_parameters do %> - • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end %> + * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end %> ## Optional parameters:<%= for parameter <- action.query_parameters do %> - • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end + * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end %><%= for parameter <- action.request_header_parameters do %> - • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end + * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end %><%= for parameter <- action.request_headers_parameters do %> - • :<%= parameter.code_name %> (t:<%= parameter.type %>) <%= parameter.location_name %><% end %> + * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end %> """<% end %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do From 27f10934b6fab4b7c144379548255078ab081767 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 31 May 2024 17:22:25 +0200 Subject: [PATCH 05/22] Add text wrapping of parameter docs with Excribe. --- lib/aws_codegen.ex | 10 ++++++++++ lib/aws_codegen/rest_service.ex | 8 ++++++-- mix.exs | 1 + mix.lock | 1 + priv/rest.ex.eex | 8 ++++---- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index 8fc0483..70d217a 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -1,4 +1,5 @@ defmodule AWS.CodeGen do + alias AWS.CodeGen.RestService.Parameter alias AWS.CodeGen.Spec @moduledoc """ @@ -132,6 +133,15 @@ defmodule AWS.CodeGen do format_string!(context.language, rendered) end + @param_quoted_elixir EEx.compile_string( + ~s|* `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %>| + ) + def render_parameter(:elixir, %Parameter{} = parameter) do + Code.eval_quoted(@param_quoted_elixir, parameter: parameter) + |> then(&elem(&1, 0)) + |> Excribe.format(width: 80, hanging: 4, pretty: true) + end + defp format_string!(:elixir, rendered) do [Code.format_string!(rendered), ?\n] end diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index c2c8e6b..4c417df 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -385,10 +385,14 @@ defmodule AWS.CodeGen.RestService do def extract_param_docs_snippet(docs) do case Floki.parse_fragment(docs) do {:ok, [{"p", _attrs, inner_content} | _rest]} -> - inner_content |> sanitize_html() |> Floki.raw_html() + inner_content + |> sanitize_html() + |> Floki.text() {:ok, [first_node | _rest]} -> - first_node |> sanitize_html() |> Floki.raw_html() + first_node + |> sanitize_html() + |> Floki.text() {:error, _} -> "" diff --git a/mix.exs b/mix.exs index 60d3721..bc63673 100644 --- a/mix.exs +++ b/mix.exs @@ -25,6 +25,7 @@ defmodule AWS.CodeGen.Mixfile do [ {:earmark, "~> 1.4", only: :dev}, {:ex_doc, "~> 0.31.1", only: :dev}, + {:excribe, "~> 0.1.1", only: :dev}, {:floki, "~> 0.35"}, {:fast_html, "~> 2.3"}, {:poison, "~> 4.0 or ~> 5.0"} diff --git a/mix.lock b/mix.lock index 75a2f5d..aeb3876 100644 --- a/mix.lock +++ b/mix.lock @@ -3,6 +3,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"}, "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, + "excribe": {:hex, :excribe, "0.1.1", "3276fb00c2dd2928e06a29521a9b78934de8929f5a8081de39510d7df5a3c7ac", [:mix], [], "hexpm", "e6b26840f340cb20e6dbf774c556a6cbd4e8a3aec3a34f366749ba6d98dba3e3"}, "fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"}, "floki": {:hex, :floki, "0.35.3", "0c8c6234aa71cb2b069cf801e8f8f30f8d096eb452c3dae2ccc409510ec32720", [:mix], [], "hexpm", "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 7ea20d8..22336fd 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -72,14 +72,14 @@ defmodule <%= context.module_name %> do <%= action.docstring %> ## Required positional parameters:<%= for parameter <- action.url_parameters do %> - * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> ## Optional parameters:<%= for parameter <- action.query_parameters do %> - * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.request_header_parameters do %> - * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.request_headers_parameters do %> - * `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %><% end %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> """<% end %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do From ee5d7d1ca5d2ceb48a79cb488f8e416619d8571d Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Sat, 1 Jun 2024 12:53:40 +0200 Subject: [PATCH 06/22] Move params docs near the top of the action docstring. --- lib/aws_codegen/rest_service.ex | 28 +++++++++++++++++++++++----- priv/rest.ex.eex | 5 ++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 4c417df..e6cb111 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -8,6 +8,8 @@ defmodule AWS.CodeGen.RestService do defstruct arity: nil, docstring: nil, + docstring_header: nil, + docstring_rest: nil, method: nil, request_uri: nil, success_status_code: nil, @@ -281,13 +283,29 @@ defmodule AWS.CodeGen.RestService do input_shape = Shapes.get_input_shape(operation_spec) output_shape = Shapes.get_output_shape(operation_spec) + docstring = + Docstring.format( + language, + operation_spec["traits"]["smithy.api#documentation"] + ) + + [docstring_header, docstring_rest] = + case String.split(docstring, "\n", parts: 2, trim: true) do + [a, b] -> + [a, b] + + [a] -> + [a, ""] + + [] -> + ["", ""] + end + %Action{ arity: length(url_parameters) + len_for_method, - docstring: - Docstring.format( - language, - operation_spec["traits"]["smithy.api#documentation"] - ), + docstring: docstring, + docstring_header: docstring_header, + docstring_rest: docstring_rest, method: method, request_uri: request_uri, success_status_code: success_status_code(operation_spec), diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 22336fd..a72f410 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -69,7 +69,7 @@ defmodule <%= context.module_name %> do <%= if String.trim(action.docstring) != "" do %> @doc """ -<%= action.docstring %> +<%= action.docstring_header %> ## Required positional parameters:<%= for parameter <- action.url_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> @@ -80,6 +80,9 @@ defmodule <%= context.module_name %> do <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.request_headers_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> +<%= if action.docstring_rest != "" do %> +## Details +<%= action.docstring_rest %><% end %> """<% end %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do From d1515f5134f65e95e6dc05b66a5c8117ca4c20b4 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Sat, 1 Jun 2024 14:38:30 +0200 Subject: [PATCH 07/22] Add link to docs (search). --- lib/aws_codegen/docstring.ex | 41 ++++++++++++++++++++++----------- lib/aws_codegen/rest_service.ex | 33 ++++++++++++++------------ priv/rest.ex.eex | 9 ++++---- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/lib/aws_codegen/docstring.ex b/lib/aws_codegen/docstring.ex index 2a5e21f..94453d2 100644 --- a/lib/aws_codegen/docstring.ex +++ b/lib/aws_codegen/docstring.ex @@ -10,18 +10,33 @@ defmodule AWS.CodeGen.Docstring do heredoc in generated Elixir or Erlang code. """ def format(:elixir, text) do - text - |> html_to_markdown() - |> split_first_sentence_in_one_line() - |> split_paragraphs() - |> Enum.map(&justify_line(&1, @max_elixir_line_length)) - |> Enum.join("\n") - |> fix_broken_markdown_links() - |> fix_elixir_lookalike_format_strings() - |> fix_html_spaces() - |> fix_long_break_lines() - |> transform_subtitles() - |> String.trim_trailing() + docstring = + text + |> html_to_markdown() + |> fix_broken_markdown_links() + |> fix_elixir_lookalike_format_strings() + |> fix_html_spaces() + |> fix_long_break_lines() + |> transform_subtitles() + |> String.trim_trailing() + + # Split off the beginning of the docs. + [docstring_header, _docstring_rest] = + case String.split(docstring, "\n", parts: 3, trim: true) do + [a, b, rest] -> + [a <> "\n" <> b, rest] + + [a, b] -> + [a, b] + + [a] -> + [a, ""] + + [] -> + ["", ""] + end + + docstring_header |> Excribe.format(width: 80, hanging: 2) end def format(:erlang, nil), do: "" @@ -149,7 +164,7 @@ defmodule AWS.CodeGen.Docstring do defp update_nodes({tag, _, children}) when tag in ~w(i em), do: "*#{Floki.text(children)}*" defp update_nodes({tag, _, children}) when tag in ~w(p fullname note important), - do: Floki.text(children) <> @two_break_lines + do: (Floki.text(children) |> String.replace("\n", " ")) <> @two_break_lines defp update_nodes({"a", attrs, children}) do case Enum.find(attrs, fn {attr, _} -> attr == "href" end) do diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index e6cb111..5d0aa32 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -9,7 +9,7 @@ defmodule AWS.CodeGen.RestService do defstruct arity: nil, docstring: nil, docstring_header: nil, - docstring_rest: nil, + docs_url: nil, method: nil, request_uri: nil, success_status_code: nil, @@ -225,6 +225,18 @@ defmodule AWS.CodeGen.RestService do defp collect_actions(language, api_spec) do shapes = api_spec["shapes"] + service_name = + shapes + |> Map.keys() + |> List.first() + |> then(fn name -> + name + |> String.split("#") + |> hd() + |> String.split(".") + |> List.last() + end) + operations = Enum.reduce(shapes, [], fn {_, shape}, acc -> case shape["type"] do @@ -260,6 +272,10 @@ defmodule AWS.CodeGen.RestService do function_name = AWS.CodeGen.Name.to_snake_case(operation) request_header_parameters = collect_request_header_parameters(language, api_spec, operation) + # The AWS Docs sometimes use an arbitrary service name, so we cannot build direct urls. Instead we just link to a search + docs_url = + "https://docs.aws.amazon.com/search/doc-search.html?searchPath=documentation&searchQuery=#{service_name}%20#{operation |> String.split("#") |> List.last()}&this_doc_guide=API%2520Reference" + is_required = fn param -> param.required end required_query_parameters = Enum.filter(query_parameters, is_required) required_request_header_parameters = Enum.filter(request_header_parameters, is_required) @@ -289,23 +305,10 @@ defmodule AWS.CodeGen.RestService do operation_spec["traits"]["smithy.api#documentation"] ) - [docstring_header, docstring_rest] = - case String.split(docstring, "\n", parts: 2, trim: true) do - [a, b] -> - [a, b] - - [a] -> - [a, ""] - - [] -> - ["", ""] - end - %Action{ arity: length(url_parameters) + len_for_method, docstring: docstring, - docstring_header: docstring_header, - docstring_rest: docstring_rest, + docs_url: docs_url, method: method, request_uri: request_uri, success_status_code: success_status_code(operation_spec), diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index a72f410..d52b9f1 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -69,9 +69,11 @@ defmodule <%= context.module_name %> do <%= if String.trim(action.docstring) != "" do %> @doc """ -<%= action.docstring_header %> + <%= action.docstring %> - ## Required positional parameters:<%= for parameter <- action.url_parameters do %> + [API Reference](<%= action.docs_url %>) + + ## Parameters:<%= for parameter <- action.url_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> ## Optional parameters:<%= for parameter <- action.query_parameters do %> @@ -80,9 +82,6 @@ defmodule <%= context.module_name %> do <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.request_headers_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> -<%= if action.docstring_rest != "" do %> -## Details -<%= action.docstring_rest %><% end %> """<% end %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do From 545f5f801a59aece8d85c4dde3775858949e1772 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Tue, 16 Jul 2024 15:05:04 +0200 Subject: [PATCH 08/22] Sort parameters into required and optional in the REST Action Validate optional parameters. --- lib/aws_codegen/elixir_helpers.ex | 30 ++++++++++++++++++++++ lib/aws_codegen/rest_service.ex | 8 ++++++ priv/rest.ex.eex | 41 ++++++++++++++++++++----------- 3 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 lib/aws_codegen/elixir_helpers.ex diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex new file mode 100644 index 0000000..91601d2 --- /dev/null +++ b/lib/aws_codegen/elixir_helpers.ex @@ -0,0 +1,30 @@ +defmodule AWS.CodeGen.ElixirHelpers do + require EEx + + @validate_quoted EEx.compile_string(~s{ + # Validate optional parameters + optional_params = [<%= Enum.map(action.optional_query_parameters ++ action.optional_request_header_parameters ++ action.request_headers_parameters, &(&1.code_name <> ": nil")) |> Enum.join(", ") %>] + options = Keyword.validate!(options, [enable_retries?: false, retry_num: 0, retry_opts: []] ++ optional_params) + }) + def define_and_validate_optionals(action) do + {res, _} = Code.eval_quoted(@validate_quoted, action: action) + res + end + + @drop_optionals_quoted EEx.compile_string(~s{ + # Drop optionals that have been moved to query/header-params + options = options + |> Keyword.drop([<%= action.optional_query_parameters ++ action.optional_request_header_parameters |> Enum.map(fn act -> ":" <> act.code_name end) |> Enum.join(", ") %>]) + }) + def drop_optionals(action) do + if Enum.empty?(action.optional_query_parameters) and + Enum.empty?(action.optional_request_header_parameters) do + # Don't drop anything, if there are no optional params + nil + else + # Drop the optional params + {res, _} = Code.eval_quoted(@drop_optionals_quoted, action: action) + res + end + end +end diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 5d0aa32..a6eedd1 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -18,9 +18,11 @@ defmodule AWS.CodeGen.RestService do url_parameters: [], query_parameters: [], required_query_parameters: [], + optional_query_parameters: [], request_header_parameters: [], request_headers_parameters: [], required_request_header_parameters: [], + optional_request_header_parameters: [], response_header_parameters: [], send_body_as_binary?: false, receive_body_as_binary?: false, @@ -279,6 +281,10 @@ defmodule AWS.CodeGen.RestService do is_required = fn param -> param.required end required_query_parameters = Enum.filter(query_parameters, is_required) required_request_header_parameters = Enum.filter(request_header_parameters, is_required) + + is_not_required = fn param -> not param.required end + optional_query_parameters = Enum.filter(query_parameters, is_not_required) + optional_request_header_parameters = Enum.filter(request_header_parameters, is_not_required) method = operation_spec["traits"]["smithy.api#http"]["method"] len_for_method = @@ -317,8 +323,10 @@ defmodule AWS.CodeGen.RestService do url_parameters: url_parameters, query_parameters: query_parameters, required_query_parameters: required_query_parameters, + optional_query_parameters: optional_query_parameters, request_header_parameters: request_header_parameters, required_request_header_parameters: required_request_header_parameters, + optional_request_header_parameters: optional_request_header_parameters, response_header_parameters: collect_response_header_parameters(language, api_spec, operation), send_body_as_binary?: Shapes.body_as_binary?(shapes, input_shape), diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index d52b9f1..130f52f 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -74,11 +74,15 @@ defmodule <%= context.module_name %> do [API Reference](<%= action.docs_url %>) ## Parameters:<%= for parameter <- action.url_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.required_query_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.required_request_header_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> - ## Optional parameters:<%= for parameter <- action.query_parameters do %> + ## Optional parameters:<%= for parameter <- action.optional_query_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.request_header_parameters do %> + %><%= for parameter <- action.optional_request_header_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.request_headers_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> @@ -87,23 +91,24 @@ defmodule <%= context.module_name %> do def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" - # NOTE: We can't use validate!/2 here because the user might pass options to the client too... - # options = Keyword.validate!(options, [<%= Enum.map(action.query_parameters ++ action.request_header_parameters ++ action.request_headers_parameters, &(&1.code_name <> ": nil")) |> Enum.join(", ") %> - # ]) + <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> - headers = []<%= for parameter <- action.request_header_parameters do %> + # Required headers + headers = [<%= action.required_request_header_parameters |> Enum.map(fn parameter -> ~s|{"#{parameter.location_name}", #{parameter.code_name}}| end) |> Enum.join(",") %>] - {<%= parameter.code_name %>, options} = Keyword.pop(options, :<%= parameter.code_name %>, nil) - headers = if !is_nil(<%= parameter.code_name %>) do - [{"<%= parameter.location_name %>", <%= parameter.code_name %>} | headers] + # Optional headers<%= for parameter <- Enum.reverse(action.optional_request_header_parameters) do %> + headers = if opt_val = Keyword.get(options, :<%= parameter.code_name %>) do + [{"<%= parameter.location_name %>", opt_val} | headers] else headers end<% end %> - query_params = []<%= for parameter <- Enum.reverse(action.query_parameters) do %> - {<%= parameter.code_name %>, options} = Keyword.pop(options, :<%= parameter.code_name %>, nil) - query_params = if !is_nil(<%= parameter.code_name %>) do - [{"<%= parameter.location_name %>", <%= parameter.code_name %>} | query_params] + # Required query params + query_params = [<%= action.required_query_parameters |> Enum.map(fn parameter -> ~s|{"#{parameter.location_name}", #{parameter.code_name}}| end) |> Enum.join(",") %>] + + # Optional query params<%= for parameter <- Enum.reverse(action.optional_query_parameters) do %> + query_params = if opt_val = Keyword.get(options, :<%= parameter.code_name %>) do + [{"<%= parameter.location_name %>", opt_val} | query_params] else query_params end<% end %><%= if length(action.response_header_parameters) > 0 do %> @@ -136,10 +141,16 @@ defmodule <%= context.module_name %> do metadata() <% end %> + <%= AWS.CodeGen.ElixirHelpers.drop_optionals(action) %> + Request.request_rest(client, meta, :get, url_path, query_params, headers, nil, options, <%= inspect(action.success_status_code) %>)<% else %> @spec <%= action.function_name %>(AWS.Client.t()<%= AWS.CodeGen.Types.required_function_parameter_types(action)%>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> String.t(), <% end %><%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end %>input, options \\ []) do - url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>"<%= if length(action.request_header_parameters) > 0 do %> + url_path = "<%= if context.module_name == ~s(AWS.ApiGatewayManagementApi) do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>"<%= if length(action.request_header_parameters) > 0 do %> + + optional_params = [<%= Enum.map(action.query_parameters ++ action.request_header_parameters ++ action.request_headers_parameters, &(&1.code_name <> ": nil")) |> Enum.join(", ") %>] + options = Keyword.validate!(options, [enable_retries?: false, retry_num: 0, retry_opts: []] ++ optional_params) + {headers, input} = [<%= for parameter <- action.request_header_parameters do %> {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> @@ -189,6 +200,8 @@ def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.re metadata() <% end %> + <%= AWS.CodeGen.ElixirHelpers.drop_optionals(action) %> + Request.request_rest(client, meta, <%= AWS.CodeGen.RestService.Action.method(action) %>, url_path, query_params, headers, input, options, <%= inspect(action.success_status_code) %>)<% end %> end<% end %> end From dc18ee4fecd40110e631a06c60e37cb0e0a27871 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Tue, 16 Jul 2024 15:44:55 +0200 Subject: [PATCH 09/22] Dirty fix for types named `identifier`. See latest quicksight. --- lib/aws_codegen/types.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex index dba1bb9..0801a6e 100644 --- a/lib/aws_codegen/types.ex +++ b/lib/aws_codegen/types.ex @@ -194,7 +194,8 @@ defmodule AWS.CodeGen.Types do end defp reserved_type(type) do - type == "node" || type == "term" || type == "function" || type == "reference" + type == "node" || type == "term" || type == "function" || type == "reference" || + type == "identifier" end def function_argument_type(:elixir, action) do From 3acf04b8dbdb02587041546665dae57fa1929276 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Wed, 17 Jul 2024 11:18:32 +0200 Subject: [PATCH 10/22] Document the `input` params for PostServices. --- lib/aws_codegen/docstring.ex | 18 ++++++++ lib/aws_codegen/elixir_helpers.ex | 70 +++++++++++++++++++++++++++++++ lib/aws_codegen/post_service.ex | 5 +++ lib/aws_codegen/rest_service.ex | 15 +------ priv/post.ex.eex | 19 ++++----- priv/rest.ex.eex | 22 +--------- 6 files changed, 104 insertions(+), 45 deletions(-) diff --git a/lib/aws_codegen/docstring.ex b/lib/aws_codegen/docstring.ex index 94453d2..c23f880 100644 --- a/lib/aws_codegen/docstring.ex +++ b/lib/aws_codegen/docstring.ex @@ -382,4 +382,22 @@ defmodule AWS.CodeGen.Docstring do List.flatten(lines ++ [current]) end + + def docs_url(shapes, operation) do + service_name = + shapes + |> Map.keys() + |> List.first() + |> then(fn name -> + name + |> String.split("#") + |> hd() + |> String.split(".") + |> List.last() + end) + + op_name = operation |> String.split("#") |> List.last() + + "https://docs.aws.amazon.com/search/doc-search.html?searchPath=documentation&searchQuery=#{service_name}%20#{op_name}&this_doc_guide=API%2520Reference" + end end diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 91601d2..af44f89 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -1,4 +1,6 @@ defmodule AWS.CodeGen.ElixirHelpers do + alias AWS.CodeGen.PostService + alias AWS.CodeGen.RestService require EEx @validate_quoted EEx.compile_string(~s{ @@ -27,4 +29,72 @@ defmodule AWS.CodeGen.ElixirHelpers do res end end + + def render_type_fields(_type_name, type_fields, indent \\ 4) do + indent_str = String.duplicate(" ", indent) + + Enum.map_join(type_fields, ",\n" <> indent_str, fn {field_name, field_type} -> + field_name <> field_type + end) + end + + @docstring_rest_quoted EEx.compile_string(~s{ + <%= if String.trim(action.docstring) != "" do %> + @doc """ + <%= action.docstring %> + + [API Reference](<%= action.docs_url %>) + + ## Parameters:<%= for parameter <- action.url_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.required_query_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.required_request_header_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> + + ## Optional parameters:<%= for parameter <- action.optional_query_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.optional_request_header_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.request_headers_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> + """<% end %> + }) + def render_docstring(%RestService.Action{} = action, _context, _types) do + {res, _} = Code.eval_quoted(@docstring_rest_quoted, action: action) + res + end + + @docstring_post_quoted EEx.compile_string(~s/ + <%= if String.trim(action.docstring) != "" do %> + @doc """ + <%= action.docstring %> + + [API Reference](<%= action.docs_url %>) + + ## Parameters: + * `:input` (`t:<%= input_type %>`): + %{ + <%= AWS.CodeGen.ElixirHelpers.render_type_fields(input_type, type_fields, 9) %> + } + """<% end %>/) + def render_docstring(%PostService.Action{} = action, context, types) do + input_type = + AWS.CodeGen.Types.function_argument_type(:elixir, action) + # TODO: This is dirty. + |> String.split("(") + |> hd() + + type_fields = + types[input_type] + + {res, _} = + Code.eval_quoted(@docstring_post_quoted, + action: action, + input_type: input_type, + type_fields: type_fields + ) + + res + end end diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index bcdf314..89fca08 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -6,6 +6,7 @@ defmodule AWS.CodeGen.PostService do defmodule Action do defstruct arity: nil, docstring: nil, + docs_url: nil, function_name: nil, input: nil, output: nil, @@ -159,6 +160,9 @@ defmodule AWS.CodeGen.PostService do Enum.map(operations, fn operation -> operation_spec = shapes[operation] + # The AWS Docs sometimes use an arbitrary service name, so we cannot build direct urls. Instead we just link to a search + docs_url = Docstring.docs_url(shapes, operation) + %Action{ arity: 3, docstring: @@ -166,6 +170,7 @@ defmodule AWS.CodeGen.PostService do language, operation_spec["traits"]["smithy.api#documentation"] ), + docs_url: docs_url, 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\.[^#]+#/, ""), diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index a6eedd1..58f7626 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -227,18 +227,6 @@ defmodule AWS.CodeGen.RestService do defp collect_actions(language, api_spec) do shapes = api_spec["shapes"] - service_name = - shapes - |> Map.keys() - |> List.first() - |> then(fn name -> - name - |> String.split("#") - |> hd() - |> String.split(".") - |> List.last() - end) - operations = Enum.reduce(shapes, [], fn {_, shape}, acc -> case shape["type"] do @@ -275,8 +263,7 @@ defmodule AWS.CodeGen.RestService do request_header_parameters = collect_request_header_parameters(language, api_spec, operation) # The AWS Docs sometimes use an arbitrary service name, so we cannot build direct urls. Instead we just link to a search - docs_url = - "https://docs.aws.amazon.com/search/doc-search.html?searchPath=documentation&searchQuery=#{service_name}%20#{operation |> String.split("#") |> List.last()}&this_doc_guide=API%2520Reference" + docs_url = Docstring.docs_url(shapes, operation) is_required = fn param -> param.required end required_query_parameters = Enum.filter(query_parameters, is_required) diff --git a/priv/post.ex.eex b/priv/post.ex.eex index 6c4ecf1..987526c 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -11,17 +11,17 @@ defmodule <%= context.module_name %> do alias AWS.Client alias AWS.Request - <%= for {type_name, type_fields} <- AWS.CodeGen.Types.types(context) do %> +<% types = AWS.CodeGen.Types.types(context) %> + +<%= for {type_name, type_fields} <- types do %> @typedoc """ ## Example: <%= if map_size(type_fields) == 0 do %> - <%= "#{type_name}() :: %{}" %> + <%= type_name %>() :: %{} <% else %> - <%= "#{type_name}() :: %{" %> - <%= Enum.map_join(type_fields, ",\n ", fn {field_name, field_type} -> - ~s{ #{field_name}#{field_type}} - end) %> + <%= type_name %>() :: %{ + <%= AWS.CodeGen.ElixirHelpers.render_type_fields(type_name, type_fields, 6) %> } <% end %> """ @@ -33,7 +33,7 @@ defmodule <%= context.module_name %> do errors = action.errors if not is_nil(errors) do errors_snakecased = Enum.map(errors, fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) - all_types = AWS.CodeGen.Types.types(context) + all_types = types error_types = Enum.reduce(all_types, [], fn {type_name, _type_fields}, acc -> @@ -67,10 +67,7 @@ defmodule <%= context.module_name %> do } end <%= for action <- context.actions do %> - <%= if String.trim(action.docstring) != "" do %> - @doc """ -<%= action.docstring %> - """<% end %> + <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> @spec <%= action.function_name %>(AWS.Client.t(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client, input, options \\ []) do meta = diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 130f52f..c4cbc45 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -67,26 +67,8 @@ defmodule <%= context.module_name %> do } end<%= for action <- context.actions do %> - <%= if String.trim(action.docstring) != "" do %> - @doc """ - <%= action.docstring %> - - [API Reference](<%= action.docs_url %>) - - ## Parameters:<%= for parameter <- action.url_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.required_query_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.required_request_header_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> - - ## Optional parameters:<%= for parameter <- action.optional_query_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.optional_request_header_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.request_headers_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> - """<% end %><%= if action.method == "GET" do %> + <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context) + %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" From bc51b1af40a0a24f4471a0756a54fcbb5c124337 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Wed, 17 Jul 2024 11:37:24 +0200 Subject: [PATCH 11/22] Fix Rest. --- priv/rest.ex.eex | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index c4cbc45..27136b0 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -11,19 +11,19 @@ defmodule <%= context.module_name %> do alias AWS.Client alias AWS.Request - <%= for {type_name, type_fields} <- AWS.CodeGen.Types.types(context) do %> +<% types = AWS.CodeGen.Types.types(context) %> + +<%= for {type_name, type_fields} <- types do %> @typedoc """ ## Example: -<%= if Enum.empty?(type_fields) do %> - <%= "#{type_name}() :: %{}" %> -<% else %> - <%= "#{type_name}() :: %{" %> - <%= Enum.map_join(type_fields, ",\n ", fn {field_name, field_type} -> - ~s{ #{field_name}#{field_type}} - end) %> + <%= if Enum.empty?(type_fields) do %> + <%= type_name %>() :: %{} + <% else %> + <%= type_name %>() :: %{ + <%= AWS.CodeGen.ElixirHelpers.render_type_fields(type_name, type_fields, 6) %> } -<% end %> + <% end %> """ @type <%= if Enum.empty?(type_fields) do "#{type_name}() :: %{}" else "#{type_name}() :: %{String.t => any()}" end %> <% end %> @@ -67,7 +67,7 @@ defmodule <%= context.module_name %> do } end<%= for action <- context.actions do %> - <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context) + <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do From ef78fd83c32d178404a11277289560a0975b2718 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 19 Jul 2024 13:52:58 +0200 Subject: [PATCH 12/22] Handle parameters that are sent as HTTP bodies better: - Document type (binary vs map). - Add guards for body. - Add guards for required params. - Refactor required/optional params collection. --- lib/aws_codegen/elixir_helpers.ex | 65 ++++++++++++++++++++++++-- lib/aws_codegen/post_service.ex | 37 ++++++++++++++- lib/aws_codegen/rest_service.ex | 69 ++++++++++++++++++++++------ priv/post.ex.eex | 2 +- priv/rest.ex.eex | 76 ++++++++++++++++++++++--------- 5 files changed, 207 insertions(+), 42 deletions(-) diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index af44f89..4503931 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -50,13 +50,20 @@ defmodule AWS.CodeGen.ElixirHelpers do %><%= for parameter <- action.required_query_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.required_request_header_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= if action.send_body_as_binary? do %> + * `:input` (`t:binary`) + <% else %><%= if action.has_body? do %> + * `:input` (`t:map`):<%= for parameter <- action.required_body_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.optional_body_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><% end + %><% end %> ## Optional parameters:<%= for parameter <- action.optional_query_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.optional_request_header_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.request_headers_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> """<% end %> }) @@ -97,4 +104,56 @@ defmodule AWS.CodeGen.ElixirHelpers do res end + + def render_guards(action) do + required_params = + action.required_query_parameters ++ action.required_request_header_parameters + + body_guard = + cond do + action.send_body_as_binary? -> + # TODO: Distinguish between optional and required http bodies. + "is_binary(input) or is_nil(input)" + + action.has_body? -> + # TODO: Distinguish between optional and required http bodies. + "is_map(input) or is_nil(input)" + + true -> + "" + end + + req_guards = + required_params + |> Enum.map(fn param -> + case param.type do + "integer" -> + "is_integer(#{param.code_name})" + + "long" -> + "is_integer(#{param.code_name})" + + "string" -> + "is_binary(#{param.code_name})" + + "list[" <> _ -> + "is_binary(#{param.code_name})" + end + end) + |> Enum.join(" and ") + + cond do + body_guard == "" and req_guards == "" -> + "" + + body_guard == "" -> + "when " <> req_guards + + req_guards == "" -> + "when " <> body_guard + + true -> + "when (" <> body_guard <> ") and " <> req_guards + end + end end diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index 89fca08..6694f42 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -10,9 +10,25 @@ defmodule AWS.CodeGen.PostService do function_name: nil, input: nil, output: nil, + url_parameters: [], + has_body?: false, + # body_required?: false, + body_parameters: [], + required_body_parameters: [], + optional_body_parameters: [], + query_parameters: [], + required_query_parameters: [], + optional_query_parameters: [], + request_header_parameters: [], + request_headers_parameters: [], + required_request_header_parameters: [], + optional_request_header_parameters: [], errors: %{}, host_prefix: nil, - name: nil + name: nil, + send_body_as_binary?: false, + language: nil, + method: :post end @configuration %{ @@ -127,6 +143,16 @@ defmodule AWS.CodeGen.PostService do end end + defp collect_params(language, api_spec, operation) do + AWS.CodeGen.RestService.collect_parameters( + language, + api_spec, + operation, + "members", + "smithy.api#input" + ) + end + defp collect_actions(language, api_spec) do shapes = api_spec["shapes"] @@ -163,6 +189,12 @@ defmodule AWS.CodeGen.PostService do # The AWS Docs sometimes use an arbitrary service name, so we cannot build direct urls. Instead we just link to a search docs_url = Docstring.docs_url(shapes, operation) + # input_shape = Shapes.get_input_shape(operation_spec) + + params = + collect_params(language, api_spec, operation) + |> dbg() + %Action{ arity: 3, docstring: @@ -176,7 +208,8 @@ defmodule AWS.CodeGen.PostService do name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, ""), input: operation_spec["input"], output: operation_spec["output"], - errors: operation_spec["errors"] + errors: operation_spec["errors"], + language: language } end) |> Enum.sort(fn a, b -> a.function_name < b.function_name end) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 58f7626..086dd2d 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -16,6 +16,11 @@ defmodule AWS.CodeGen.RestService do function_name: nil, name: nil, url_parameters: [], + has_body?: false, + # body_required?: false, + body_parameters: [], + required_body_parameters: [], + optional_body_parameters: [], query_parameters: [], required_query_parameters: [], optional_query_parameters: [], @@ -204,7 +209,20 @@ defmodule AWS.CodeGen.RestService do end _ -> - [] + case required_only do + false -> + [ + join_parameters(action.query_parameters, language), + join_parameters(action.request_header_parameters, language), + join_parameters(action.request_headers_parameters, language) + ] + + true -> + [ + join_parameters(action.required_query_parameters, language), + join_parameters(action.required_request_header_parameters, language) + ] + end end ]) end @@ -258,6 +276,7 @@ defmodule AWS.CodeGen.RestService do operation_spec = shapes[operation] request_uri = operation_spec["traits"]["smithy.api#http"]["uri"] url_parameters = collect_url_parameters(language, api_spec, operation) + body_parameters = collect_body_parameters(language, api_spec, operation) query_parameters = collect_query_parameters(language, api_spec, operation) function_name = AWS.CodeGen.Name.to_snake_case(operation) request_header_parameters = collect_request_header_parameters(language, api_spec, operation) @@ -265,13 +284,13 @@ defmodule AWS.CodeGen.RestService do # The AWS Docs sometimes use an arbitrary service name, so we cannot build direct urls. Instead we just link to a search docs_url = Docstring.docs_url(shapes, operation) - is_required = fn param -> param.required end - required_query_parameters = Enum.filter(query_parameters, is_required) - required_request_header_parameters = Enum.filter(request_header_parameters, is_required) + {required_query_params, opt_query_params} = split_parameters(query_parameters) + + {required_request_header_params, opt_request_header_params} = + split_parameters(request_header_parameters) + + {required_body_params, opt_body_params} = split_parameters(body_parameters) - is_not_required = fn param -> not param.required end - optional_query_parameters = Enum.filter(query_parameters, is_not_required) - optional_request_header_parameters = Enum.filter(request_header_parameters, is_not_required) method = operation_spec["traits"]["smithy.api#http"]["method"] len_for_method = @@ -279,10 +298,10 @@ defmodule AWS.CodeGen.RestService do "GET" -> case language do :elixir -> - 2 + length(request_header_parameters) + length(query_parameters) + 2 + length(required_request_header_params) + length(required_query_params) :erlang -> - 4 + length(required_request_header_parameters) + length(required_query_parameters) + 4 + length(required_request_header_params) + length(required_query_params) end _ -> @@ -298,6 +317,9 @@ defmodule AWS.CodeGen.RestService do operation_spec["traits"]["smithy.api#documentation"] ) + has_body? = not Enum.empty?(body_parameters) + send_body_as_binary? = Shapes.body_as_binary?(shapes, input_shape) + %Action{ arity: length(url_parameters) + len_for_method, docstring: docstring, @@ -309,14 +331,19 @@ defmodule AWS.CodeGen.RestService do name: operation, url_parameters: url_parameters, query_parameters: query_parameters, - required_query_parameters: required_query_parameters, - optional_query_parameters: optional_query_parameters, + body_parameters: body_parameters, + required_body_parameters: required_body_params, + optional_body_parameters: opt_body_params, + has_body?: has_body?, + # body_required?: not send_body_as_binary? and Enum.empty?(required_body_params), + required_query_parameters: required_query_params, + optional_query_parameters: opt_query_params, request_header_parameters: request_header_parameters, - required_request_header_parameters: required_request_header_parameters, - optional_request_header_parameters: optional_request_header_parameters, + required_request_header_parameters: required_request_header_params, + optional_request_header_parameters: opt_request_header_params, response_header_parameters: collect_response_header_parameters(language, api_spec, operation), - send_body_as_binary?: Shapes.body_as_binary?(shapes, input_shape), + send_body_as_binary?: send_body_as_binary?, receive_body_as_binary?: Shapes.body_as_binary?(shapes, output_shape), host_prefix: operation_spec["traits"]["smithy.api#endpoint"]["hostPrefix"], language: language, @@ -344,6 +371,13 @@ defmodule AWS.CodeGen.RestService do url_params end + defp collect_body_parameters(language, api_spec, operation) do + url_params = + collect_parameters(language, api_spec, operation, "input", "smithy.api#httpPayload") + + url_params + end + defp collect_query_parameters(language, api_spec, operation) do query_params = collect_parameters(language, api_spec, operation, "input", "smithy.api#httpQueryParams") @@ -352,6 +386,11 @@ defmodule AWS.CodeGen.RestService do query_params ++ params end + @spec split_parameters(any()) :: {list(any), list(any)} + def split_parameters(params) do + Enum.split_with(params, & &1.required) + end + defp collect_request_header_parameters(language, api_spec, operation) do collect_parameters(language, api_spec, operation, "input", "smithy.api#httpHeader") end @@ -360,7 +399,7 @@ defmodule AWS.CodeGen.RestService do collect_parameters(language, api_spec, operation, "output", "smithy.api#httpHeader") end - defp collect_parameters(language, api_spec, operation, data_type, param_type) do + def collect_parameters(language, api_spec, operation, data_type, param_type) do shape_name = api_spec["shapes"][operation][data_type]["target"] if shape_name do diff --git a/priv/post.ex.eex b/priv/post.ex.eex index 987526c..e373ace 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -69,7 +69,7 @@ defmodule <%= context.module_name %> do <%= for action <- context.actions do %> <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> @spec <%= action.function_name %>(AWS.Client.t(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> - def <%= action.function_name %>(%Client{} = client, input, options \\ []) do + def <%= action.function_name %>(%Client{} = client, input<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do meta = <%= if action.host_prefix do %> metadata() |> Map.put_new(:host_prefix, <%= inspect(action.host_prefix) %>) diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 27136b0..fa7b218 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -70,7 +70,7 @@ defmodule <%= context.module_name %> do <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %><%= if action.method == "GET" do %> @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> - def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) do + def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> @@ -126,25 +126,57 @@ defmodule <%= context.module_name %> do <%= AWS.CodeGen.ElixirHelpers.drop_optionals(action) %> Request.request_rest(client, meta, :get, url_path, query_params, headers, nil, options, <%= inspect(action.success_status_code) %>)<% else %> -@spec <%= action.function_name %>(AWS.Client.t()<%= AWS.CodeGen.Types.required_function_parameter_types(action)%>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> String.t(), <% end %><%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> -def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end %>input, options \\ []) do - url_path = "<%= if context.module_name == ~s(AWS.ApiGatewayManagementApi) do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>"<%= if length(action.request_header_parameters) > 0 do %> - - optional_params = [<%= Enum.map(action.query_parameters ++ action.request_header_parameters ++ action.request_headers_parameters, &(&1.code_name <> ": nil")) |> Enum.join(", ") %>] - options = Keyword.validate!(options, [enable_retries?: false, retry_num: 0, retry_opts: []] ++ optional_params) - - {headers, input} = - [<%= for parameter <- action.request_header_parameters do %> - {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> - ] - |> Request.build_params(input)<% else %> - headers = []<% end %><%= if length(action.query_parameters) > 0 do %> - {query_params, input} = - [<%= for parameter <- action.query_parameters do %> - {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> - ] - |> Request.build_params(input)<% else %> - query_params = []<% end %><%= if length(action.response_header_parameters) > 0 do %> +def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.required_function_parameters(action) +%>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end +%><%= if action.has_body?, do: "input,", else: "" %> options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do + url_path = "<%= if context.module_name == ~s(AWS.ApiGatewayManagementApi) do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" +<%= if length(action.request_header_parameters) > 0 do %> + + <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> + + # This is the issue + # {headers, input} = + # [<%= for parameter <- action.request_header_parameters do %> + # {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> + # ] + # |> Request.build_params(input)<% else %> + # headers = []<% end %><%= if length(action.query_parameters) > 0 do %> + # {query_params, input} = + # [<%= for parameter <- action.query_parameters do %> + # {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> + # ] + # |> Request.build_params(input)<% else %> + # query_params = []<% end %> + # FROM HERE + + # Required headers + headers = [<%= action.required_request_header_parameters |> Enum.map(fn parameter -> ~s|{"#{parameter.location_name}", #{parameter.code_name}}| end) |> Enum.join(",") %>] + + # Optional headers<%= for parameter <- Enum.reverse(action.optional_request_header_parameters) do %> + headers = if opt_val = Keyword.get(options, :<%= parameter.code_name %>) do + [{"<%= parameter.location_name %>", opt_val} | headers] + else + headers + end<% end %> + + # Required query params + query_params = [<%= action.required_query_parameters |> Enum.map(fn parameter -> ~s|{"#{parameter.location_name}", #{parameter.code_name}}| end) |> Enum.join(",") %>] + + # Optional query params<%= for parameter <- Enum.reverse(action.optional_query_parameters) do %> + query_params = if opt_val = Keyword.get(options, :<%= parameter.code_name %>) do + [{"<%= parameter.location_name %>", opt_val} | query_params] + else + query_params + end<% end %><%= if length(action.response_header_parameters) > 0 do %> + options = Keyword.put( + options, + :response_header_parameters, + <%= inspect((for param <- action.response_header_parameters, do: {param.location_name, param.name}), pretty: true) %> + )<% end %> + +# TO HERE + + <%= if length(action.response_header_parameters) > 0 do %> options = Keyword.put( options, :response_header_parameters, @@ -184,6 +216,8 @@ def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.re <%= AWS.CodeGen.ElixirHelpers.drop_optionals(action) %> - Request.request_rest(client, meta, <%= AWS.CodeGen.RestService.Action.method(action) %>, url_path, query_params, headers, input, options, <%= inspect(action.success_status_code) %>)<% end %> + body = <%= if action.has_body? do %>input<% else %>nil<% end %> + + Request.request_rest(client, meta, <%= AWS.CodeGen.RestService.Action.method(action) %>, url_path, query_params, headers, body, options, <%= inspect(action.success_status_code) %>)<% end %> end<% end %> end From 45409d6cf77a51c9b9f262a11ffde827c82607a6 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 19 Jul 2024 14:05:10 +0200 Subject: [PATCH 13/22] Remove junk. --- priv/rest.ex.eex | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index fa7b218..1c761bc 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -130,25 +130,9 @@ def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.re %>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end %><%= if action.has_body?, do: "input,", else: "" %> options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do url_path = "<%= if context.module_name == ~s(AWS.ApiGatewayManagementApi) do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" -<%= if length(action.request_header_parameters) > 0 do %> <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> - # This is the issue - # {headers, input} = - # [<%= for parameter <- action.request_header_parameters do %> - # {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> - # ] - # |> Request.build_params(input)<% else %> - # headers = []<% end %><%= if length(action.query_parameters) > 0 do %> - # {query_params, input} = - # [<%= for parameter <- action.query_parameters do %> - # {"<%= parameter.name %>", "<%= parameter.location_name %>"},<% end %> - # ] - # |> Request.build_params(input)<% else %> - # query_params = []<% end %> - # FROM HERE - # Required headers headers = [<%= action.required_request_header_parameters |> Enum.map(fn parameter -> ~s|{"#{parameter.location_name}", #{parameter.code_name}}| end) |> Enum.join(",") %>] @@ -174,8 +158,6 @@ def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.re <%= inspect((for param <- action.response_header_parameters, do: {param.location_name, param.name}), pretty: true) %> )<% end %> -# TO HERE - <%= if length(action.response_header_parameters) > 0 do %> options = Keyword.put( options, From 549081737be6b1c586f99b17fe674d6e3414ca35 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 19 Jul 2024 14:14:01 +0200 Subject: [PATCH 14/22] Document and and add guards for optional bodies. --- lib/aws_codegen/elixir_helpers.ex | 19 +++++++++++++------ lib/aws_codegen/rest_service.ex | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 4503931..8e6c414 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -52,9 +52,9 @@ defmodule AWS.CodeGen.ElixirHelpers do %><%= for parameter <- action.required_request_header_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= if action.send_body_as_binary? do %> - * `:input` (`t:binary`) + * `:input` (`t:binary<%= if action.body_required? do %> | nil<% end %>`) <% else %><%= if action.has_body? do %> - * `:input` (`t:map`):<%= for parameter <- action.required_body_parameters do %> + * `:input` (`t:map<%= if action.body_required? do %> | nil<% end %>`):<%= for parameter <- action.required_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.optional_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end @@ -111,12 +111,19 @@ defmodule AWS.CodeGen.ElixirHelpers do body_guard = cond do - action.send_body_as_binary? -> - # TODO: Distinguish between optional and required http bodies. - "is_binary(input) or is_nil(input)" + action.send_body_as_binary? and action.body_required? -> + "is_binary(input)" + + action.send_body_as_binary? and not action.body_required? -> + "is_binary(input)" action.has_body? -> - # TODO: Distinguish between optional and required http bodies. + "is_map(input) or is_nil(input)" + + action.has_body? and action.body_required? -> + "is_map(input)" + + action.has_body? and not action.body_required? -> "is_map(input) or is_nil(input)" true -> diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 086dd2d..82baa2d 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -17,7 +17,7 @@ defmodule AWS.CodeGen.RestService do name: nil, url_parameters: [], has_body?: false, - # body_required?: false, + body_required?: false, body_parameters: [], required_body_parameters: [], optional_body_parameters: [], @@ -335,7 +335,7 @@ defmodule AWS.CodeGen.RestService do required_body_parameters: required_body_params, optional_body_parameters: opt_body_params, has_body?: has_body?, - # body_required?: not send_body_as_binary? and Enum.empty?(required_body_params), + body_required?: not send_body_as_binary? and Enum.empty?(required_body_params), required_query_parameters: required_query_params, optional_query_parameters: opt_query_params, request_header_parameters: request_header_parameters, From e732f63cce2c408e5bc5fd0b2156ca61131ae400 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 19 Jul 2024 18:24:37 +0200 Subject: [PATCH 15/22] Improve types, guards, specs for both params and body. --- lib/aws_codegen/elixir_helpers.ex | 114 ++++++++++++++++++++++++++---- lib/aws_codegen/post_service.ex | 1 - lib/aws_codegen/rest_service.ex | 49 ++++++++++--- lib/aws_codegen/types.ex | 10 ++- priv/post.ex.eex | 2 +- priv/rest.ex.eex | 20 +++--- 6 files changed, 164 insertions(+), 32 deletions(-) diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 8e6c414..7c25740 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -52,9 +52,9 @@ defmodule AWS.CodeGen.ElixirHelpers do %><%= for parameter <- action.required_request_header_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= if action.send_body_as_binary? do %> - * `:input` (`t:binary<%= if action.body_required? do %> | nil<% end %>`) + * `:input` (`t:binary<%= if not action.body_required? do %> | nil<% end %>`) <% else %><%= if action.has_body? do %> - * `:input` (`t:map<%= if action.body_required? do %> | nil<% end %>`):<%= for parameter <- action.required_body_parameters do %> + * `:input` (`t:map<%= if not action.body_required? do %> | nil<% end %>`):<%= for parameter <- action.required_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.optional_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end @@ -80,17 +80,24 @@ defmodule AWS.CodeGen.ElixirHelpers do [API Reference](<%= action.docs_url %>) ## Parameters: - * `:input` (`t:<%= input_type %>`): + * `:input` (`t:<%= input_type %>`)<%= if not is_nil(type_fields) do %> %{ <%= AWS.CodeGen.ElixirHelpers.render_type_fields(input_type, type_fields, 9) %> - } + }<% end %> """<% end %>/) def render_docstring(%PostService.Action{} = action, context, types) do input_type = AWS.CodeGen.Types.function_argument_type(:elixir, action) # TODO: This is dirty. - |> String.split("(") - |> hd() + |> then(fn x -> + if String.contains?(x, "(") do + x + |> String.split("(") + |> hd() + else + x + end + end) type_fields = types[input_type] @@ -115,10 +122,7 @@ defmodule AWS.CodeGen.ElixirHelpers do "is_binary(input)" action.send_body_as_binary? and not action.body_required? -> - "is_binary(input)" - - action.has_body? -> - "is_map(input) or is_nil(input)" + "is_binary(input) or is_nil(input)" action.has_body? and action.body_required? -> "is_map(input)" @@ -134,17 +138,29 @@ defmodule AWS.CodeGen.ElixirHelpers do required_params |> Enum.map(fn param -> case param.type do + "string" -> + "is_binary(#{param.code_name})" + "integer" -> "is_integer(#{param.code_name})" "long" -> "is_integer(#{param.code_name})" - "string" -> - "is_binary(#{param.code_name})" + "boolean" -> + "is_boolean(#{param.code_name})" "list[" <> _ -> "is_binary(#{param.code_name})" + + "enum[" <> _ -> + "is_binary(#{param.code_name})" + + "timestamp" <> _ -> + "is_binary(#{param.code_name})" + + nil -> + raise "UNKNOWN TYPE" end end) |> Enum.join(" and ") @@ -163,4 +179,78 @@ defmodule AWS.CodeGen.ElixirHelpers do "when (" <> body_guard <> ") and " <> req_guards end end + + def maybe_render_stage(context) do + if context.module_name == "AWS.ApiGatewayManagementApi" do + ", stage" + else + "" + end + end + + def maybe_render_stage_spec(context) do + if context.module_name == "AWS.ApiGatewayManagementApi" do + ", any()" + else + "" + end + end + + @render_spec_get EEx.compile_string(~s/ + @spec <%= action.function_name %>(AWS.Client.t()<%= maybe_stage + %><%= required_param_types %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> + /) + def render_spec(:get, context, action) do + maybe_stage = maybe_render_stage_spec(context) + required_param_types = AWS.CodeGen.Types.required_function_parameter_types(action) + + {res, _} = + Code.eval_quoted(@render_spec_get, + action: action, + context: context, + maybe_stage: maybe_stage, + required_param_types: required_param_types + ) + + res + end + + @render_spec_other EEx.compile_string(~s/ + @spec <%= action.function_name %>(AWS.Client.t()<%= maybe_stage + %><%= required_param_types %><%= body_type %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> + /) + def render_spec(_, context, action) do + maybe_stage = maybe_render_stage_spec(context) + + required_param_types = AWS.CodeGen.Types.required_function_parameter_types(action) + + body_type = + cond do + action.send_body_as_binary? and action.body_required? -> + ", input :: binary()" + + action.send_body_as_binary? and not action.body_required? -> + ", input :: binary() | nil" + + action.has_body? and action.body_required? -> + ", input :: map()" + + action.has_body? and not action.body_required? -> + ", input :: map() | nil" + + true -> + "" + end + + {res, _} = + Code.eval_quoted(@render_spec_other, + action: action, + context: context, + body_type: body_type, + maybe_stage: maybe_stage, + required_param_types: required_param_types + ) + + res + end end diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index 6694f42..a4e93a3 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -193,7 +193,6 @@ defmodule AWS.CodeGen.PostService do params = collect_params(language, api_spec, operation) - |> dbg() %Action{ arity: 3, diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 82baa2d..9b654e5 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -276,7 +276,16 @@ defmodule AWS.CodeGen.RestService do operation_spec = shapes[operation] request_uri = operation_spec["traits"]["smithy.api#http"]["uri"] url_parameters = collect_url_parameters(language, api_spec, operation) - body_parameters = collect_body_parameters(language, api_spec, operation) + + body_parameters = + collect_body_parameters(language, api_spec, operation) + + # if operation in [ + # "com.amazonaws.apigatewayv2#ReimportApi" + # ] do + # dbg({url_parameters, body_parameters}) + # end + query_parameters = collect_query_parameters(language, api_spec, operation) function_name = AWS.CodeGen.Name.to_snake_case(operation) request_header_parameters = collect_request_header_parameters(language, api_spec, operation) @@ -317,7 +326,7 @@ defmodule AWS.CodeGen.RestService do operation_spec["traits"]["smithy.api#documentation"] ) - has_body? = not Enum.empty?(body_parameters) + has_body? = method != "GET" and not Enum.empty?(body_parameters) send_body_as_binary? = Shapes.body_as_binary?(shapes, input_shape) %Action{ @@ -335,7 +344,7 @@ defmodule AWS.CodeGen.RestService do required_body_parameters: required_body_params, optional_body_parameters: opt_body_params, has_body?: has_body?, - body_required?: not send_body_as_binary? and Enum.empty?(required_body_params), + body_required?: has_body? and not Enum.empty?(required_body_params), required_query_parameters: required_query_params, optional_query_parameters: opt_query_params, request_header_parameters: request_header_parameters, @@ -372,10 +381,12 @@ defmodule AWS.CodeGen.RestService do end defp collect_body_parameters(language, api_spec, operation) do - url_params = - collect_parameters(language, api_spec, operation, "input", "smithy.api#httpPayload") - - url_params + [ + collect_parameters(language, api_spec, operation, "input", "smithy.api#httpPayload"), + collect_parameters(language, api_spec, operation, "input", "smithy.api#jsonName") + # collect_parameters(language, api_spec, operation, "members", "smithy.api#jsonName") + ] + |> Enum.concat() end defp collect_query_parameters(language, api_spec, operation) do @@ -418,7 +429,8 @@ defmodule AWS.CodeGen.RestService do |> Enum.map(fn {name, x} -> required = Enum.member?(required_members, name) - tynfo = get_type_info(x, api_spec) + tynfo = + get_type_info(x, api_spec) docs = get_in(x, ["traits", "smithy.api#documentation"]) @@ -429,7 +441,11 @@ defmodule AWS.CodeGen.RestService do extract_param_docs_snippet(docs) end - build_parameter(language, {name, x["traits"][param_type]}, required, tynfo, docs) + if is_nil(tynfo) do + build_parameter(language, {name, x["traits"][param_type]}, required, "string", docs) + else + build_parameter(language, {name, x["traits"][param_type]}, required, tynfo, docs) + end end) end else @@ -478,6 +494,11 @@ defmodule AWS.CodeGen.RestService do t = x["target"] type = api_spec["shapes"][t] + # if is_nil(type) do + # # THIS IS SOMETIMES INCORRECT! t is not guaranteed to exist in api_spec["shapes"]... + # dbg({x, t, type}) + # end + build_type_details(type, api_spec) end @@ -508,6 +529,11 @@ defmodule AWS.CodeGen.RestService do "list[#{deets}]" end + # TODO: Should raise here on unknown types, but handling it elsewhere for now. + # def build_type_details(nil, api_spec) do + # "string" + # end + def build_type_details(type, api_spec) do type["type"] end @@ -518,6 +544,11 @@ defmodule AWS.CodeGen.RestService do end end + defp build_parameter(_, a, required, nil, docs) do + dbg() + raise "build_parameter type is nil" + end + defp build_parameter(language, {name, %{}}, required, type, docs) do %Parameter{ code_name: diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex index 0801a6e..b2d3e4e 100644 --- a/lib/aws_codegen/types.ex +++ b/lib/aws_codegen/types.ex @@ -17,6 +17,7 @@ defmodule AWS.CodeGen.Types do defp process_structure_shape(context, shape, acc) do type = normalize_type_name(shape.name) + types = process_shape_members(context, shape) update_acc_with_types(acc, type, types, context) end @@ -303,7 +304,12 @@ defmodule AWS.CodeGen.Types do def function_parameter_types(_method, action, _required_only) do language = action.language - join_parameter_types(action.url_parameters, 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 defp join_parameter_types(parameters, :elixir) do @@ -311,8 +317,10 @@ defmodule AWS.CodeGen.Types do parameters, fn parameter -> if not parameter.required do + # TODO: map the types to elixir types here: ", #{parameter.type} | nil" ", String.t() | nil" else + # TODO: map the types to elixir types here: ", #{parameter.type}" ", String.t()" end end diff --git a/priv/post.ex.eex b/priv/post.ex.eex index e373ace..88dba3e 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -68,7 +68,7 @@ defmodule <%= context.module_name %> do end <%= for action <- context.actions do %> <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> - @spec <%= action.function_name %>(AWS.Client.t(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> + <%= AWS.CodeGen.ElixirHelpers.render_spec(:other, context, action) %> def <%= action.function_name %>(%Client{} = client, input<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do meta = <%= if action.host_prefix do %> diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 1c761bc..2d25dfd 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -67,10 +67,10 @@ defmodule <%= context.module_name %> do } end<%= for action <- context.actions do %> - <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) - %><%= if action.method == "GET" do %> - @spec <%= action.function_name %>(AWS.Client.t()<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, String.t()<% end %><%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> - def <%= action.function_name %>(%Client{} = client<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>, stage<% end %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do +<%= if action.method == "GET" do %> + <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> + <%= AWS.CodeGen.ElixirHelpers.render_spec(:get, context, action) %> + def <%= action.function_name %>(%Client{} = client <%= AWS.CodeGen.ElixirHelpers.maybe_render_stage(context) %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> @@ -125,10 +125,14 @@ defmodule <%= context.module_name %> do <%= AWS.CodeGen.ElixirHelpers.drop_optionals(action) %> - 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.required_function_parameters(action) -%>, <%= if context.module_name == "AWS.ApiGatewayManagementApi" do %> stage, <% end -%><%= if action.has_body?, do: "input,", else: "" %> options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do + Request.request_rest(client, meta, :get, url_path, query_params, headers, nil, options, <%= inspect(action.success_status_code) %>)<% +else %> + + <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> + <%= AWS.CodeGen.ElixirHelpers.render_spec(:other, context, action) %> +def <%= action.function_name %>(%Client{} = client <%= AWS.CodeGen.ElixirHelpers.maybe_render_stage(context) +%><%= AWS.CodeGen.RestService.required_function_parameters(action) +%>, <%= if action.has_body?, do: "input,", else: "" %> options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do url_path = "<%= if context.module_name == ~s(AWS.ApiGatewayManagementApi) do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> From 9e4bb8a1f97c5af915c16b4b287c8b7f0b16efd2 Mon Sep 17 00:00:00 2001 From: Aske Doerge Date: Fri, 19 Jul 2024 18:40:53 +0200 Subject: [PATCH 16/22] Refactor to use render_def. --- lib/aws_codegen/elixir_helpers.ex | 21 +++++++++++++++++++++ lib/aws_codegen/post_service.ex | 4 ++-- lib/aws_codegen/rest_service.ex | 5 +++-- priv/post.ex.eex | 2 +- priv/rest.ex.eex | 6 ++---- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 7c25740..0cfc60e 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -253,4 +253,25 @@ defmodule AWS.CodeGen.ElixirHelpers do res end + + @render_def EEx.compile_string(~s/ +def <%= action.function_name %>(%Client{} = client <%= maybe_stage +%><%= required_params +%>, <%= if action.has_body?, do: "input,", else: "" %> options \\\\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> + /) + def render_def(context, action) do + maybe_stage = maybe_render_stage(context) + + required_params = AWS.CodeGen.RestService.required_function_parameters(action) + + {res, _} = + Code.eval_quoted(@render_def, + action: action, + context: context, + maybe_stage: maybe_stage, + required_params: required_params + ) + + res + end end diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index a4e93a3..24d80b4 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -11,8 +11,8 @@ defmodule AWS.CodeGen.PostService do input: nil, output: nil, url_parameters: [], - has_body?: false, - # body_required?: false, + has_body?: true, + body_required?: true, body_parameters: [], required_body_parameters: [], optional_body_parameters: [], diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 9b654e5..0d1321a 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -328,6 +328,7 @@ defmodule AWS.CodeGen.RestService do has_body? = method != "GET" and not Enum.empty?(body_parameters) send_body_as_binary? = Shapes.body_as_binary?(shapes, input_shape) + body_required? = has_body? and not Enum.empty?(required_body_params) %Action{ arity: length(url_parameters) + len_for_method, @@ -344,7 +345,7 @@ defmodule AWS.CodeGen.RestService do required_body_parameters: required_body_params, optional_body_parameters: opt_body_params, has_body?: has_body?, - body_required?: has_body? and not Enum.empty?(required_body_params), + body_required?: body_required?, required_query_parameters: required_query_params, optional_query_parameters: opt_query_params, request_header_parameters: request_header_parameters, @@ -534,7 +535,7 @@ defmodule AWS.CodeGen.RestService do # "string" # end - def build_type_details(type, api_spec) do + def build_type_details(type, _api_spec) do type["type"] end diff --git a/priv/post.ex.eex b/priv/post.ex.eex index 88dba3e..a90d22d 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -69,7 +69,7 @@ defmodule <%= context.module_name %> do <%= for action <- context.actions do %> <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> <%= AWS.CodeGen.ElixirHelpers.render_spec(:other, context, action) %> - def <%= action.function_name %>(%Client{} = client, input<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do + <%= AWS.CodeGen.ElixirHelpers.render_def(context, action) %> do meta = <%= if action.host_prefix do %> metadata() |> Map.put_new(:host_prefix, <%= inspect(action.host_prefix) %>) diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 2d25dfd..2ca82f1 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -70,7 +70,7 @@ defmodule <%= context.module_name %> do <%= if action.method == "GET" do %> <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> <%= AWS.CodeGen.ElixirHelpers.render_spec(:get, context, action) %> - def <%= action.function_name %>(%Client{} = client <%= AWS.CodeGen.ElixirHelpers.maybe_render_stage(context) %><%= AWS.CodeGen.RestService.required_function_parameters(action) %>, options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do + <%= AWS.CodeGen.ElixirHelpers.render_def(context, action) %> do url_path = "<%= if context.module_name == "AWS.ApiGatewayManagementApi" do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> @@ -130,9 +130,7 @@ else %> <%= AWS.CodeGen.ElixirHelpers.render_docstring(action, context, types) %> <%= AWS.CodeGen.ElixirHelpers.render_spec(:other, context, action) %> -def <%= action.function_name %>(%Client{} = client <%= AWS.CodeGen.ElixirHelpers.maybe_render_stage(context) -%><%= AWS.CodeGen.RestService.required_function_parameters(action) -%>, <%= if action.has_body?, do: "input,", else: "" %> options \\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> do + <%= AWS.CodeGen.ElixirHelpers.render_def(context, action) %> do url_path = "<%= if context.module_name == ~s(AWS.ApiGatewayManagementApi) do %>/#{stage}<% end %><%= AWS.CodeGen.RestService.Action.url_path(action) %>" <%= AWS.CodeGen.ElixirHelpers.define_and_validate_optionals(action) %> From 5836feac58c4ad717e6ebb4eae6cacb78e159bf6 Mon Sep 17 00:00:00 2001 From: Aske Date: Sat, 3 Aug 2024 22:59:25 +0200 Subject: [PATCH 17/22] Fix optional body. --- lib/aws_codegen/rest_service.ex | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 0d1321a..d086994 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -280,11 +280,7 @@ defmodule AWS.CodeGen.RestService do body_parameters = collect_body_parameters(language, api_spec, operation) - # if operation in [ - # "com.amazonaws.apigatewayv2#ReimportApi" - # ] do - # dbg({url_parameters, body_parameters}) - # end + body_required? = get_body_required?(api_spec, operation_spec) query_parameters = collect_query_parameters(language, api_spec, operation) function_name = AWS.CodeGen.Name.to_snake_case(operation) @@ -328,7 +324,6 @@ defmodule AWS.CodeGen.RestService do has_body? = method != "GET" and not Enum.empty?(body_parameters) send_body_as_binary? = Shapes.body_as_binary?(shapes, input_shape) - body_required? = has_body? and not Enum.empty?(required_body_params) %Action{ arity: length(url_parameters) + len_for_method, @@ -454,6 +449,37 @@ defmodule AWS.CodeGen.RestService do end end + def get_body_required?(api_spec, operation_spec) do + body_req_name = get_in(operation_spec, ["input", "target"]) + + if is_nil(body_req_name) do + false + else + members = api_spec["shapes"][body_req_name]["members"] + + if is_nil(members) do + false + else + payloads = + members + |> Enum.filter(filter_fn("smithy.api#httpPayload")) + + jsons = + members + |> Enum.filter(filter_fn("smithy.api#jsonName")) + + Enum.concat(payloads, jsons) + |> Enum.map(fn {_name, x} -> + required? = get_in(x, ["traits", "smithy.api#required"]) + default = get_in(x, ["traits", "smithy.api#default"]) + + required? || default == "" + end) + |> Enum.any?() + end + end + end + def extract_param_docs_snippet(docs) do case Floki.parse_fragment(docs) do {:ok, [{"p", _attrs, inner_content} | _rest]} -> From eeb672dac2b95d2d0d3ee0072a28273a59ce81a7 Mon Sep 17 00:00:00 2001 From: Aske Date: Sun, 4 Aug 2024 11:29:05 +0200 Subject: [PATCH 18/22] Add "required" to docstring param docs, and cleanup whitespaces. --- lib/aws_codegen.ex | 2 +- lib/aws_codegen/elixir_helpers.ex | 51 +++++++++++++++---------------- lib/aws_codegen/rest_service.ex | 2 ++ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index 70d217a..23e7daf 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -134,7 +134,7 @@ defmodule AWS.CodeGen do end @param_quoted_elixir EEx.compile_string( - ~s|* `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`) <%= parameter.docs %>| + ~s|* `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`<%= if parameter.required do %> required<% end %>) <%= parameter.docs %>| ) def render_parameter(:elixir, %Parameter{} = parameter) do Code.eval_quoted(@param_quoted_elixir, parameter: parameter) diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 0cfc60e..5ac67d1 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -38,10 +38,9 @@ defmodule AWS.CodeGen.ElixirHelpers do end) end - @docstring_rest_quoted EEx.compile_string(~s{ - <%= if String.trim(action.docstring) != "" do %> - @doc """ - <%= action.docstring %> + @docstring_rest_quoted EEx.compile_string(~s{@doc """ + <%= if String.trim(action.docstring) != "" do + %><%= action.docstring %><% end %> [API Reference](<%= action.docs_url %>) @@ -52,39 +51,37 @@ defmodule AWS.CodeGen.ElixirHelpers do %><%= for parameter <- action.required_request_header_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= if action.send_body_as_binary? do %> - * `:input` (`t:binary<%= if not action.body_required? do %> | nil<% end %>`) + * `:input` (`t:binary<%= if not action.body_required? do %> | nil<% end %>`<%= if action.body_required? do %> required<% end %>) <% else %><%= if action.has_body? do %> - * `:input` (`t:map<%= if not action.body_required? do %> | nil<% end %>`):<%= for parameter <- action.required_body_parameters do %> + * `:input` (`t:map<%= if not action.body_required? do %> | nil<% end %>`<% if action.body_required? do %> required<% end %>):<%= for parameter <- action.required_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.optional_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><% end - %><% end %> - - ## Optional parameters:<%= for parameter <- action.optional_query_parameters do %> + %><% end + %> + ## Keyword parameters:<%= for parameter <- action.optional_query_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %><%= for parameter <- action.optional_request_header_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> - """<% end %> - }) + """}) def render_docstring(%RestService.Action{} = action, _context, _types) do {res, _} = Code.eval_quoted(@docstring_rest_quoted, action: action) res end - @docstring_post_quoted EEx.compile_string(~s/ - <%= if String.trim(action.docstring) != "" do %> - @doc """ - <%= action.docstring %> + @docstring_post_quoted EEx.compile_string(~s/@doc """ + <%= if String.trim(action.docstring) != "" do %> + <%= action.docstring %><% end %> [API Reference](<%= action.docs_url %>) ## Parameters: - * `:input` (`t:<%= input_type %>`)<%= if not is_nil(type_fields) do %> + * `:input` (`t:<%= input_type %>`<% if action.body_required? do %> required<% end %>)<%= if not is_nil(type_fields) do %> %{ <%= AWS.CodeGen.ElixirHelpers.render_type_fields(input_type, type_fields, 9) %> }<% end %> - """<% end %>/) + """/) def render_docstring(%PostService.Action{} = action, context, types) do input_type = AWS.CodeGen.Types.function_argument_type(:elixir, action) @@ -196,10 +193,10 @@ defmodule AWS.CodeGen.ElixirHelpers do end end - @render_spec_get EEx.compile_string(~s/ - @spec <%= action.function_name %>(AWS.Client.t()<%= maybe_stage - %><%= required_param_types %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> - /) + @render_spec_get EEx.compile_string( + ~s/@spec <%= action.function_name %>(AWS.Client.t()<%= maybe_stage + %><%= required_param_types %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action) + %>/) def render_spec(:get, context, action) do maybe_stage = maybe_render_stage_spec(context) required_param_types = AWS.CodeGen.Types.required_function_parameter_types(action) @@ -215,10 +212,10 @@ defmodule AWS.CodeGen.ElixirHelpers do res end - @render_spec_other EEx.compile_string(~s/ - @spec <%= action.function_name %>(AWS.Client.t()<%= maybe_stage - %><%= required_param_types %><%= body_type %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action)%> - /) + @render_spec_other EEx.compile_string( + ~s/@spec <%= action.function_name %>(AWS.Client.t()<%= maybe_stage + %><%= required_param_types %><%= body_type %>, Keyword.t()) :: <%= AWS.CodeGen.Types.return_type(context.language, action) + %>/) def render_spec(_, context, action) do maybe_stage = maybe_render_stage_spec(context) @@ -254,8 +251,8 @@ defmodule AWS.CodeGen.ElixirHelpers do res end - @render_def EEx.compile_string(~s/ -def <%= action.function_name %>(%Client{} = client <%= maybe_stage + @render_def EEx.compile_string( +~s/def <%= action.function_name %>(%Client{} = client <%= maybe_stage %><%= required_params %>, <%= if action.has_body?, do: "input,", else: "" %> options \\\\ []) <%= AWS.CodeGen.ElixirHelpers.render_guards(action) %> /) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index d086994..d78854a 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -464,6 +464,8 @@ defmodule AWS.CodeGen.RestService do members |> Enum.filter(filter_fn("smithy.api#httpPayload")) + # This is necessary to detect e.g. APIGatewayV2 create_api/3 input as + # required. jsons = members |> Enum.filter(filter_fn("smithy.api#jsonName")) From 13925f6e01b606fa168d144db400fe3146522589 Mon Sep 17 00:00:00 2001 From: Aske Date: Sun, 4 Aug 2024 11:45:11 +0200 Subject: [PATCH 19/22] Improve whitespace rendering. --- lib/aws_codegen/elixir_helpers.ex | 59 ++++++++++++++++++------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 5ac67d1..5e12211 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -39,37 +39,48 @@ defmodule AWS.CodeGen.ElixirHelpers do end @docstring_rest_quoted EEx.compile_string(~s{@doc """ - <%= if String.trim(action.docstring) != "" do - %><%= action.docstring %><% end %> - - [API Reference](<%= action.docs_url %>) - - ## Parameters:<%= for parameter <- action.url_parameters do %> + <%= if String.trim(action.docstring) != "" do + %><%= action.docstring %><% end %> + + [API Reference](<%= action.docs_url %>) + + ## Parameters:<%= for parameter <- action.url_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.required_query_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.required_request_header_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= if action.send_body_as_binary? do %> + * `:input` (`t:binary<%= if not action.body_required? do %> | nil<% end %>`<%= if action.body_required? do %> required<% end %>) + <% else %><%= if action.has_body? do %> + * `:input` (`t:map<%= if not action.body_required? do %> | nil<% end %>`<% if action.body_required? do %> required<% end %>):<%= for parameter <- action.required_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.required_query_parameters do %> + %><%= for parameter <- action.optional_body_parameters do %> <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.required_request_header_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= if action.send_body_as_binary? do %> - * `:input` (`t:binary<%= if not action.body_required? do %> | nil<% end %>`<%= if action.body_required? do %> required<% end %>) - <% else %><%= if action.has_body? do %> - * `:input` (`t:map<%= if not action.body_required? do %> | nil<% end %>`<% if action.body_required? do %> required<% end %>):<%= for parameter <- action.required_body_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.optional_body_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><% end - %><% end - %> - ## Keyword parameters:<%= for parameter <- action.optional_query_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end - %><%= for parameter <- action.optional_request_header_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %> - """}) + %><% end + %><% end + %><%= AWS.CodeGen.ElixirHelpers.render_keyword_parameters(action) %> + """}) def render_docstring(%RestService.Action{} = action, _context, _types) do {res, _} = Code.eval_quoted(@docstring_rest_quoted, action: action) res end + @keyword_params EEx.compile_string(~s{ + + ## Keyword parameters:<%= for parameter <- action.optional_query_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + %><%= for parameter <- action.optional_request_header_parameters do %> + <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end %>}) + def render_keyword_parameters(action) do + if Enum.empty?(action.optional_query_parameters) and Enum.empty?(action.optional_request_header_parameters) do + "" + else + {res, _} = Code.eval_quoted(@keyword_params, action: action) + res + end + end + @docstring_post_quoted EEx.compile_string(~s/@doc """ <%= if String.trim(action.docstring) != "" do %> <%= action.docstring %><% end %> From 6c77d69bc7667af2e1659082ec9bb0505f1956a6 Mon Sep 17 00:00:00 2001 From: Aske Date: Sun, 4 Aug 2024 13:22:11 +0200 Subject: [PATCH 20/22] Fix body-parameters location name. --- lib/aws_codegen.ex | 13 +++++++++++-- lib/aws_codegen/elixir_helpers.ex | 4 ++-- lib/aws_codegen/rest_service.ex | 9 ++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index 23e7daf..541e554 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -134,14 +134,23 @@ defmodule AWS.CodeGen do end @param_quoted_elixir EEx.compile_string( - ~s|* `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`<%= if parameter.required do %> required<% end %>) <%= parameter.docs %>| - ) + ~s|* `:<%= parameter.code_name %>` (`t:<%= parameter.type %>`<%= if parameter.required do %> required<% end %>) <%= parameter.docs %>| + ) def render_parameter(:elixir, %Parameter{} = parameter) do Code.eval_quoted(@param_quoted_elixir, parameter: parameter) |> then(&elem(&1, 0)) |> Excribe.format(width: 80, hanging: 4, pretty: true) end + @body_param_quoted EEx.compile_string( + ~s|* `"<%= parameter.location_name %>" => t:<%= parameter.type %>`<%= if parameter.required do %> required<% end %> <%= parameter.docs %>| + ) + def render_body_parameter(%Parameter{} = parameter) do + Code.eval_quoted(@body_param_quoted, parameter: parameter) + |> then(&elem(&1, 0)) + |> Excribe.format(width: 80, hanging: 4, pretty: true) + end + defp format_string!(:elixir, rendered) do [Code.format_string!(rendered), ?\n] end diff --git a/lib/aws_codegen/elixir_helpers.ex b/lib/aws_codegen/elixir_helpers.ex index 5e12211..c62c1b7 100644 --- a/lib/aws_codegen/elixir_helpers.ex +++ b/lib/aws_codegen/elixir_helpers.ex @@ -54,9 +54,9 @@ defmodule AWS.CodeGen.ElixirHelpers do * `:input` (`t:binary<%= if not action.body_required? do %> | nil<% end %>`<%= if action.body_required? do %> required<% end %>) <% else %><%= if action.has_body? do %> * `:input` (`t:map<%= if not action.body_required? do %> | nil<% end %>`<% if action.body_required? do %> required<% end %>):<%= for parameter <- action.required_body_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + <%= AWS.CodeGen.render_body_parameter(parameter) %><% end %><%= for parameter <- action.optional_body_parameters do %> - <%= AWS.CodeGen.render_parameter(:elixir, parameter) %><% end + <%= AWS.CodeGen.render_body_parameter(parameter) %><% end %><% end %><% end %><%= AWS.CodeGen.ElixirHelpers.render_keyword_parameters(action) %> diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index d78854a..47afd55 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -440,7 +440,14 @@ defmodule AWS.CodeGen.RestService do if is_nil(tynfo) do build_parameter(language, {name, x["traits"][param_type]}, required, "string", docs) else - build_parameter(language, {name, x["traits"][param_type]}, required, tynfo, docs) + traits = x["traits"] + xml_name = traits["smithy.api#xmlName"] + json_name = traits["smithy.api#jsonName"] + + # If the parameter is a body parameter use the xml/json name instead of the smithy name. + location_name = xml_name || json_name || x["traits"][param_type] + + build_parameter(language, {name, location_name}, required, tynfo, docs) end end) end From b79af589ff534c2dba3ffcfd9c56218ed9b925d8 Mon Sep 17 00:00:00 2001 From: Aske Date: Sun, 4 Aug 2024 13:22:28 +0200 Subject: [PATCH 21/22] Remove repeated query param usage. --- priv/rest.ex.eex | 7 ------- 1 file changed, 7 deletions(-) diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 2ca82f1..022f248 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -160,13 +160,6 @@ else %> <%= inspect((for param <- action.response_header_parameters, do: {param.location_name, param.name}), pretty: true) %> )<% end %> - <%= if length(action.response_header_parameters) > 0 do %> - options = Keyword.put( - options, - :response_header_parameters, - <%= inspect((for param <- action.response_header_parameters, do: {param.location_name, param.name}), pretty: true) %> - )<% end %> - <%= if action.send_body_as_binary? do %> options = Keyword.put( options, From 7a8317d86cdd4c013d1bce3a547ad0f5f1490c94 Mon Sep 17 00:00:00 2001 From: Aske Date: Sun, 4 Aug 2024 14:46:24 +0200 Subject: [PATCH 22/22] Formatting of body param required. --- 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 541e554..a47fd69 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -143,7 +143,7 @@ defmodule AWS.CodeGen do end @body_param_quoted EEx.compile_string( - ~s|* `"<%= parameter.location_name %>" => t:<%= parameter.type %>`<%= if parameter.required do %> required<% end %> <%= parameter.docs %>| + ~s|* `"<%= parameter.location_name %>" => t:<%= parameter.type %>`<%= if parameter.required do %> (required)<% end %> <%= parameter.docs %>| ) def render_body_parameter(%Parameter{} = parameter) do Code.eval_quoted(@body_param_quoted, parameter: parameter)