From 643d44362b2d50934fe39c74fb66567c2fd1e22d Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Tue, 12 Mar 2024 22:25:24 +0100 Subject: [PATCH 1/2] Generate types and type specs for all generated functions in aws-elixir and aws-erlang - Generate common errors in order to unify types and reduce noise - Ensure that for Elixir, typedoc examples are considered code blocks - Erlang Type docs are not hoverable for now due to issues with 'rebar3 ex_doc' - Will follow up on this task - Increase Task.await/2 timeout --- lib/aws_codegen.ex | 3 +- lib/aws_codegen/post_service.ex | 12 +- lib/aws_codegen/rest_service.ex | 13 +- lib/aws_codegen/shapes.ex | 32 +- lib/aws_codegen/types.ex | 288 +++++++++++++ lib/aws_codegen/util.ex | 23 + priv/post.erl.eex | 37 ++ priv/post.ex.eex | 41 ++ priv/rest.erl.eex | 48 ++- priv/rest.ex.eex | 44 +- test/aws_codegen/rest_service_test.exs | 12 +- test/aws_codegen_test.exs | 553 +++++++++++++++++++------ 12 files changed, 981 insertions(+), 125 deletions(-) create mode 100644 lib/aws_codegen/types.ex diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index a614383..248d6ed 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -33,6 +33,7 @@ defmodule AWS.CodeGen do protocol: nil, signature_version: nil, service_id: nil, + shapes: %{}, signing_name: nil, target_prefix: nil end @@ -96,7 +97,7 @@ defmodule AWS.CodeGen do end ) - Enum.each(tasks, fn task -> Task.await(task, 60_000) end) + Enum.each(tasks, fn task -> Task.await(task, 120_000) end) end defp generate_code(spec, language, endpoints_spec, template_base_path, output_path) do diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index c32e4bd..f735d5e 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -1,11 +1,15 @@ defmodule AWS.CodeGen.PostService do alias AWS.CodeGen.Docstring alias AWS.CodeGen.Service + alias AWS.CodeGen.Shapes defmodule Action do defstruct arity: nil, docstring: nil, function_name: nil, + input: nil, + output: nil, + errors: %{}, host_prefix: nil, name: nil end @@ -57,6 +61,7 @@ defmodule AWS.CodeGen.PostService do service = spec.api["shapes"][spec.shape_name] traits = service["traits"] actions = collect_actions(language, spec.api) + shapes = Shapes.collect_shapes(language, spec.api) endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] endpoint_info = endpoints_spec["services"][endpoint_prefix] is_global = not is_nil(endpoint_info) and not Map.get(endpoint_info, "isRegionalized", true) @@ -89,6 +94,7 @@ defmodule AWS.CodeGen.PostService do language: language, module_name: spec.module_name, protocol: protocol |> to_string() |> String.replace("_", "-"), + shapes: shapes, signing_name: signing_name, signature_version: AWS.CodeGen.Util.get_signature_version(service), service_id: AWS.CodeGen.Util.get_service_id(service), @@ -137,10 +143,14 @@ defmodule AWS.CodeGen.PostService do ), function_name: AWS.CodeGen.Name.to_snake_case(operation), host_prefix: operation_spec["traits"]["smithy.api#endpoint"]["hostPrefix"], - name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, "") + name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, ""), + input: operation_spec["input"], + output: operation_spec["output"], + errors: operation_spec["errors"] } end) |> Enum.sort(fn a, b -> a.function_name < b.function_name end) |> Enum.uniq() end + end diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 1b91d69..03284dd 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -23,7 +23,10 @@ defmodule AWS.CodeGen.RestService do send_body_as_binary?: false, receive_body_as_binary?: false, host_prefix: nil, - language: nil + language: nil, + input: nil, + output: nil, + errors: [] def method(action) do result = action.method |> String.downcase() |> String.to_atom() @@ -145,7 +148,8 @@ defmodule AWS.CodeGen.RestService do signing_name: signing_name, signature_version: AWS.CodeGen.Util.get_signature_version(service), service_id: AWS.CodeGen.Util.get_service_id(service), - target_prefix: nil, ##TODO: metadata["targetPrefix"] + target_prefix: nil, ##TODO: metadata["targetPrefix"], + shapes: Shapes.collect_shapes(language, spec.api) } end @@ -275,7 +279,10 @@ defmodule AWS.CodeGen.RestService do send_body_as_binary?: Shapes.body_as_binary?(shapes, input_shape), receive_body_as_binary?: Shapes.body_as_binary?(shapes, output_shape), host_prefix: operation_spec["traits"]["smithy.api#endpoint"]["hostPrefix"], - language: language + language: language, + input: operation_spec["input"], + output: operation_spec["output"], + errors: operation_spec["errors"] } end) |> Enum.sort(fn a, b -> a.function_name < b.function_name end) diff --git a/lib/aws_codegen/shapes.ex b/lib/aws_codegen/shapes.ex index 946f9cc..669968a 100644 --- a/lib/aws_codegen/shapes.ex +++ b/lib/aws_codegen/shapes.ex @@ -1,5 +1,15 @@ defmodule AWS.CodeGen.Shapes do - @moduledoc false + + defmodule Shape do + defstruct name: nil, + type: nil, + members: [], + member: [], + enum: [], + min: nil, + required: [], + is_input: nil + end def get_input_shape(operation_spec) do get_in(operation_spec, ["input", "target"]) @@ -9,6 +19,22 @@ defmodule AWS.CodeGen.Shapes do get_in(operation_spec, ["output", "target"]) end + def collect_shapes(_language, api_spec) do + api_spec["shapes"] + |> Map.new(fn {name, shape} -> + {name, + %Shape{ + name: name, + type: shape["type"], + member: shape["member"], + members: shape["members"], + min: shape["min"], + enum: shape["enum"], + is_input: is_input?(shape) + }} + end) + end + def body_as_binary?(shapes, shape) do ## TODO: Should we validate or search for trait `smithy.api#httpPayload` rather than ## trust that the member is always named `Body`? @@ -23,4 +49,8 @@ defmodule AWS.CodeGen.Shapes do end end + def is_input?(shape) do + !Map.has_key?(shape, "traits") or Map.has_key?(shape["traits"], "smithy.api#input") + end + end diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex new file mode 100644 index 0000000..d1d43ef --- /dev/null +++ b/lib/aws_codegen/types.ex @@ -0,0 +1,288 @@ +defmodule AWS.CodeGen.Types do + + alias AWS.CodeGen.Shapes.Shape + + def types(context) do + Enum.reduce(context.shapes, %{}, fn {_name, shape}, acc -> + process_shape(context, shape, acc) + end) + end + + defp process_shape(context, shape, acc) do + if shape.type == "structure" and not is_nil(shape.members) do + process_structure_shape(context, shape, acc) + else + acc + end + end + + 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 + + defp normalize_type_name(name) do + name + |> String.replace(~r/com\.amazonaws\.[^#]+#/, "") + |> AWS.CodeGen.Name.to_snake_case() + end + + defp process_shape_members(context, shape) do + Enum.reduce(shape.members, %{}, fn {name, shape_member}, a -> + process_shape_member(context, shape, name, shape_member, a) + end) + end + + defp process_shape_member(context, shape, name, shape_member, a) do + target = shape_member["target"] + if Map.has_key?(shape_member, "traits") and has_http_label_trait(shape_member["traits"]) do + a + else + shape_member_type = shape_to_type(context, target, context.module_name, context.shapes) + Map.put(a, is_required(context.language, shape.is_input, shape_member, name), shape_member_type) + end + end + + defp has_http_label_trait(traits), do: Map.has_key?(traits, "smithy.api#httpLabel") + + defp update_acc_with_types(acc, type, types, context) do + if reserved_type(type) do + module_name = String.downcase(String.replace(context.module_name, "AWS.", "")) + Map.put(acc, "#{module_name}_#{type}", types) + else + Map.put(acc, type, types) + end + end + + defp shape_to_type(context, shape_name, module_name, all_shapes) do + case shape_name do + "smithy.api#String" -> + "[#{shape_to_type(context.language, %Shape{type: "string"}, module_name)}]" + + "smithy.api#Integer" -> + "[#{shape_to_type(context.language, %Shape{type: "integer"}, module_name)}]" + + "smithy.api#Timestamp" -> + "[#{shape_to_type(context.language, %Shape{type: "timestamp"}, module_name)}]" + + "smithy.api#PrimitiveLong" -> + "[#{shape_to_type(context.language, %Shape{type: "long"}, module_name)}]" + + "smithy.api#Long" -> + "[#{shape_to_type(context.language, %Shape{type: "long"}, module_name)}]" + + "smithy.api#Boolean" -> + "[#{shape_to_type(context.language, %Shape{type: "boolean"}, module_name)}]" + + "smithy.api#PrimitiveBoolean" -> + "[#{shape_to_type(context.language, %Shape{type: "boolean"}, module_name)}]" + + "smithy.api#Double" -> + "[#{shape_to_type(context.language, %Shape{type: "double"}, module_name)}]" + + "smithy.api#Document" -> + "[#{shape_to_type(context.language, %Shape{type: "document"}, module_name)}]" + + "smithy.api#Unit" -> + "[]" + + "smithy.api#Float" -> + "[#{shape_to_type(context.language, %Shape{type: "float"}, module_name)}]" + + "smithy.api#Blob" -> + "[#{shape_to_type(context.language, %Shape{type: "blob"}, module_name)}]" + + _ -> + case all_shapes[shape_name] do + + %Shape{type: "structure"} -> + type = "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" + if reserved_type(type) do + "#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}()" + else + "#{type}()" + end + + %Shape{type: "list", member: member} -> + type = "#{shape_to_type(context, member["target"], module_name, all_shapes)}" + if reserved_type(type) do + "list(#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}())" + else + "list(#{type}())" + end + + nil -> + raise "Tried to reference an undefined shape for #{shape_name}" + + shape -> + shape_to_type(context.language, shape, module_name) + end + end + end + + # Unfortunately, gotta patch over auto-defining types that already exist in Elixir + defp shape_to_type(:elixir, "String", _), do: "String.t()" + defp shape_to_type(:erlang, "String", _), do: "string()" + defp shape_to_type(:elixir, "string", _), do: "String.t()" + defp shape_to_type(:erlang, "string", _), do: "string()" + defp shape_to_type(:elixir, "Identifier", _), do: "String.t()" + defp shape_to_type(:erlang, "Identifier", _), do: "string()" + defp shape_to_type(:elixir, "identifier", _), do: "String.t()" + defp shape_to_type(:erlang, "identifier", _), do: "string()" + defp shape_to_type(:elixir, "XmlString" <> _rest, _), do: "String.t()" + defp shape_to_type(:erlang, "XmlString" <> _rest, _), do: "string" + defp shape_to_type(:elixir, "NullablePositiveInteger", _), do: "nil | non_neg_integer()" + defp shape_to_type(:erlang, "NullablePositiveInteger", _), do: "undefined | non_neg_integer()" + defp shape_to_type(_, %Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" + defp shape_to_type(_, %Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" + defp shape_to_type(_, %Shape{type: "map"}, _module_name), do: "map()" + defp shape_to_type(_, %Shape{type: "blob"}, _module_name), do: "binary()" + defp shape_to_type(:elixir, %Shape{type: "string"}, _module_name), do: "String.t()" + defp shape_to_type(:erlang, %Shape{type: "string"}, _module_name), do: "string()" + defp shape_to_type(_, %Shape{type: "integer"}, _module_name), do: "integer()" + defp shape_to_type(_, %Shape{type: "boolean"}, _module_name), do: "boolean()" + defp shape_to_type(_, %Shape{type: "enum"}, _module_name), do: "list(any())" + defp shape_to_type(_, %Shape{type: "union"}, _module_name), do: "list()" + defp shape_to_type(_, %Shape{type: "document"}, _module_name), do: "any()" + + defp is_required(:elixir, is_input, shape, target) do + trimmed_name = String.replace(target, ~r/com\.amazonaws\.[^#]+#/, "") + if is_input do + if Map.has_key?(shape, "traits") do + if Map.has_key?(shape["traits"], "smithy.api#required") do + "required(\"#{trimmed_name}\") => " + else + "optional(\"#{trimmed_name}\") => " + end + else + "optional(\"#{trimmed_name}\") => " + end + else + "\"#{trimmed_name}\" => " + end + end + defp is_required(:erlang, is_input, shape, target) do + trimmed_name = String.replace(target, ~r/com\.amazonaws\.[^#]+#/, "") + if is_input do + if Map.has_key?(shape, "traits") do + if Map.has_key?(shape["traits"], "smithy.api#required") do + "<<\"#{trimmed_name}\">> := " + else + "<<\"#{trimmed_name}\">> => " + end + else + "<<\"#{trimmed_name}\">> => " + end + else + "<<\"#{trimmed_name}\">> => " + end + end + + defp reserved_type(type) do + type == "node" || type == "term" || type == "function" || type == "reference" + end + + def function_argument_type(:elixir, action) do + case Map.fetch!(action.input, "target") do + "smithy.api#Unit" -> "%{}" + type -> + "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" + end + end + def function_argument_type(:erlang, action) do + case Map.fetch!(action.input, "target") do + "smithy.api#Unit" -> "\#{}" + type -> + "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" + end + end + + def return_type(:elixir, action) do + case Map.fetch!(action.output, "target") do + "smithy.api#Unit" -> + normal = "{:ok, nil, any()}" + errors = + if is_list(action.errors) do + ["{:error, #{action.function_name}_errors()}"] + else + [] + end + Enum.join([normal, "{:error, {:unexpected_response, any()}}" | errors], " | \n") + type -> + normal = "{:ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), any()}" + errors = + if is_list(action.errors) do + ["{:error, #{action.function_name}_errors()}"] + else + [] + end + Enum.join([normal, "{:error, {:unexpected_response, any()}}" | errors], " | \n") + end + end + def return_type(:erlang, action) do + case Map.get(action.output, "target") do + "smithy.api#Unit" -> + normal = "{ok, undefined, tuple()}" + errors = + if is_list(action.errors) do + ["{error, #{action.function_name}_errors(), tuple()}"] + else + [] + end + Enum.join([normal, "{error, any()}" | errors], " |\n ") + type -> + normal = "{ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" + errors = + if is_list(action.errors) do + ["{error, #{action.function_name}_errors(), tuple()}"] + else + [] + end + Enum.join([normal, "{error, any()}" | errors], " |\n ") + end + end + + def required_function_parameter_types(action) do + function_parameter_types(action.method, action, true) + end + + def function_parameter_types("GET", action, false = _required_only) do + language = action.language + Enum.join( + [join_parameter_types(action.url_parameters, language), + join_parameter_types(action.query_parameters, language), + join_parameter_types(action.request_header_parameters, language), + join_parameter_types(action.request_headers_parameters, language) + ]) + end + def function_parameter_types("GET", action, true = _required_only) do + language = action.language + Enum.join( + [join_parameter_types(action.url_parameters, language), + join_parameter_types(action.required_query_parameters, language), + join_parameter_types(action.required_request_header_parameters, language) + ]) + end + def function_parameter_types(_method, action, _required_only) do + language = action.language + join_parameter_types(action.url_parameters, language) + end + + defp join_parameter_types(parameters, :elixir) do + Enum.map_join( + parameters, + fn parameter -> + if not parameter.required do + ", String.t() | nil" + else + ", String.t()" + end + end + ) + end + defp join_parameter_types(parameters, :erlang) do + Enum.join(Enum.map(parameters, fn _parameter -> ", binary() | list()" end)) + end + +end diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index 9c416fa..b67de6f 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -30,4 +30,27 @@ defmodule AWS.CodeGen.Util do service["traits"]["aws.api#service"]["sdkId"] end + def input_keys(action, context) do + shapes = context.shapes + input_shape = action.input["target"] + maybe_shape = Enum.filter(shapes, fn {name, _shape} -> input_shape == name end) + case maybe_shape do + [] -> + [] + [{_name, shape}] -> + Enum.reduce(shape.members, + [], + fn {name, %{"traits" => traits}}, acc -> + if Map.has_key?(traits, "smithy.api#required") do + [name <> " Required: true" | acc] + else + [name <> " Required: false" | acc] + end + {name, _shape}, acc -> + [name <> " Required: false" | acc] + end) + |> Enum.reverse() + end + end + end diff --git a/priv/post.erl.eex b/priv/post.erl.eex index 691b1f3..592570d 100644 --- a/priv/post.erl.eex +++ b/priv/post.erl.eex @@ -8,14 +8,51 @@ -include_lib("hackney/include/hackney_lib.hrl"). +<%= for {type_name, type_fields} <- AWS.CodeGen.Types.types(context) do %> +%% Example: +%% <%= type_name %>() :: #{ +<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> + ~s{%% #{field_name}#{field_type}} +end) %> +%% } +-type <%= "#{type_name}()" %> :: #{binary() => any()}. +<% end %> +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Types.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "-type #{action.function_name}_errors() ::\n #{Enum.join(error_types, " | \n ")}." + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> + %%==================================================================== %% API %%==================================================================== <%= for action <- context.actions do %> <%= action.docstring %> +-spec <%= action.function_name %>(aws_client:aws_client(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client, Input) when is_map(Client), is_map(Input) -> <%= action.function_name %>(Client, Input, []). + +-spec <%= action.function_name %>(aws_client:aws_client(), <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, proplists:proplist()) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client, Input, Options) when is_map(Client), is_map(Input), is_list(Options) -> request(Client, <<"<%= action.name %>">>, Input, Options). diff --git a/priv/post.ex.eex b/priv/post.ex.eex index fc8bd43..7d284a7 100644 --- a/priv/post.ex.eex +++ b/priv/post.ex.eex @@ -11,6 +11,46 @@ defmodule <%= context.module_name %> do alias AWS.Client alias AWS.Request + <%= for {type_name, type_fields} <- AWS.CodeGen.Types.types(context) do %> +@typedoc """ + +## Example: + <%= if map_size(type_fields) == 0 do %> + <%= "#{type_name}() :: %{}" %> + <% else %> + <%= "#{type_name}() :: %{" %> + <%= Enum.map_join(type_fields, ",\n ", fn {field_name, field_type} -> + ~s{ #{field_name}#{field_type}} + end) %> + } + <% end %> +""" +@type <%= if map_size(type_fields) == 0 do "#{type_name}() :: %{}" else "#{type_name}() :: %{String.t => any()}" end %> +<% end %> + +<%= Enum.map(context.actions, + fn action -> + 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) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "@type #{action.function_name}_errors() :: #{Enum.join(error_types, " | ")}" + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> + def metadata do %{ api_version: <%= inspect(context.api_version) %>, @@ -30,6 +70,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)%> def <%= action.function_name %>(%Client{} = client, input, options \\ []) do meta = <%= if action.host_prefix do %> diff --git a/priv/rest.erl.eex b/priv/rest.erl.eex index cfa3a4d..8453ab8 100644 --- a/priv/rest.erl.eex +++ b/priv/rest.erl.eex @@ -8,19 +8,60 @@ -include_lib("hackney/include/hackney_lib.hrl"). +<%= for {type_name, type_fields} <- AWS.CodeGen.Types.types(context) do %> +<%= if Enum.empty?(type_fields) do %>%% Example: +%% <%= type_name %>() :: #{} +-type <%= "#{type_name}()" %> :: #{}.<% else %> +%% Example: +%% <%= type_name %>() :: #{ +<%= Enum.map_join(type_fields, ",\n", fn {field_name, field_type} -> + ~s{%% #{field_name}#{field_type}} +end) %> +%% } +-type <%= "#{type_name}()" %> :: #{binary() => any()}.<% end %> +<% end %> +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Types.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "-type #{action.function_name}_errors() ::\n #{Enum.join(error_types, " | \n ")}." + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> + %%==================================================================== %% API %%==================================================================== <%= for action <- context.actions do %> <%= action.docstring %><%= if action.method == "GET" do %> +-spec <%= action.function_name %>(aws_client:aws_client()<%= AWS.CodeGen.Types.required_function_parameter_types(action) %>) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>) when is_map(Client) -> <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, #{}, #{}). +-spec <%= action.function_name %>(aws_client:aws_client()<%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, map(), map()) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, QueryMap, HeadersMap) when is_map(Client), is_map(QueryMap), is_map(HeadersMap) -> <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, QueryMap, HeadersMap, []). +-spec <%= action.function_name %>(aws_client:aws_client()<%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, map(), map(), proplists:proplist()) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.required_function_parameters(action) %>, QueryMap, HeadersMap, Options0) when is_map(Client), is_map(QueryMap), is_map(HeadersMap), is_list(Options0) -> Path = ["<%= AWS.CodeGen.RestService.Action.url_path(action) %>"],<%= if AWS.CodeGen.RestService.Context.s3_context?(context) do %> @@ -70,8 +111,13 @@ end.<% else %> request(Client, get, Path, Query_, Headers, undefined, Options, SuccessStatusCode<%= if AWS.CodeGen.RestService.Context.s3_context?(context) do %>, Bucket<% end %>).<% end %> <% else %> +-spec <%= action.function_name %>(aws_client:aws_client()<%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.function_parameters(action) %>, Input) -> <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.function_parameters(action) %>, Input, []). + +-spec <%= action.function_name %>(aws_client:aws_client()<%= AWS.CodeGen.Types.required_function_parameter_types(action) %>, <%= AWS.CodeGen.Types.function_argument_type(context.language, action)%>, proplists:proplist()) -> + <%= AWS.CodeGen.Types.return_type(context.language, action)%>. <%= action.function_name %>(Client<%= AWS.CodeGen.RestService.function_parameters(action) %>, Input0, Options0) -> Method = <%= AWS.CodeGen.RestService.Action.method(action) %>, Path = ["<%= AWS.CodeGen.RestService.Action.url_path(action) %>"],<%= if AWS.CodeGen.RestService.Context.s3_context?(context) do %> @@ -135,7 +181,7 @@ %% Internal functions %%==================================================================== --spec proplists_take(any(), proplists:proplists(), any()) -> {any(), proplists:proplists()}. +-spec proplists_take(any(), proplists:proplist(), any()) -> {any(), proplists:proplist()}. proplists_take(Key, Proplist, Default) -> Value = proplists:get_value(Key, Proplist, Default), {Value, proplists:delete(Key, Proplist)}. diff --git a/priv/rest.ex.eex b/priv/rest.ex.eex index 003ad39..067416b 100644 --- a/priv/rest.ex.eex +++ b/priv/rest.ex.eex @@ -11,6 +11,46 @@ defmodule <%= context.module_name %> do alias AWS.Client alias AWS.Request + <%= for {type_name, type_fields} <- AWS.CodeGen.Types.types(context) 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) %> + } +<% end %> +""" +@type <%= if Enum.empty?(type_fields) do "#{type_name}() :: %{}" else "#{type_name}() :: %{String.t => any()}" end %> +<% end %> + +<%= Enum.map(context.actions, + fn action -> + errors = action.errors + if not is_nil(errors) do + errors_snakecased = errors |> Enum.map(fn error -> AWS.CodeGen.Name.to_snake_case(String.replace(error["target"], ~r/com\.amazonaws\.[^#]+#/, "")) end) + all_types = AWS.CodeGen.Types.types(context) + error_types = Enum.reduce(all_types, + [], + fn {type_name, _type_fields}, acc -> + if Enum.member?(errors_snakecased, type_name) do + ["#{type_name}()" | acc] + else + acc + end + end + ) + "@type #{action.function_name}_errors() :: #{Enum.join(error_types, " | ")}" + end + end) + |> Enum.reject(&is_nil/1) + |> Enum.join("\n\n") +%> + def metadata do %{ api_version: <%= inspect(context.api_version) %>, @@ -30,6 +70,7 @@ defmodule <%= context.module_name %> do @doc """ <%= action.docstring %> """<% end %><%= if action.method == "GET" do %> + @spec <%= action.function_name %>(map()<%= 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<%= AWS.CodeGen.RestService.function_parameters(action) %>, options \\ []) do url_path = "<%= AWS.CodeGen.RestService.Action.url_path(action) %>" headers = []<%= for parameter <- action.request_header_parameters do %> @@ -74,7 +115,8 @@ defmodule <%= context.module_name %> do <% end %> Request.request_rest(client, meta, :get, url_path, query_params, headers, nil, options, <%= inspect(action.success_status_code) %>)<% else %> - def <%= action.function_name %>(%Client{} = client<%= AWS.CodeGen.RestService.function_parameters(action) %>, input, options \\ []) do +@spec <%= action.function_name %>(map()<%= AWS.CodeGen.Types.function_parameter_types(action.method, action, false)%>, <%= 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) %>, input, options \\ []) do url_path = "<%= AWS.CodeGen.RestService.Action.url_path(action) %>"<%= if length(action.request_header_parameters) > 0 do %> {headers, input} = [<%= for parameter <- action.request_header_parameters do %> diff --git a/test/aws_codegen/rest_service_test.exs b/test/aws_codegen/rest_service_test.exs index ed8bd69..24944f3 100644 --- a/test/aws_codegen/rest_service_test.exs +++ b/test/aws_codegen/rest_service_test.exs @@ -59,10 +59,20 @@ defmodule AWS.CodeGen.RestServiceTest do %RestService.Action{ arity: 3, docstring: " Ingests your application events into CloudTrail Lake.\n\n A required parameter,\n `auditEvents`, accepts the JSON records (also called\n *payload*) of events that you want CloudTrail to ingest. You\n can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents`\n request.", - function_name: "put_audit_events", + errors: [ + %{"target" => "com.amazonaws.cloudtraildata#ChannelInsufficientPermission"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelNotFound"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema"}, + %{"target" => "com.amazonaws.cloudtraildata#DuplicatedAuditEventId"}, + %{"target" => "com.amazonaws.cloudtraildata#InvalidChannelARN"}, + %{"target" => "com.amazonaws.cloudtraildata#UnsupportedOperationException"} + ], + function_name: "put_audit_events", language: :elixir, method: "POST", name: "com.amazonaws.cloudtraildata#PutAuditEvents", + input: %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsRequest"}, + output: %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsResponse"}, query_parameters: [ %RestService.Parameter{ code_name: "channel_arn", diff --git a/test/aws_codegen_test.exs b/test/aws_codegen_test.exs index de43c64..5504e39 100644 --- a/test/aws_codegen_test.exs +++ b/test/aws_codegen_test.exs @@ -69,105 +69,154 @@ defmodule AWS.CodeGenTest do alias AWS.Client alias AWS.Request - def metadata do - %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", - credential_scope: nil, - endpoint_prefix: "cloudtrail-data", - global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", - target_prefix: nil - } - end + @typedoc \"\"\" - @doc \"\"\" - Ingests your application events into CloudTrail Lake. + ## Example: + + audit_event() :: %{ + \"eventData\" => [String.t()], + \"eventDataChecksum\" => [String.t()], + \"id\" => String.t() + } - A required parameter, - `auditEvents`, accepts the JSON records (also called - *payload*) of events that you want CloudTrail to ingest. You - can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` - request. \"\"\" - def put_audit_events(%Client{} = client, input, options \\\\ []) do - url_path = "/PutAuditEvents" - headers = [] + @type audit_event() :: %{String.t() => any()} - {query_params, input} = - [ - {"channelArn", "channelArn"}, - {"externalId", "externalId"} - ] - |> Request.build_params(input) + @typedoc \"\"\" - meta = metadata() + ## Example: - Request.request_rest( - client, - meta, - :post, - url_path, - query_params, - headers, - input, - options, - 200 - ) - end - end - """) - end + audit_event_result_entry() :: %{ + \"eventID\" => String.t(), + \"id\" => String.t() + } - test "renders POST action with options to send/receive binary", %{specs: specs} do - context = setup_context(:elixir, specs) + \"\"\" + @type audit_event_result_entry() :: %{String.t() => any()} - [action | _] = context.actions - action = %{action | send_body_as_binary?: true, receive_body_as_binary?: true} + @typedoc \"\"\" - result = - %{context | actions: [action]} - |> AWS.CodeGen.render("priv/rest.ex.eex") - |> IO.iodata_to_binary() + ## Example: - assert result == - String.trim_leading(""" - # WARNING: DO NOT EDIT, AUTO-GENERATED CODE! - # See https://github.com/aws-beam/aws-codegen for more details. + channel_insufficient_permission() :: %{ + \"message\" => [String.t()] + } - defmodule AWS.CloudTrailData do - @moduledoc \"\"\" - The CloudTrail Data Service lets you ingest events into CloudTrail from any - source in your - hybrid environments, such as in-house or SaaS applications hosted on-premises or - in the cloud, - virtual machines, or containers. + \"\"\" + @type channel_insufficient_permission() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_not_found() :: %{ + \"message\" => [String.t()] + } - You can store, access, analyze, troubleshoot and take action on - this data without maintaining multiple log aggregators and reporting tools. - After you run - `PutAuditEvents` to ingest your application activity into CloudTrail, you can - use CloudTrail Lake to search, query, and analyze the data that is logged - from your applications. \"\"\" + @type channel_not_found() :: %{String.t() => any()} - alias AWS.Client - alias AWS.Request + @typedoc \"\"\" + + ## Example: + + channel_unsupported_schema() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_unsupported_schema() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + duplicated_audit_event_id() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type duplicated_audit_event_id() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + invalid_channel_arn() :: %{ + \"message\" => [String.t()] + } + + \"\"" + @type invalid_channel_arn() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_request() :: %{ + optional(\"externalId\") => String.t(), + required(\"auditEvents\") => list(audit_event()()), + required(\"channelArn\") => String.t() + } + + \"\"\" + @type put_audit_events_request() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_response() :: %{ + required(\"failed\") => list(result_error_entry()()), + required(\"successful\") => list(audit_event_result_entry()()) + } + + \"\"\" + @type put_audit_events_response() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + result_error_entry() :: %{ + \"errorCode\" => String.t(), + \"errorMessage\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type result_error_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + unsupported_operation_exception() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type unsupported_operation_exception() :: %{String.t() => any()} + + @type put_audit_events_errors() :: + unsupported_operation_exception() + | invalid_channel_arn() + | duplicated_audit_event_id() + | channel_unsupported_schema() + | channel_not_found() + | channel_insufficient_permission() def metadata do %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", + api_version: \"2021-08-11\", + content_type: \"application/x-amz-json-1.1\", credential_scope: nil, - endpoint_prefix: "cloudtrail-data", + endpoint_prefix: \"cloudtrail-data\", global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", + protocol: \"rest-json\", + service_id: \"CloudTrail Data\", + signature_version: \"v4\", + signing_name: \"cloudtrail-data\", target_prefix: nil } end @@ -181,31 +230,21 @@ defmodule AWS.CodeGenTest do can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` request. \"\"\" + @spec put_audit_events(map(), put_audit_events_request(), list()) :: + {:ok, put_audit_events_response(), any()} + | {:error, {:unexpected_response, any()}} + | {:error, put_audit_events_errors()} def put_audit_events(%Client{} = client, input, options \\\\ []) do - url_path = "/PutAuditEvents" + url_path = \"/PutAuditEvents\" headers = [] {query_params, input} = [ - {"channelArn", "channelArn"}, - {"externalId", "externalId"} + {\"channelArn\", \"channelArn\"}, + {\"externalId\", \"externalId\"} ] |> Request.build_params(input) - options = - Keyword.put( - options, - :send_body_as_binary?, - true - ) - - options = - Keyword.put( - options, - :receive_body_as_binary?, - true - ) - meta = metadata() Request.request_rest( @@ -265,17 +304,154 @@ defmodule AWS.CodeGenTest do alias AWS.Client alias AWS.Request + @typedoc \"\"\" + + ## Example: + + audit_event() :: %{ + \"eventData\" => [String.t()], + \"eventDataChecksum\" => [String.t()], + \"id\" => String.t() + } + + \"\"\" + @type audit_event() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + audit_event_result_entry() :: %{ + \"eventID\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type audit_event_result_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_insufficient_permission() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_insufficient_permission() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_not_found() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_not_found() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_unsupported_schema() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_unsupported_schema() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + duplicated_audit_event_id() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type duplicated_audit_event_id() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + invalid_channel_arn() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type invalid_channel_arn() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_request() :: %{ + optional(\"externalId\") => String.t(), + required(\"auditEvents\") => list(audit_event()()), + required(\"channelArn\") => String.t() + } + + \"\"\" + @type put_audit_events_request() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_response() :: %{ + required(\"failed\") => list(result_error_entry()()), + required(\"successful\") => list(audit_event_result_entry()()) + } + + \"\"\" + @type put_audit_events_response() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + result_error_entry() :: %{ + \"errorCode\" => String.t(), + \"errorMessage\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type result_error_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + unsupported_operation_exception() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type unsupported_operation_exception() :: %{String.t() => any()} + + @type put_audit_events_errors() :: + unsupported_operation_exception() + | invalid_channel_arn() + | duplicated_audit_event_id() + | channel_unsupported_schema() + | channel_not_found() + | channel_insufficient_permission() + def metadata do %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", + api_version: \"2021-08-11\", + content_type: \"application/x-amz-json-1.1\", credential_scope: nil, - endpoint_prefix: "cloudtrail-data", + endpoint_prefix: \"cloudtrail-data\", global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", + protocol: \"rest-json\", + service_id: \"CloudTrail Data\", + signature_version: \"v4\", + signing_name: \"cloudtrail-data\", target_prefix: nil } end @@ -289,8 +465,12 @@ defmodule AWS.CodeGenTest do can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` request. \"\"\" + @spec put_audit_events(map(), String.t(), String.t() | nil, list()) :: + {:ok, put_audit_events_response(), any()} + | {:error, {:unexpected_response, any()}} + | {:error, put_audit_events_errors()} def put_audit_events(%Client{} = client, channel_arn, external_id \\\\ nil, options \\\\ []) do - url_path = "/PutAuditEvents" + url_path = \"/PutAuditEvents\" headers = [] query_params = [] @@ -366,17 +546,154 @@ defmodule AWS.CodeGenTest do alias AWS.Client alias AWS.Request + @typedoc \"\"\" + + ## Example: + + audit_event() :: %{ + \"eventData\" => [String.t()], + \"eventDataChecksum\" => [String.t()], + \"id\" => String.t() + } + + \"\"\" + @type audit_event() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + audit_event_result_entry() :: %{ + \"eventID\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type audit_event_result_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_insufficient_permission() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_insufficient_permission() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_not_found() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_not_found() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + channel_unsupported_schema() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type channel_unsupported_schema() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + duplicated_audit_event_id() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type duplicated_audit_event_id() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + invalid_channel_arn() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type invalid_channel_arn() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_request() :: %{ + optional(\"externalId\") => String.t(), + required(\"auditEvents\") => list(audit_event()()), + required(\"channelArn\") => String.t() + } + + \"\"\" + @type put_audit_events_request() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + put_audit_events_response() :: %{ + required(\"failed\") => list(result_error_entry()()), + required(\"successful\") => list(audit_event_result_entry()()) + } + + \"\"\" + @type put_audit_events_response() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + result_error_entry() :: %{ + \"errorCode\" => String.t(), + \"errorMessage\" => String.t(), + \"id\" => String.t() + } + + \"\"\" + @type result_error_entry() :: %{String.t() => any()} + + @typedoc \"\"\" + + ## Example: + + unsupported_operation_exception() :: %{ + \"message\" => [String.t()] + } + + \"\"\" + @type unsupported_operation_exception() :: %{String.t() => any()} + + @type put_audit_events_errors() :: + unsupported_operation_exception() + | invalid_channel_arn() + | duplicated_audit_event_id() + | channel_unsupported_schema() + | channel_not_found() + | channel_insufficient_permission() + def metadata do %{ - api_version: "2021-08-11", - content_type: "application/x-amz-json-1.1", + api_version: \"2021-08-11\", + content_type: \"application/x-amz-json-1.1\", credential_scope: nil, - endpoint_prefix: "cloudtrail-data", + endpoint_prefix: \"cloudtrail-data\", global?: false, - protocol: "rest-json", - service_id: "CloudTrail Data", - signature_version: "v4", - signing_name: "cloudtrail-data", + protocol: \"rest-json\", + service_id: \"CloudTrail Data\", + signature_version: \"v4\", + signing_name: \"cloudtrail-data\", target_prefix: nil } end @@ -390,18 +707,22 @@ defmodule AWS.CodeGenTest do can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents` request. \"\"\" + @spec put_audit_events(map(), put_audit_events_request(), list()) :: + {:ok, put_audit_events_response(), any()} + | {:error, {:unexpected_response, any()}} + | {:error, put_audit_events_errors()} def put_audit_events(%Client{} = client, input, options \\\\ []) do - url_path = "/PutAuditEvents" + url_path = \"/PutAuditEvents\" headers = [] {query_params, input} = [ - {"channelArn", "channelArn"}, - {"externalId", "externalId"} + {\"channelArn\", \"channelArn\"}, + {\"externalId\", \"externalId\"} ] |> Request.build_params(input) - meta = metadata() |> Map.put_new(:host_prefix, "my-host-prefix.") + meta = metadata() |> Map.put_new(:host_prefix, \"my-host-prefix.\") Request.request_rest( client, From 70fdc8ed57de2895cae018167ce9310a63a4b7a2 Mon Sep 17 00:00:00 2001 From: Onno Vos Date: Thu, 28 Mar 2024 17:44:15 +0100 Subject: [PATCH 2/2] Formatter pass --- lib/aws_codegen.ex | 13 +- lib/aws_codegen/docstring.ex | 9 +- lib/aws_codegen/post_service.ex | 28 +- lib/aws_codegen/rest_service.ex | 41 +- lib/aws_codegen/shapes.ex | 17 +- lib/aws_codegen/spec.ex | 51 +- lib/aws_codegen/types.ex | 78 +- lib/aws_codegen/util.ex | 39 +- test/aws_codegen/rest_service_test.exs | 62 +- test/aws_codegen/shapes_test.exs | 12 +- test/aws_codegen/spec_test.exs | 950 ++++++++++++++++++++++++- 11 files changed, 1148 insertions(+), 152 deletions(-) diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index 248d6ed..858dccf 100644 --- a/lib/aws_codegen.ex +++ b/lib/aws_codegen.ex @@ -108,15 +108,17 @@ defmodule AWS.CodeGen do template_path = Path.join(template_base_path, template) context = protocol_service.load_context(language, spec, endpoints_spec) + case Map.get(context, :actions) do [] -> IO.puts(["Skipping ", spec.module_name, " due to no actions"]) + _ -> code = render(context, template_path) - IO.puts(["Writing ", spec.module_name, " to ", output_path]) + IO.puts(["Writing ", spec.module_name, " to ", output_path]) - File.write(output_path, code) + File.write(output_path, code) end else IO.puts("Failed to generate #{spec.module_name}, protocol #{spec.protocol}") @@ -150,7 +152,12 @@ defmodule AWS.CodeGen do end defp get_endpoints_spec(base_path) do - Path.join([base_path, "../../", "smithy-aws-go-codegen/src/main/resources/software/amazon/smithy/aws/go/codegen", "endpoints.json"]) + Path.join([ + base_path, + "../../", + "smithy-aws-go-codegen/src/main/resources/software/amazon/smithy/aws/go/codegen", + "endpoints.json" + ]) |> Spec.parse_json() |> get_in(["partitions"]) |> Enum.filter(fn x -> x["partition"] == "aws" end) diff --git a/lib/aws_codegen/docstring.ex b/lib/aws_codegen/docstring.ex index dd537f9..2a5e21f 100644 --- a/lib/aws_codegen/docstring.ex +++ b/lib/aws_codegen/docstring.ex @@ -39,8 +39,10 @@ defmodule AWS.CodeGen.Docstring do |> String.trim_trailing() |> String.replace(@two_break_lines, "\n%%\n") |> String.replace(~r/'/, "'") - |> String.replace("`AVAILABLE`", "`AVAILABLE'") # aws-sdk-go docs are broken for this, hack it to make the edocs work - |> String.replace("`PENDING`", "`PENDING'") # aws-sdk-go docs are broken for this, hack it to make the edocs work + # aws-sdk-go docs are broken for this, hack it to make the edocs work + |> String.replace("`AVAILABLE`", "`AVAILABLE'") + # aws-sdk-go docs are broken for this, hack it to make the edocs work + |> String.replace("`PENDING`", "`PENDING'") end defp split_first_sentence_in_one_line(doc) do @@ -292,6 +294,7 @@ defmodule AWS.CodeGen.Docstring do case Enum.find(attrs, fn {attr, _} -> attr == "href" end) do {_, href} -> text = Floki.text(children) + if text == href do "[#{href}]" else @@ -304,7 +307,7 @@ defmodule AWS.CodeGen.Docstring do {_, _attrs, children} -> "#{Floki.text(children)}" - end) + end) |> Floki.raw_html(encode: true) end diff --git a/lib/aws_codegen/post_service.ex b/lib/aws_codegen/post_service.ex index f735d5e..576ea3e 100644 --- a/lib/aws_codegen/post_service.ex +++ b/lib/aws_codegen/post_service.ex @@ -62,17 +62,23 @@ defmodule AWS.CodeGen.PostService do traits = service["traits"] actions = collect_actions(language, spec.api) shapes = Shapes.collect_shapes(language, spec.api) - endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] + + endpoint_prefix = + traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] + endpoint_info = endpoints_spec["services"][endpoint_prefix] is_global = not is_nil(endpoint_info) and not Map.get(endpoint_info, "isRegionalized", true) + credential_scope = if is_global do endpoint_info["endpoints"]["aws-global"]["credentialScope"]["region"] end + json_version = AWS.CodeGen.Util.get_json_version(service) protocol = spec.protocol |> to_string() content_type = @configuration[protocol][:content_type] content_type = content_type <> if protocol == "json", do: json_version, else: "" + signing_name = if String.starts_with?(endpoint_prefix, "api.") do String.replace(endpoint_prefix, "api.", "") @@ -108,7 +114,9 @@ defmodule AWS.CodeGen.PostService do |> case do {key, _} -> String.replace(key, ~r/.*#/, "") - nil -> nil + + nil -> + nil end end @@ -119,12 +127,22 @@ defmodule AWS.CodeGen.PostService do Enum.reduce(shapes, [], fn {_, shape}, acc -> case shape["type"] do "service" -> - [acc | List.wrap(shape["operations"])] + "resource" -> - [shape["operations"], shape["collectionOperations"], shape["create"], shape["put"], shape["read"], shape["update"], shape["delete"], shape["list"]] + [ + shape["operations"], + shape["collectionOperations"], + shape["create"], + shape["put"], + shape["read"], + shape["update"], + shape["delete"], + shape["list"] + ] |> Enum.reject(&is_nil/1) |> Kernel.++(acc) + _ -> acc end @@ -134,6 +152,7 @@ defmodule AWS.CodeGen.PostService do Enum.map(operations, fn operation -> operation_spec = shapes[operation] + %Action{ arity: 3, docstring: @@ -152,5 +171,4 @@ defmodule AWS.CodeGen.PostService do |> Enum.sort(fn a, b -> a.function_name < b.function_name end) |> Enum.uniq() end - end diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 03284dd..cfe723e 100644 --- a/lib/aws_codegen/rest_service.ex +++ b/lib/aws_codegen/rest_service.ex @@ -120,7 +120,10 @@ defmodule AWS.CodeGen.RestService do traits = service["traits"] actions = collect_actions(language, spec.api) protocol = spec.protocol - endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] ##TODO: for some reason this field is not always present and docs are not clear on what to do + ## TODO: for some reason this field is not always present and docs are not clear on what to do + endpoint_prefix = + traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] + endpoint_info = endpoints_spec["services"][endpoint_prefix] is_global = not is_nil(endpoint_info) and not Map.get(endpoint_info, "isRegionalized", true) @@ -148,7 +151,8 @@ defmodule AWS.CodeGen.RestService do signing_name: signing_name, signature_version: AWS.CodeGen.Util.get_signature_version(service), service_id: AWS.CodeGen.Util.get_service_id(service), - target_prefix: nil, ##TODO: metadata["targetPrefix"], + ## TODO: metadata["targetPrefix"], + target_prefix: nil, shapes: Shapes.collect_shapes(language, spec.api) } end @@ -167,6 +171,7 @@ defmodule AWS.CodeGen.RestService do """ def function_parameters(action, required_only \\ false) do language = action.language + Enum.join([ join_parameters(action.url_parameters, language) | case action.method do @@ -214,12 +219,22 @@ defmodule AWS.CodeGen.RestService do Enum.reduce(shapes, [], fn {_, shape}, acc -> case shape["type"] do "service" -> - List.wrap(shape["operations"]) ++ acc + "resource" -> - [shape["operations"], shape["collectionOperations"], shape["create"], shape["put"], shape["read"], shape["update"], shape["delete"], shape["list"]] + [ + shape["operations"], + shape["collectionOperations"], + shape["create"], + shape["put"], + shape["read"], + shape["update"], + shape["delete"], + shape["list"] + ] |> Enum.reject(&is_nil/1) |> Kernel.++(acc) + _ -> acc end @@ -257,6 +272,7 @@ defmodule AWS.CodeGen.RestService do input_shape = Shapes.get_input_shape(operation_spec) output_shape = Shapes.get_output_shape(operation_spec) + %Action{ arity: length(url_parameters) + len_for_method, docstring: @@ -298,12 +314,16 @@ defmodule AWS.CodeGen.RestService do end defp collect_url_parameters(language, api_spec, operation) do - url_params = collect_parameters(language, api_spec, operation, "input", "smithy.api#httpLabel") + url_params = + collect_parameters(language, api_spec, operation, "input", "smithy.api#httpLabel") + url_params end defp collect_query_parameters(language, api_spec, operation) do - query_params = collect_parameters(language, api_spec, operation, "input", "smithy.api#httpQueryParams") + query_params = + collect_parameters(language, api_spec, operation, "input", "smithy.api#httpQueryParams") + params = collect_parameters(language, api_spec, operation, "input", "smithy.api#httpQuery") query_params ++ params end @@ -318,13 +338,18 @@ defmodule AWS.CodeGen.RestService do defp collect_parameters(language, api_spec, operation, data_type, param_type) do shape_name = api_spec["shapes"][operation][data_type]["target"] + if shape_name do case api_spec["shapes"][shape_name] do nil -> [] + shape -> required_members = - for {name, %{"traits" => traits}} <- shape["members"], Map.has_key?(traits, "smithy.api#required"), do: name + for {name, %{"traits" => traits}} <- shape["members"], + Map.has_key?(traits, "smithy.api#required"), + do: name + shape["members"] |> Enum.filter(filter_fn(param_type)) |> Enum.map(fn {name, x} -> @@ -356,6 +381,7 @@ defmodule AWS.CodeGen.RestService do required: required } end + defp build_parameter(language, {name, data}, required) do %Parameter{ code_name: @@ -369,5 +395,4 @@ defmodule AWS.CodeGen.RestService do required: required } end - end diff --git a/lib/aws_codegen/shapes.ex b/lib/aws_codegen/shapes.ex index 669968a..96d6af4 100644 --- a/lib/aws_codegen/shapes.ex +++ b/lib/aws_codegen/shapes.ex @@ -1,14 +1,13 @@ defmodule AWS.CodeGen.Shapes do - defmodule Shape do defstruct name: nil, - type: nil, - members: [], - member: [], - enum: [], - min: nil, - required: [], - is_input: nil + type: nil, + members: [], + member: [], + enum: [], + min: nil, + required: [], + is_input: nil end def get_input_shape(operation_spec) do @@ -39,6 +38,7 @@ defmodule AWS.CodeGen.Shapes do ## TODO: Should we validate or search for trait `smithy.api#httpPayload` rather than ## trust that the member is always named `Body`? inner_spec = get_in(shapes, [shape, "members", "Body"]) + if is_map(inner_spec) && Map.has_key?(inner_spec, "target") do ## TODO: we should extract the type from the actual shape `type` rather than infer it from the naming inner_spec["target"] @@ -52,5 +52,4 @@ defmodule AWS.CodeGen.Shapes do def is_input?(shape) do !Map.has_key?(shape, "traits") or Map.has_key?(shape["traits"], "smithy.api#input") end - end diff --git a/lib/aws_codegen/spec.ex b/lib/aws_codegen/spec.ex index 81c5946..2ad72e2 100644 --- a/lib/aws_codegen/spec.ex +++ b/lib/aws_codegen/spec.ex @@ -22,37 +22,44 @@ defmodule AWS.CodeGen.Spec do shape_name: nil def parse(api_filename, language) do - api_name = api_filename - |> Path.basename() - |> String.replace(["-", ".json"], "") + api_name = + api_filename + |> Path.basename() + |> String.replace(["-", ".json"], "") + api = parse_json(api_filename) + service_name = api |> find_service() |> String.replace("com.amazonaws.#{api_name}#", "") + traits = api["shapes"]["com.amazonaws." <> api_name <> "#" <> service_name]["traits"] - protocol = Enum.find_value(traits, fn {k, _v} -> - case String.split(k, "#") do - ["aws.protocols", protocol] -> protocol - _ -> nil - end - end) - |> String.replace("restJson1", "rest_json") - |> String.replace(["awsJson1_0", "awsJson1_1"], "json") - |> String.replace("awsQuery", "query") - |> String.replace("restXml", "rest_xml") - |> String.replace("ec2Query", "ec2") - |> then(fn value-> - if value in ~w(rest_json json query rest_xml ec2) do - value - else - raise "the protocol #{value} is not valid" - end - end) - |> String.to_atom() + protocol = + Enum.find_value(traits, fn {k, _v} -> + case String.split(k, "#") do + ["aws.protocols", protocol] -> protocol + _ -> nil + end + end) + |> String.replace("restJson1", "rest_json") + |> String.replace(["awsJson1_0", "awsJson1_1"], "json") + |> String.replace("awsQuery", "query") + |> String.replace("restXml", "rest_xml") + |> String.replace("ec2Query", "ec2") + |> then(fn value -> + if value in ~w(rest_json json query rest_xml ec2) do + value + else + raise "the protocol #{value} is not valid" + end + end) + |> String.to_atom() + module_name = module_name(traits, language) filename = filename(module_name, language) + %AWS.CodeGen.Spec{ protocol: protocol, module_name: module_name, diff --git a/lib/aws_codegen/types.ex b/lib/aws_codegen/types.ex index d1d43ef..dba1bb9 100644 --- a/lib/aws_codegen/types.ex +++ b/lib/aws_codegen/types.ex @@ -1,5 +1,4 @@ defmodule AWS.CodeGen.Types do - alias AWS.CodeGen.Shapes.Shape def types(context) do @@ -36,11 +35,17 @@ defmodule AWS.CodeGen.Types do defp process_shape_member(context, shape, name, shape_member, a) do target = shape_member["target"] + if Map.has_key?(shape_member, "traits") and has_http_label_trait(shape_member["traits"]) do a else shape_member_type = shape_to_type(context, target, context.module_name, context.shapes) - Map.put(a, is_required(context.language, shape.is_input, shape_member, name), shape_member_type) + + Map.put( + a, + is_required(context.language, shape.is_input, shape_member, name), + shape_member_type + ) end end @@ -95,9 +100,10 @@ defmodule AWS.CodeGen.Types do _ -> case all_shapes[shape_name] do - %Shape{type: "structure"} -> - type = "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" + type = + "#{AWS.CodeGen.Name.to_snake_case(String.replace(shape_name, ~r/com\.amazonaws\.[^#]+#/, ""))}" + if reserved_type(type) do "#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}()" else @@ -106,6 +112,7 @@ defmodule AWS.CodeGen.Types do %Shape{type: "list", member: member} -> type = "#{shape_to_type(context, member["target"], module_name, all_shapes)}" + if reserved_type(type) do "list(#{String.downcase(String.replace(context.module_name, ["aws_", "AWS."], ""))}_#{type}())" else @@ -134,7 +141,11 @@ defmodule AWS.CodeGen.Types do defp shape_to_type(:erlang, "XmlString" <> _rest, _), do: "string" defp shape_to_type(:elixir, "NullablePositiveInteger", _), do: "nil | non_neg_integer()" defp shape_to_type(:erlang, "NullablePositiveInteger", _), do: "undefined | non_neg_integer()" - defp shape_to_type(_, %Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()" + + defp shape_to_type(_, %Shape{type: type}, _module_name) + when type in ["float", "double", "long"], + do: "float()" + defp shape_to_type(_, %Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()" defp shape_to_type(_, %Shape{type: "map"}, _module_name), do: "map()" defp shape_to_type(_, %Shape{type: "blob"}, _module_name), do: "binary()" @@ -148,6 +159,7 @@ defmodule AWS.CodeGen.Types do defp is_required(:elixir, is_input, shape, target) do trimmed_name = String.replace(target, ~r/com\.amazonaws\.[^#]+#/, "") + if is_input do if Map.has_key?(shape, "traits") do if Map.has_key?(shape["traits"], "smithy.api#required") do @@ -162,8 +174,10 @@ defmodule AWS.CodeGen.Types do "\"#{trimmed_name}\" => " end end + defp is_required(:erlang, is_input, shape, target) do trimmed_name = String.replace(target, ~r/com\.amazonaws\.[^#]+#/, "") + if is_input do if Map.has_key?(shape, "traits") do if Map.has_key?(shape["traits"], "smithy.api#required") do @@ -185,14 +199,19 @@ defmodule AWS.CodeGen.Types do def function_argument_type(:elixir, action) do case Map.fetch!(action.input, "target") do - "smithy.api#Unit" -> "%{}" + "smithy.api#Unit" -> + "%{}" + type -> "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" end end + def function_argument_type(:erlang, action) do case Map.fetch!(action.input, "target") do - "smithy.api#Unit" -> "\#{}" + "smithy.api#Unit" -> + "\#{}" + type -> "#{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}()" end @@ -202,43 +221,56 @@ defmodule AWS.CodeGen.Types do case Map.fetch!(action.output, "target") do "smithy.api#Unit" -> normal = "{:ok, nil, any()}" + errors = if is_list(action.errors) do ["{:error, #{action.function_name}_errors()}"] else [] end + Enum.join([normal, "{:error, {:unexpected_response, any()}}" | errors], " | \n") + type -> - normal = "{:ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), any()}" + normal = + "{:ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), any()}" + errors = if is_list(action.errors) do ["{:error, #{action.function_name}_errors()}"] else [] end + Enum.join([normal, "{:error, {:unexpected_response, any()}}" | errors], " | \n") end end + def return_type(:erlang, action) do case Map.get(action.output, "target") do "smithy.api#Unit" -> normal = "{ok, undefined, tuple()}" + errors = if is_list(action.errors) do ["{error, #{action.function_name}_errors(), tuple()}"] else [] end + Enum.join([normal, "{error, any()}" | errors], " |\n ") + type -> - normal = "{ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" + normal = + "{ok, #{AWS.CodeGen.Name.to_snake_case(String.replace(type, ~r/com\.amazonaws\.[^#]+#/, ""))}(), tuple()}" + errors = if is_list(action.errors) do ["{error, #{action.function_name}_errors(), tuple()}"] else [] end + Enum.join([normal, "{error, any()}" | errors], " |\n ") end end @@ -249,21 +281,25 @@ defmodule AWS.CodeGen.Types do def function_parameter_types("GET", action, false = _required_only) do language = action.language - Enum.join( - [join_parameter_types(action.url_parameters, language), - join_parameter_types(action.query_parameters, language), - join_parameter_types(action.request_header_parameters, language), - join_parameter_types(action.request_headers_parameters, language) - ]) + + Enum.join([ + join_parameter_types(action.url_parameters, language), + join_parameter_types(action.query_parameters, language), + join_parameter_types(action.request_header_parameters, language), + join_parameter_types(action.request_headers_parameters, language) + ]) end + def function_parameter_types("GET", action, true = _required_only) do language = action.language - Enum.join( - [join_parameter_types(action.url_parameters, language), - join_parameter_types(action.required_query_parameters, language), - join_parameter_types(action.required_request_header_parameters, language) - ]) + + Enum.join([ + join_parameter_types(action.url_parameters, language), + join_parameter_types(action.required_query_parameters, language), + join_parameter_types(action.required_request_header_parameters, language) + ]) end + def function_parameter_types(_method, action, _required_only) do language = action.language join_parameter_types(action.url_parameters, language) @@ -281,8 +317,8 @@ defmodule AWS.CodeGen.Types do end ) end + defp join_parameter_types(parameters, :erlang) do Enum.join(Enum.map(parameters, fn _parameter -> ", binary() | list()" end)) end - end diff --git a/lib/aws_codegen/util.ex b/lib/aws_codegen/util.ex index b67de6f..c09d6f0 100644 --- a/lib/aws_codegen/util.ex +++ b/lib/aws_codegen/util.ex @@ -1,14 +1,17 @@ defmodule AWS.CodeGen.Util do - def service_docs(service) do with "

" <- service["traits"]["smithy.api#documentation"], do: "" end def get_json_version(service) do traits = service["traits"] - ["aws.protocols#" <> protocol | _] = Enum.filter(Map.keys(traits), &String.starts_with?(&1, "aws.protocols#")) + + ["aws.protocols#" <> protocol | _] = + Enum.filter(Map.keys(traits), &String.starts_with?(&1, "aws.protocols#")) + case protocol do - "restJson1" -> "1.1" ## TODO: according to the docs this should result in application/json but our current code will make it application/x-amz-json-1.1 + ## TODO: according to the docs this should result in application/json but our current code will make it application/x-amz-json-1.1 + "restJson1" -> "1.1" "awsJson1_0" -> "1.0" "awsJson1_1" -> "1.1" "awsQuery" -> nil @@ -20,6 +23,7 @@ defmodule AWS.CodeGen.Util do def get_signature_version(service) do traits = service["traits"] signature = Enum.filter(Map.keys(traits), &String.starts_with?(&1, "aws.auth#")) + case signature do ["aws.auth#sig" <> version] -> version [] -> nil @@ -34,23 +38,28 @@ defmodule AWS.CodeGen.Util do shapes = context.shapes input_shape = action.input["target"] maybe_shape = Enum.filter(shapes, fn {name, _shape} -> input_shape == name end) + case maybe_shape do [] -> [] + [{_name, shape}] -> - Enum.reduce(shape.members, - [], - fn {name, %{"traits" => traits}}, acc -> - if Map.has_key?(traits, "smithy.api#required") do - [name <> " Required: true" | acc] - else - [name <> " Required: false" | acc] - end - {name, _shape}, acc -> - [name <> " Required: false" | acc] - end) + Enum.reduce( + shape.members, + [], + fn + {name, %{"traits" => traits}}, acc -> + if Map.has_key?(traits, "smithy.api#required") do + [name <> " Required: true" | acc] + else + [name <> " Required: false" | acc] + end + + {name, _shape}, acc -> + [name <> " Required: false" | acc] + end + ) |> Enum.reverse() end end - end diff --git a/test/aws_codegen/rest_service_test.exs b/test/aws_codegen/rest_service_test.exs index 24944f3..cd45c61 100644 --- a/test/aws_codegen/rest_service_test.exs +++ b/test/aws_codegen/rest_service_test.exs @@ -42,7 +42,8 @@ defmodule AWS.CodeGen.RestServiceTest do content_type: "application/x-amz-json-1.1", credential_scope: nil, decode: "json", - docstring: " The CloudTrail Data Service lets you ingest events into CloudTrail from any\n source in your\n hybrid environments, such as in-house or SaaS applications hosted on-premises or\n in the cloud,\n virtual machines, or containers.\n\n You can store, access, analyze, troubleshoot and take action on\n this data without maintaining multiple log aggregators and reporting tools.\n After you run\n `PutAuditEvents` to ingest your application activity into CloudTrail, you can\n use CloudTrail Lake to search, query, and analyze the data that is logged\n from your applications.", + docstring: + " The CloudTrail Data Service lets you ingest events into CloudTrail from any\n source in your\n hybrid environments, such as in-house or SaaS applications hosted on-premises or\n in the cloud,\n virtual machines, or containers.\n\n You can store, access, analyze, troubleshoot and take action on\n this data without maintaining multiple log aggregators and reporting tools.\n After you run\n `PutAuditEvents` to ingest your application activity into CloudTrail, you can\n use CloudTrail Lake to search, query, and analyze the data that is logged\n from your applications.", encode: "json", endpoint_prefix: "cloudtrail-data", is_global: false, @@ -58,46 +59,47 @@ defmodule AWS.CodeGen.RestServiceTest do assert action == %RestService.Action{ arity: 3, - docstring: " Ingests your application events into CloudTrail Lake.\n\n A required parameter,\n `auditEvents`, accepts the JSON records (also called\n *payload*) of events that you want CloudTrail to ingest. You\n can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents`\n request.", + docstring: + " Ingests your application events into CloudTrail Lake.\n\n A required parameter,\n `auditEvents`, accepts the JSON records (also called\n *payload*) of events that you want CloudTrail to ingest. You\n can add up to 100 of these events (or up to 1 MB) per `PutAuditEvents`\n request.", errors: [ - %{"target" => "com.amazonaws.cloudtraildata#ChannelInsufficientPermission"}, - %{"target" => "com.amazonaws.cloudtraildata#ChannelNotFound"}, - %{"target" => "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema"}, - %{"target" => "com.amazonaws.cloudtraildata#DuplicatedAuditEventId"}, - %{"target" => "com.amazonaws.cloudtraildata#InvalidChannelARN"}, - %{"target" => "com.amazonaws.cloudtraildata#UnsupportedOperationException"} - ], - function_name: "put_audit_events", + %{"target" => "com.amazonaws.cloudtraildata#ChannelInsufficientPermission"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelNotFound"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema"}, + %{"target" => "com.amazonaws.cloudtraildata#DuplicatedAuditEventId"}, + %{"target" => "com.amazonaws.cloudtraildata#InvalidChannelARN"}, + %{"target" => "com.amazonaws.cloudtraildata#UnsupportedOperationException"} + ], + function_name: "put_audit_events", language: :elixir, method: "POST", name: "com.amazonaws.cloudtraildata#PutAuditEvents", input: %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsRequest"}, output: %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsResponse"}, query_parameters: [ - %RestService.Parameter{ - code_name: "channel_arn", - location_name: "channelArn", - name: "channelArn", - required: true - }, - %RestService.Parameter{ - code_name: "external_id", - location_name: "externalId", - name: "externalId", - required: false - } - ], + %RestService.Parameter{ + code_name: "channel_arn", + location_name: "channelArn", + name: "channelArn", + required: true + }, + %RestService.Parameter{ + code_name: "external_id", + location_name: "externalId", + name: "externalId", + required: false + } + ], receive_body_as_binary?: false, request_header_parameters: [], request_uri: "/PutAuditEvents", required_query_parameters: [ - %RestService.Parameter{ - code_name: "channel_arn", - location_name: "channelArn", - name: "channelArn", - required: true - } - ], + %RestService.Parameter{ + code_name: "channel_arn", + location_name: "channelArn", + name: "channelArn", + required: true + } + ], required_request_header_parameters: [], response_header_parameters: [], send_body_as_binary?: false, diff --git a/test/aws_codegen/shapes_test.exs b/test/aws_codegen/shapes_test.exs index 8e4b7ce..29b86a5 100644 --- a/test/aws_codegen/shapes_test.exs +++ b/test/aws_codegen/shapes_test.exs @@ -8,30 +8,30 @@ defmodule AWS.CodeGen.ShapesTest do "type" => "structure", "members" => %{ "ACL" => %{ - "target" => "com.amazonaws.s3#ObjectCannedACL", + "target" => "com.amazonaws.s3#ObjectCannedACL" }, "Body" => %{ - "target" => "com.amazonaws.s3#StreamingBlob", + "target" => "com.amazonaws.s3#StreamingBlob" } } }, "com.amazonaws.s3#StreamingBlob" => %{ "type" => "blob", "traits" => %{ - "smithy.api#streaming" => %{} + "smithy.api#streaming" => %{} } }, "com.amazonaws.s3#PutObjectAclRequest" => %{ "type" => "structure", "members" => %{ "ACL" => %{ - "target" => "com.amazonaws.s3#ObjectCannedACL", + "target" => "com.amazonaws.s3#ObjectCannedACL" }, "AccessControlPolicy" => %{ - "target" => "com.amazonaws.s3#AccessControlPolicy", + "target" => "com.amazonaws.s3#AccessControlPolicy" } } - }, + } } assert Shapes.body_as_binary?(shapes, "com.amazonaws.s3#PutObjectRequest") diff --git a/test/aws_codegen/spec_test.exs b/test/aws_codegen/spec_test.exs index 11098ad..c0cd04a 100644 --- a/test/aws_codegen/spec_test.exs +++ b/test/aws_codegen/spec_test.exs @@ -9,41 +9,931 @@ defmodule AWS.CodeGen.SpecTest do filename: "cloud_trail_data.ex", module_name: "AWS.CloudTrailData", protocol: :rest_json - } = - Spec.parse("test/fixtures/apis_specs/cloudtrail-data.json", :elixir) + } = Spec.parse("test/fixtures/apis_specs/cloudtrail-data.json", :elixir) assert %Spec{ filename: "aws_cloudtrail_data.erl", module_name: "aws_cloudtrail_data" - } = - Spec.parse("test/fixtures/apis_specs/cloudtrail-data.json", :erlang) + } = Spec.parse("test/fixtures/apis_specs/cloudtrail-data.json", :erlang) assert api == - %{ - "shapes" => %{ - "com.amazonaws.cloudtraildata#AuditEvent" => %{"members" => %{"eventData" => %{"target" => "smithy.api#String", "traits" => %{"smithy.api#documentation" => "

The content of an audit event that comes from the event, such as userIdentity, \n userAgent, and eventSource.

", "smithy.api#required" => %{}}}, "eventDataChecksum" => %{"target" => "smithy.api#String", "traits" => %{"smithy.api#documentation" => "

A checksum is a base64-SHA256 algorithm that helps you verify that CloudTrail receives the event that matches \n with the checksum. Calculate the checksum by running a command like the following:

\n

\n printf %s $eventdata | openssl dgst -binary -sha256 | base64\n

"}}, "id" => %{"target" => "com.amazonaws.cloudtraildata#Uuid", "traits" => %{"smithy.api#documentation" => "

The original event ID from the source event.

", "smithy.api#required" => %{}}}}, "traits" => %{"smithy.api#documentation" => "

An event from a source outside of Amazon Web Services that you want CloudTrail \n to log.

"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#AuditEventResultEntries" => %{"member" => %{"target" => "com.amazonaws.cloudtraildata#AuditEventResultEntry"}, "traits" => %{"smithy.api#length" => %{"max" => 100, "min" => 0}}, "type" => "list"}, - "com.amazonaws.cloudtraildata#AuditEventResultEntry" => %{"members" => %{"eventID" => %{"target" => "com.amazonaws.cloudtraildata#Uuid", "traits" => %{"smithy.api#documentation" => "

The event ID assigned by CloudTrail.

", "smithy.api#required" => %{}}}, "id" => %{"target" => "com.amazonaws.cloudtraildata#Uuid", "traits" => %{"smithy.api#documentation" => "

The original event ID from the source event.

", "smithy.api#required" => %{}}}}, "traits" => %{"smithy.api#documentation" => "

A response that includes successful and failed event results.

"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#AuditEvents" => %{"member" => %{"target" => "com.amazonaws.cloudtraildata#AuditEvent"}, "traits" => %{"smithy.api#length" => %{"max" => 100, "min" => 1}}, "type" => "list"}, - "com.amazonaws.cloudtraildata#ChannelArn" => %{"traits" => %{"aws.api#arnReference" => %{}, "smithy.api#pattern" => "^arn:.*$"}, "type" => "string"}, - "com.amazonaws.cloudtraildata#ChannelInsufficientPermission" => %{"members" => %{"message" => %{"target" => "smithy.api#String"}}, "traits" => %{"smithy.api#documentation" => "

The caller's account ID must be the same as the channel owner's account ID.

", "smithy.api#error" => "client"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#ChannelNotFound" => %{"members" => %{"message" => %{"target" => "smithy.api#String"}}, "traits" => %{"smithy.api#documentation" => "

The channel could not be found.

", "smithy.api#error" => "client"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema" => %{"members" => %{"message" => %{"target" => "smithy.api#String"}}, "traits" => %{"smithy.api#documentation" => "

The schema type of the event is not supported.

", "smithy.api#error" => "client"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#CloudTrailDataService" => %{"operations" => [%{"target" => "com.amazonaws.cloudtraildata#PutAuditEvents"}], "traits" => %{"aws.api#service" => %{"endpointPrefix" => "cloudtrail-data", "sdkId" => "CloudTrail Data"}, "aws.auth#sigv4" => %{"name" => "cloudtrail-data"}, "aws.protocols#restJson1" => %{}, "smithy.api#cors" => %{}, "smithy.api#documentation" => "

The CloudTrail Data Service lets you ingest events into CloudTrail from any source in your\nhybrid environments, such as in-house or SaaS applications hosted on-premises or in the cloud,\nvirtual machines, or containers. You can store, access, analyze, troubleshoot and take action on\nthis data without maintaining multiple log aggregators and reporting tools. After you run \nPutAuditEvents to ingest your application activity into CloudTrail, you can use CloudTrail Lake to search, query, and analyze the data that is logged\nfrom your applications.

", "smithy.api#title" => "AWS CloudTrail Data Service", "smithy.rules#endpointRuleSet" => %{"parameters" => %{"Endpoint" => %{"builtIn" => "SDK::Endpoint", "documentation" => "Override the endpoint used to send this request", "required" => false, "type" => "String"}, "Region" => %{"builtIn" => "AWS::Region", "documentation" => "The AWS region used to dispatch the request.", "required" => false, "type" => "String"}, "UseDualStack" => %{"builtIn" => "AWS::UseDualStack", "default" => false, "documentation" => "When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.", "required" => true, "type" => "Boolean"}, "UseFIPS" => %{"builtIn" => "AWS::UseFIPS", "default" => false, "documentation" => "When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.", "required" => true, "type" => "Boolean"}}, "rules" => [%{"conditions" => [%{"argv" => [%{"ref" => "Endpoint"}], "fn" => "isSet"}], "rules" => [%{"conditions" => [%{"argv" => [%{"ref" => "UseFIPS"}, true], "fn" => "booleanEquals"}], "error" => "Invalid Configuration: FIPS and custom endpoint are not supported", "type" => "error"}, %{"conditions" => [], "rules" => [%{"conditions" => [%{"argv" => [%{"ref" => "UseDualStack"}, true], "fn" => "booleanEquals"}], "error" => "Invalid Configuration: Dualstack and custom endpoint are not supported", "type" => "error"}, %{"conditions" => [], "endpoint" => %{"headers" => %{}, "properties" => %{}, "url" => %{"ref" => "Endpoint"}}, "type" => "endpoint"}], "type" => "tree"}], "type" => "tree"}, %{"conditions" => [], "rules" => [%{"conditions" => [%{"argv" => [%{"ref" => "Region"}], "fn" => "isSet"}], "rules" => [%{"conditions" => [%{"argv" => [%{"ref" => "Region"}], "assign" => "PartitionResult", "fn" => "aws.partition"}], "rules" => [%{"conditions" => [%{"argv" => [%{"ref" => "UseFIPS"}, true], "fn" => "booleanEquals"}, %{"argv" => [%{"ref" => "UseDualStack"}, true], "fn" => "booleanEquals"}], "rules" => [%{"conditions" => [%{"argv" => [true, %{"argv" => [%{"ref" => "PartitionResult"}, "supportsFIPS"], "fn" => "getAttr"}], "fn" => "booleanEquals"}, %{"argv" => [true, %{"argv" => [%{"ref" => "PartitionResult"}, "supportsDualStack"], "fn" => "getAttr"}], "fn" => "booleanEquals"}], "rules" => [%{"conditions" => [], "rules" => [%{"conditions" => [], "endpoint" => %{"headers" => %{}, "properties" => %{}, "url" => "https://cloudtrail-data-fips.{Region}.{PartitionResult#dualStackDnsSuffix}"}, "type" => "endpoint"}], "type" => "tree"}], "type" => "tree"}, %{"conditions" => [], "error" => "FIPS and DualStack are enabled, but this partition does not support one or both", "type" => "error"}], "type" => "tree"}, %{"conditions" => [%{"argv" => [%{"ref" => "UseFIPS"}, true], "fn" => "booleanEquals"}], "rules" => [%{"conditions" => [%{"argv" => [true, %{"argv" => [%{"ref" => "PartitionResult"}, "supportsFIPS"], "fn" => "getAttr"}], "fn" => "booleanEquals"}], "rules" => [%{"conditions" => [], "rules" => [%{"conditions" => [], "endpoint" => %{"headers" => %{}, "properties" => %{}, "url" => "https://cloudtrail-data-fips.{Region}.{PartitionResult#dnsSuffix}"}, "type" => "endpoint"}], "type" => "tree"}], "type" => "tree"}, %{"conditions" => [], "error" => "FIPS is enabled but this partition does not support FIPS", "type" => "error"}], "type" => "tree"}, %{"conditions" => [%{"argv" => [%{"ref" => "UseDualStack"}, true], "fn" => "booleanEquals"}], "rules" => [%{"conditions" => [%{"argv" => [true, %{"argv" => [%{"ref" => "PartitionResult"}, "supportsDualStack"], "fn" => "getAttr"}], "fn" => "booleanEquals"}], "rules" => [%{"conditions" => [], "rules" => [%{"conditions" => [], "endpoint" => %{"headers" => %{}, "properties" => %{}, "url" => "https://cloudtrail-data.{Region}.{PartitionResult#dualStackDnsSuffix}"}, "type" => "endpoint"}], "type" => "tree"}], "type" => "tree"}, %{"conditions" => [], "error" => "DualStack is enabled but this partition does not support DualStack", "type" => "error"}], "type" => "tree"}, %{"conditions" => [], "rules" => [%{"conditions" => [], "endpoint" => %{"headers" => %{}, "properties" => %{}, "url" => "https://cloudtrail-data.{Region}.{PartitionResult#dnsSuffix}"}, "type" => "endpoint"}], "type" => "tree"}], "type" => "tree"}], "type" => "tree"}, %{"conditions" => [], "error" => "Invalid Configuration: Missing Region", "type" => "error"}], "type" => "tree"}], "version" => "1.0"}, "smithy.rules#endpointTests" => %{"testCases" => [%{"documentation" => "For region us-east-1 with FIPS enabled and DualStack enabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.us-east-1.api.aws"}}, "params" => %{"Region" => "us-east-1", "UseDualStack" => true, "UseFIPS" => true}}, %{"documentation" => "For region us-east-1 with FIPS enabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.us-east-1.amazonaws.com"}}, "params" => %{"Region" => "us-east-1", "UseDualStack" => false, "UseFIPS" => true}}, %{"documentation" => "For region us-east-1 with FIPS disabled and DualStack enabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.us-east-1.api.aws"}}, "params" => %{"Region" => "us-east-1", "UseDualStack" => true, "UseFIPS" => false}}, %{"documentation" => "For region us-east-1 with FIPS disabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.us-east-1.amazonaws.com"}}, "params" => %{"Region" => "us-east-1", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For region cn-north-1 with FIPS enabled and DualStack enabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.cn-north-1.api.amazonwebservices.com.cn"}}, "params" => %{"Region" => "cn-north-1", "UseDualStack" => true, "UseFIPS" => true}}, %{"documentation" => "For region cn-north-1 with FIPS enabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.cn-north-1.amazonaws.com.cn"}}, "params" => %{"Region" => "cn-north-1", "UseDualStack" => false, "UseFIPS" => true}}, %{"documentation" => "For region cn-north-1 with FIPS disabled and DualStack enabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.cn-north-1.api.amazonwebservices.com.cn"}}, "params" => %{"Region" => "cn-north-1", "UseDualStack" => true, "UseFIPS" => false}}, %{"documentation" => "For region cn-north-1 with FIPS disabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.cn-north-1.amazonaws.com.cn"}}, "params" => %{"Region" => "cn-north-1", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For region us-gov-east-1 with FIPS enabled and DualStack enabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.us-gov-east-1.api.aws"}}, "params" => %{"Region" => "us-gov-east-1", "UseDualStack" => true, "UseFIPS" => true}}, %{"documentation" => "For region us-gov-east-1 with FIPS enabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.us-gov-east-1.amazonaws.com"}}, "params" => %{"Region" => "us-gov-east-1", "UseDualStack" => false, "UseFIPS" => true}}, %{"documentation" => "For region us-gov-east-1 with FIPS disabled and DualStack enabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.us-gov-east-1.api.aws"}}, "params" => %{"Region" => "us-gov-east-1", "UseDualStack" => true, "UseFIPS" => false}}, %{"documentation" => "For region us-gov-east-1 with FIPS disabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.us-gov-east-1.amazonaws.com"}}, "params" => %{"Region" => "us-gov-east-1", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For region us-iso-east-1 with FIPS enabled and DualStack enabled", "expect" => %{"error" => "FIPS and DualStack are enabled, but this partition does not support one or both"}, "params" => %{"Region" => "us-iso-east-1", "UseDualStack" => true, "UseFIPS" => true}}, %{"documentation" => "For region us-iso-east-1 with FIPS enabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.us-iso-east-1.c2s.ic.gov"}}, "params" => %{"Region" => "us-iso-east-1", "UseDualStack" => false, "UseFIPS" => true}}, %{"documentation" => "For region us-iso-east-1 with FIPS disabled and DualStack enabled", "expect" => %{"error" => "DualStack is enabled but this partition does not support DualStack"}, "params" => %{"Region" => "us-iso-east-1", "UseDualStack" => true, "UseFIPS" => false}}, %{"documentation" => "For region us-iso-east-1 with FIPS disabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.us-iso-east-1.c2s.ic.gov"}}, "params" => %{"Region" => "us-iso-east-1", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For region us-isob-east-1 with FIPS enabled and DualStack enabled", "expect" => %{"error" => "FIPS and DualStack are enabled, but this partition does not support one or both"}, "params" => %{"Region" => "us-isob-east-1", "UseDualStack" => true, "UseFIPS" => true}}, %{"documentation" => "For region us-isob-east-1 with FIPS enabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data-fips.us-isob-east-1.sc2s.sgov.gov"}}, "params" => %{"Region" => "us-isob-east-1", "UseDualStack" => false, "UseFIPS" => true}}, %{"documentation" => "For region us-isob-east-1 with FIPS disabled and DualStack enabled", "expect" => %{"error" => "DualStack is enabled but this partition does not support DualStack"}, "params" => %{"Region" => "us-isob-east-1", "UseDualStack" => true, "UseFIPS" => false}}, %{"documentation" => "For region us-isob-east-1 with FIPS disabled and DualStack disabled", "expect" => %{"endpoint" => %{"url" => "https://cloudtrail-data.us-isob-east-1.sc2s.sgov.gov"}}, "params" => %{"Region" => "us-isob-east-1", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For custom endpoint with region set and fips disabled and dualstack disabled", "expect" => %{"endpoint" => %{"url" => "https://example.com"}}, "params" => %{"Endpoint" => "https://example.com", "Region" => "us-east-1", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For custom endpoint with region not set and fips disabled and dualstack disabled", "expect" => %{"endpoint" => %{"url" => "https://example.com"}}, "params" => %{"Endpoint" => "https://example.com", "UseDualStack" => false, "UseFIPS" => false}}, %{"documentation" => "For custom endpoint with fips enabled and dualstack disabled", "expect" => %{"error" => "Invalid Configuration: FIPS and custom endpoint are not supported"}, "params" => %{"Endpoint" => "https://example.com", "Region" => "us-east-1", "UseDualStack" => false, "UseFIPS" => true}}, %{"documentation" => "For custom endpoint with fips disabled and dualstack enabled", "expect" => %{"error" => "Invalid Configuration: Dualstack and custom endpoint are not supported"}, "params" => %{"Endpoint" => "https://example.com", "Region" => "us-east-1", "UseDualStack" => true, "UseFIPS" => false}}, %{"documentation" => "Missing region", "expect" => %{"error" => "Invalid Configuration: Missing Region"}}], "version" => "1.0"}}, "type" => "service", "version" => "2021-08-11"}, - "com.amazonaws.cloudtraildata#DuplicatedAuditEventId" => %{"members" => %{"message" => %{"target" => "smithy.api#String"}}, "traits" => %{"smithy.api#documentation" => "

Two or more entries in the request have the same event ID.

", "smithy.api#error" => "client"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#ErrorCode" => %{"traits" => %{"smithy.api#length" => %{"max" => 128, "min" => 1}}, "type" => "string"}, - "com.amazonaws.cloudtraildata#ErrorMessage" => %{"traits" => %{"smithy.api#length" => %{"max" => 1024, "min" => 1}}, "type" => "string"}, - "com.amazonaws.cloudtraildata#ExternalId" => %{"traits" => %{"smithy.api#length" => %{"max" => 1224, "min" => 2}, "smithy.api#pattern" => "^[\\w+=,.@:\\/-]*$"}, "type" => "string"}, - "com.amazonaws.cloudtraildata#InvalidChannelARN" => %{"members" => %{"message" => %{"target" => "smithy.api#String"}}, "traits" => %{"smithy.api#documentation" => "

The specified channel ARN is not a valid \n channel ARN.

", "smithy.api#error" => "client"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#PutAuditEvents" => %{"errors" => [%{"target" => "com.amazonaws.cloudtraildata#ChannelInsufficientPermission"}, %{"target" => "com.amazonaws.cloudtraildata#ChannelNotFound"}, %{"target" => "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema"}, %{"target" => "com.amazonaws.cloudtraildata#DuplicatedAuditEventId"}, %{"target" => "com.amazonaws.cloudtraildata#InvalidChannelARN"}, %{"target" => "com.amazonaws.cloudtraildata#UnsupportedOperationException"}], "input" => %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsRequest"}, "output" => %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsResponse"}, "traits" => %{"smithy.api#documentation" => "

Ingests your application events into CloudTrail Lake. A required parameter,\n auditEvents, accepts the JSON records (also called\n payload) of events that you want CloudTrail to ingest. You\n can add up to 100 of these events (or up to 1 MB) per PutAuditEvents\n request.

", "smithy.api#http" => %{"method" => "POST", "uri" => "/PutAuditEvents"}}, "type" => "operation"}, - "com.amazonaws.cloudtraildata#PutAuditEventsRequest" => %{"members" => %{"auditEvents" => %{"target" => "com.amazonaws.cloudtraildata#AuditEvents", "traits" => %{"smithy.api#documentation" => "

The JSON payload of events that you want to ingest. You can also point to the JSON event\n payload in a file.

", "smithy.api#required" => %{}}}, "channelArn" => %{"target" => "com.amazonaws.cloudtraildata#ChannelArn", "traits" => %{"smithy.api#documentation" => "

The ARN or ID (the ARN suffix) of a channel.

", "smithy.api#httpQuery" => "channelArn", "smithy.api#required" => %{}}}, "externalId" => %{"target" => "com.amazonaws.cloudtraildata#ExternalId", "traits" => %{"smithy.api#documentation" => "

A unique identifier that is conditionally required when the channel's resource policy includes an external \n ID. This value can be any string, \n such as a passphrase or account number.

", "smithy.api#httpQuery" => "externalId"}}}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#PutAuditEventsResponse" => %{"members" => %{"failed" => %{"target" => "com.amazonaws.cloudtraildata#ResultErrorEntries", "traits" => %{"smithy.api#documentation" => "

Lists events in the provided event payload that could not be \n ingested into CloudTrail, and includes the error code and error message \n returned for events that could not be ingested.

", "smithy.api#required" => %{}}}, "successful" => %{"target" => "com.amazonaws.cloudtraildata#AuditEventResultEntries", "traits" => %{"smithy.api#documentation" => "

Lists events in the provided event payload that were successfully ingested \n into CloudTrail.

", "smithy.api#required" => %{}}}}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#ResultErrorEntries" => %{"member" => %{"target" => "com.amazonaws.cloudtraildata#ResultErrorEntry"}, "traits" => %{"smithy.api#length" => %{"max" => 100, "min" => 0}}, "type" => "list"}, - "com.amazonaws.cloudtraildata#ResultErrorEntry" => %{"members" => %{"errorCode" => %{"target" => "com.amazonaws.cloudtraildata#ErrorCode", "traits" => %{"smithy.api#documentation" => "

The error code for events that could not be ingested by CloudTrail. Possible error codes include: FieldTooLong, FieldNotFound, \n InvalidChecksum, InvalidData, InvalidRecipient, InvalidEventSource, AccountNotSubscribed, \n Throttling, and InternalFailure.

", "smithy.api#required" => %{}}}, "errorMessage" => %{"target" => "com.amazonaws.cloudtraildata#ErrorMessage", "traits" => %{"smithy.api#documentation" => "

The message that describes the error for events that could not be ingested by CloudTrail.

", "smithy.api#required" => %{}}}, "id" => %{"target" => "com.amazonaws.cloudtraildata#Uuid", "traits" => %{"smithy.api#documentation" => "

The original event ID from the source event that could not be ingested by CloudTrail.

", "smithy.api#required" => %{}}}}, "traits" => %{"smithy.api#documentation" => "

Includes the error code and error message for events that could not be ingested by CloudTrail.

"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#UnsupportedOperationException" => %{"members" => %{"message" => %{"target" => "smithy.api#String"}}, "traits" => %{"smithy.api#documentation" => "

The operation requested is not supported in this region or account.

", "smithy.api#error" => "client"}, "type" => "structure"}, - "com.amazonaws.cloudtraildata#Uuid" => %{"traits" => %{"smithy.api#length" => %{"max" => 128, "min" => 1}, "smithy.api#pattern" => "^[-_A-Za-z0-9]+$"}, "type" => "string"} - }, - "smithy" => "2.0" - } + %{ + "shapes" => %{ + "com.amazonaws.cloudtraildata#AuditEvent" => %{ + "members" => %{ + "eventData" => %{ + "target" => "smithy.api#String", + "traits" => %{ + "smithy.api#documentation" => + "

The content of an audit event that comes from the event, such as userIdentity, \n userAgent, and eventSource.

", + "smithy.api#required" => %{} + } + }, + "eventDataChecksum" => %{ + "target" => "smithy.api#String", + "traits" => %{ + "smithy.api#documentation" => + "

A checksum is a base64-SHA256 algorithm that helps you verify that CloudTrail receives the event that matches \n with the checksum. Calculate the checksum by running a command like the following:

\n

\n printf %s $eventdata | openssl dgst -binary -sha256 | base64\n

" + } + }, + "id" => %{ + "target" => "com.amazonaws.cloudtraildata#Uuid", + "traits" => %{ + "smithy.api#documentation" => + "

The original event ID from the source event.

", + "smithy.api#required" => %{} + } + } + }, + "traits" => %{ + "smithy.api#documentation" => + "

An event from a source outside of Amazon Web Services that you want CloudTrail \n to log.

" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#AuditEventResultEntries" => %{ + "member" => %{"target" => "com.amazonaws.cloudtraildata#AuditEventResultEntry"}, + "traits" => %{"smithy.api#length" => %{"max" => 100, "min" => 0}}, + "type" => "list" + }, + "com.amazonaws.cloudtraildata#AuditEventResultEntry" => %{ + "members" => %{ + "eventID" => %{ + "target" => "com.amazonaws.cloudtraildata#Uuid", + "traits" => %{ + "smithy.api#documentation" => + "

The event ID assigned by CloudTrail.

", + "smithy.api#required" => %{} + } + }, + "id" => %{ + "target" => "com.amazonaws.cloudtraildata#Uuid", + "traits" => %{ + "smithy.api#documentation" => + "

The original event ID from the source event.

", + "smithy.api#required" => %{} + } + } + }, + "traits" => %{ + "smithy.api#documentation" => + "

A response that includes successful and failed event results.

" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#AuditEvents" => %{ + "member" => %{"target" => "com.amazonaws.cloudtraildata#AuditEvent"}, + "traits" => %{"smithy.api#length" => %{"max" => 100, "min" => 1}}, + "type" => "list" + }, + "com.amazonaws.cloudtraildata#ChannelArn" => %{ + "traits" => %{ + "aws.api#arnReference" => %{}, + "smithy.api#pattern" => "^arn:.*$" + }, + "type" => "string" + }, + "com.amazonaws.cloudtraildata#ChannelInsufficientPermission" => %{ + "members" => %{"message" => %{"target" => "smithy.api#String"}}, + "traits" => %{ + "smithy.api#documentation" => + "

The caller's account ID must be the same as the channel owner's account ID.

", + "smithy.api#error" => "client" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#ChannelNotFound" => %{ + "members" => %{"message" => %{"target" => "smithy.api#String"}}, + "traits" => %{ + "smithy.api#documentation" => "

The channel could not be found.

", + "smithy.api#error" => "client" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema" => %{ + "members" => %{"message" => %{"target" => "smithy.api#String"}}, + "traits" => %{ + "smithy.api#documentation" => + "

The schema type of the event is not supported.

", + "smithy.api#error" => "client" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#CloudTrailDataService" => %{ + "operations" => [%{"target" => "com.amazonaws.cloudtraildata#PutAuditEvents"}], + "traits" => %{ + "aws.api#service" => %{ + "endpointPrefix" => "cloudtrail-data", + "sdkId" => "CloudTrail Data" + }, + "aws.auth#sigv4" => %{"name" => "cloudtrail-data"}, + "aws.protocols#restJson1" => %{}, + "smithy.api#cors" => %{}, + "smithy.api#documentation" => + "

The CloudTrail Data Service lets you ingest events into CloudTrail from any source in your\nhybrid environments, such as in-house or SaaS applications hosted on-premises or in the cloud,\nvirtual machines, or containers. You can store, access, analyze, troubleshoot and take action on\nthis data without maintaining multiple log aggregators and reporting tools. After you run \nPutAuditEvents to ingest your application activity into CloudTrail, you can use CloudTrail Lake to search, query, and analyze the data that is logged\nfrom your applications.

", + "smithy.api#title" => "AWS CloudTrail Data Service", + "smithy.rules#endpointRuleSet" => %{ + "parameters" => %{ + "Endpoint" => %{ + "builtIn" => "SDK::Endpoint", + "documentation" => "Override the endpoint used to send this request", + "required" => false, + "type" => "String" + }, + "Region" => %{ + "builtIn" => "AWS::Region", + "documentation" => "The AWS region used to dispatch the request.", + "required" => false, + "type" => "String" + }, + "UseDualStack" => %{ + "builtIn" => "AWS::UseDualStack", + "default" => false, + "documentation" => + "When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.", + "required" => true, + "type" => "Boolean" + }, + "UseFIPS" => %{ + "builtIn" => "AWS::UseFIPS", + "default" => false, + "documentation" => + "When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.", + "required" => true, + "type" => "Boolean" + } + }, + "rules" => [ + %{ + "conditions" => [ + %{"argv" => [%{"ref" => "Endpoint"}], "fn" => "isSet"} + ], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [%{"ref" => "UseFIPS"}, true], + "fn" => "booleanEquals" + } + ], + "error" => + "Invalid Configuration: FIPS and custom endpoint are not supported", + "type" => "error" + }, + %{ + "conditions" => [], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [%{"ref" => "UseDualStack"}, true], + "fn" => "booleanEquals" + } + ], + "error" => + "Invalid Configuration: Dualstack and custom endpoint are not supported", + "type" => "error" + }, + %{ + "conditions" => [], + "endpoint" => %{ + "headers" => %{}, + "properties" => %{}, + "url" => %{"ref" => "Endpoint"} + }, + "type" => "endpoint" + } + ], + "type" => "tree" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [], + "rules" => [ + %{ + "conditions" => [ + %{"argv" => [%{"ref" => "Region"}], "fn" => "isSet"} + ], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [%{"ref" => "Region"}], + "assign" => "PartitionResult", + "fn" => "aws.partition" + } + ], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [%{"ref" => "UseFIPS"}, true], + "fn" => "booleanEquals" + }, + %{ + "argv" => [%{"ref" => "UseDualStack"}, true], + "fn" => "booleanEquals" + } + ], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [ + true, + %{ + "argv" => [ + %{"ref" => "PartitionResult"}, + "supportsFIPS" + ], + "fn" => "getAttr" + } + ], + "fn" => "booleanEquals" + }, + %{ + "argv" => [ + true, + %{ + "argv" => [ + %{"ref" => "PartitionResult"}, + "supportsDualStack" + ], + "fn" => "getAttr" + } + ], + "fn" => "booleanEquals" + } + ], + "rules" => [ + %{ + "conditions" => [], + "rules" => [ + %{ + "conditions" => [], + "endpoint" => %{ + "headers" => %{}, + "properties" => %{}, + "url" => + "https://cloudtrail-data-fips.{Region}.{PartitionResult#dualStackDnsSuffix}" + }, + "type" => "endpoint" + } + ], + "type" => "tree" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [], + "error" => + "FIPS and DualStack are enabled, but this partition does not support one or both", + "type" => "error" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [ + %{ + "argv" => [%{"ref" => "UseFIPS"}, true], + "fn" => "booleanEquals" + } + ], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [ + true, + %{ + "argv" => [ + %{"ref" => "PartitionResult"}, + "supportsFIPS" + ], + "fn" => "getAttr" + } + ], + "fn" => "booleanEquals" + } + ], + "rules" => [ + %{ + "conditions" => [], + "rules" => [ + %{ + "conditions" => [], + "endpoint" => %{ + "headers" => %{}, + "properties" => %{}, + "url" => + "https://cloudtrail-data-fips.{Region}.{PartitionResult#dnsSuffix}" + }, + "type" => "endpoint" + } + ], + "type" => "tree" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [], + "error" => + "FIPS is enabled but this partition does not support FIPS", + "type" => "error" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [ + %{ + "argv" => [%{"ref" => "UseDualStack"}, true], + "fn" => "booleanEquals" + } + ], + "rules" => [ + %{ + "conditions" => [ + %{ + "argv" => [ + true, + %{ + "argv" => [ + %{"ref" => "PartitionResult"}, + "supportsDualStack" + ], + "fn" => "getAttr" + } + ], + "fn" => "booleanEquals" + } + ], + "rules" => [ + %{ + "conditions" => [], + "rules" => [ + %{ + "conditions" => [], + "endpoint" => %{ + "headers" => %{}, + "properties" => %{}, + "url" => + "https://cloudtrail-data.{Region}.{PartitionResult#dualStackDnsSuffix}" + }, + "type" => "endpoint" + } + ], + "type" => "tree" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [], + "error" => + "DualStack is enabled but this partition does not support DualStack", + "type" => "error" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [], + "rules" => [ + %{ + "conditions" => [], + "endpoint" => %{ + "headers" => %{}, + "properties" => %{}, + "url" => + "https://cloudtrail-data.{Region}.{PartitionResult#dnsSuffix}" + }, + "type" => "endpoint" + } + ], + "type" => "tree" + } + ], + "type" => "tree" + } + ], + "type" => "tree" + }, + %{ + "conditions" => [], + "error" => "Invalid Configuration: Missing Region", + "type" => "error" + } + ], + "type" => "tree" + } + ], + "version" => "1.0" + }, + "smithy.rules#endpointTests" => %{ + "testCases" => [ + %{ + "documentation" => + "For region us-east-1 with FIPS enabled and DualStack enabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data-fips.us-east-1.api.aws" + } + }, + "params" => %{ + "Region" => "us-east-1", + "UseDualStack" => true, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-east-1 with FIPS enabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data-fips.us-east-1.amazonaws.com" + } + }, + "params" => %{ + "Region" => "us-east-1", + "UseDualStack" => false, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-east-1 with FIPS disabled and DualStack enabled", + "expect" => %{ + "endpoint" => %{"url" => "https://cloudtrail-data.us-east-1.api.aws"} + }, + "params" => %{ + "Region" => "us-east-1", + "UseDualStack" => true, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-east-1 with FIPS disabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data.us-east-1.amazonaws.com" + } + }, + "params" => %{ + "Region" => "us-east-1", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region cn-north-1 with FIPS enabled and DualStack enabled", + "expect" => %{ + "endpoint" => %{ + "url" => + "https://cloudtrail-data-fips.cn-north-1.api.amazonwebservices.com.cn" + } + }, + "params" => %{ + "Region" => "cn-north-1", + "UseDualStack" => true, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region cn-north-1 with FIPS enabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data-fips.cn-north-1.amazonaws.com.cn" + } + }, + "params" => %{ + "Region" => "cn-north-1", + "UseDualStack" => false, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region cn-north-1 with FIPS disabled and DualStack enabled", + "expect" => %{ + "endpoint" => %{ + "url" => + "https://cloudtrail-data.cn-north-1.api.amazonwebservices.com.cn" + } + }, + "params" => %{ + "Region" => "cn-north-1", + "UseDualStack" => true, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region cn-north-1 with FIPS disabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data.cn-north-1.amazonaws.com.cn" + } + }, + "params" => %{ + "Region" => "cn-north-1", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-gov-east-1 with FIPS enabled and DualStack enabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data-fips.us-gov-east-1.api.aws" + } + }, + "params" => %{ + "Region" => "us-gov-east-1", + "UseDualStack" => true, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-gov-east-1 with FIPS enabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data-fips.us-gov-east-1.amazonaws.com" + } + }, + "params" => %{ + "Region" => "us-gov-east-1", + "UseDualStack" => false, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-gov-east-1 with FIPS disabled and DualStack enabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data.us-gov-east-1.api.aws" + } + }, + "params" => %{ + "Region" => "us-gov-east-1", + "UseDualStack" => true, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-gov-east-1 with FIPS disabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data.us-gov-east-1.amazonaws.com" + } + }, + "params" => %{ + "Region" => "us-gov-east-1", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-iso-east-1 with FIPS enabled and DualStack enabled", + "expect" => %{ + "error" => + "FIPS and DualStack are enabled, but this partition does not support one or both" + }, + "params" => %{ + "Region" => "us-iso-east-1", + "UseDualStack" => true, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-iso-east-1 with FIPS enabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data-fips.us-iso-east-1.c2s.ic.gov" + } + }, + "params" => %{ + "Region" => "us-iso-east-1", + "UseDualStack" => false, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-iso-east-1 with FIPS disabled and DualStack enabled", + "expect" => %{ + "error" => + "DualStack is enabled but this partition does not support DualStack" + }, + "params" => %{ + "Region" => "us-iso-east-1", + "UseDualStack" => true, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-iso-east-1 with FIPS disabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data.us-iso-east-1.c2s.ic.gov" + } + }, + "params" => %{ + "Region" => "us-iso-east-1", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-isob-east-1 with FIPS enabled and DualStack enabled", + "expect" => %{ + "error" => + "FIPS and DualStack are enabled, but this partition does not support one or both" + }, + "params" => %{ + "Region" => "us-isob-east-1", + "UseDualStack" => true, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-isob-east-1 with FIPS enabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => + "https://cloudtrail-data-fips.us-isob-east-1.sc2s.sgov.gov" + } + }, + "params" => %{ + "Region" => "us-isob-east-1", + "UseDualStack" => false, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For region us-isob-east-1 with FIPS disabled and DualStack enabled", + "expect" => %{ + "error" => + "DualStack is enabled but this partition does not support DualStack" + }, + "params" => %{ + "Region" => "us-isob-east-1", + "UseDualStack" => true, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For region us-isob-east-1 with FIPS disabled and DualStack disabled", + "expect" => %{ + "endpoint" => %{ + "url" => "https://cloudtrail-data.us-isob-east-1.sc2s.sgov.gov" + } + }, + "params" => %{ + "Region" => "us-isob-east-1", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For custom endpoint with region set and fips disabled and dualstack disabled", + "expect" => %{"endpoint" => %{"url" => "https://example.com"}}, + "params" => %{ + "Endpoint" => "https://example.com", + "Region" => "us-east-1", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For custom endpoint with region not set and fips disabled and dualstack disabled", + "expect" => %{"endpoint" => %{"url" => "https://example.com"}}, + "params" => %{ + "Endpoint" => "https://example.com", + "UseDualStack" => false, + "UseFIPS" => false + } + }, + %{ + "documentation" => + "For custom endpoint with fips enabled and dualstack disabled", + "expect" => %{ + "error" => + "Invalid Configuration: FIPS and custom endpoint are not supported" + }, + "params" => %{ + "Endpoint" => "https://example.com", + "Region" => "us-east-1", + "UseDualStack" => false, + "UseFIPS" => true + } + }, + %{ + "documentation" => + "For custom endpoint with fips disabled and dualstack enabled", + "expect" => %{ + "error" => + "Invalid Configuration: Dualstack and custom endpoint are not supported" + }, + "params" => %{ + "Endpoint" => "https://example.com", + "Region" => "us-east-1", + "UseDualStack" => true, + "UseFIPS" => false + } + }, + %{ + "documentation" => "Missing region", + "expect" => %{"error" => "Invalid Configuration: Missing Region"} + } + ], + "version" => "1.0" + } + }, + "type" => "service", + "version" => "2021-08-11" + }, + "com.amazonaws.cloudtraildata#DuplicatedAuditEventId" => %{ + "members" => %{"message" => %{"target" => "smithy.api#String"}}, + "traits" => %{ + "smithy.api#documentation" => + "

Two or more entries in the request have the same event ID.

", + "smithy.api#error" => "client" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#ErrorCode" => %{ + "traits" => %{"smithy.api#length" => %{"max" => 128, "min" => 1}}, + "type" => "string" + }, + "com.amazonaws.cloudtraildata#ErrorMessage" => %{ + "traits" => %{"smithy.api#length" => %{"max" => 1024, "min" => 1}}, + "type" => "string" + }, + "com.amazonaws.cloudtraildata#ExternalId" => %{ + "traits" => %{ + "smithy.api#length" => %{"max" => 1224, "min" => 2}, + "smithy.api#pattern" => "^[\\w+=,.@:\\/-]*$" + }, + "type" => "string" + }, + "com.amazonaws.cloudtraildata#InvalidChannelARN" => %{ + "members" => %{"message" => %{"target" => "smithy.api#String"}}, + "traits" => %{ + "smithy.api#documentation" => + "

The specified channel ARN is not a valid \n channel ARN.

", + "smithy.api#error" => "client" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#PutAuditEvents" => %{ + "errors" => [ + %{"target" => "com.amazonaws.cloudtraildata#ChannelInsufficientPermission"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelNotFound"}, + %{"target" => "com.amazonaws.cloudtraildata#ChannelUnsupportedSchema"}, + %{"target" => "com.amazonaws.cloudtraildata#DuplicatedAuditEventId"}, + %{"target" => "com.amazonaws.cloudtraildata#InvalidChannelARN"}, + %{"target" => "com.amazonaws.cloudtraildata#UnsupportedOperationException"} + ], + "input" => %{"target" => "com.amazonaws.cloudtraildata#PutAuditEventsRequest"}, + "output" => %{ + "target" => "com.amazonaws.cloudtraildata#PutAuditEventsResponse" + }, + "traits" => %{ + "smithy.api#documentation" => + "

Ingests your application events into CloudTrail Lake. A required parameter,\n auditEvents, accepts the JSON records (also called\n payload) of events that you want CloudTrail to ingest. You\n can add up to 100 of these events (or up to 1 MB) per PutAuditEvents\n request.

", + "smithy.api#http" => %{"method" => "POST", "uri" => "/PutAuditEvents"} + }, + "type" => "operation" + }, + "com.amazonaws.cloudtraildata#PutAuditEventsRequest" => %{ + "members" => %{ + "auditEvents" => %{ + "target" => "com.amazonaws.cloudtraildata#AuditEvents", + "traits" => %{ + "smithy.api#documentation" => + "

The JSON payload of events that you want to ingest. You can also point to the JSON event\n payload in a file.

", + "smithy.api#required" => %{} + } + }, + "channelArn" => %{ + "target" => "com.amazonaws.cloudtraildata#ChannelArn", + "traits" => %{ + "smithy.api#documentation" => + "

The ARN or ID (the ARN suffix) of a channel.

", + "smithy.api#httpQuery" => "channelArn", + "smithy.api#required" => %{} + } + }, + "externalId" => %{ + "target" => "com.amazonaws.cloudtraildata#ExternalId", + "traits" => %{ + "smithy.api#documentation" => + "

A unique identifier that is conditionally required when the channel's resource policy includes an external \n ID. This value can be any string, \n such as a passphrase or account number.

", + "smithy.api#httpQuery" => "externalId" + } + } + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#PutAuditEventsResponse" => %{ + "members" => %{ + "failed" => %{ + "target" => "com.amazonaws.cloudtraildata#ResultErrorEntries", + "traits" => %{ + "smithy.api#documentation" => + "

Lists events in the provided event payload that could not be \n ingested into CloudTrail, and includes the error code and error message \n returned for events that could not be ingested.

", + "smithy.api#required" => %{} + } + }, + "successful" => %{ + "target" => "com.amazonaws.cloudtraildata#AuditEventResultEntries", + "traits" => %{ + "smithy.api#documentation" => + "

Lists events in the provided event payload that were successfully ingested \n into CloudTrail.

", + "smithy.api#required" => %{} + } + } + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#ResultErrorEntries" => %{ + "member" => %{"target" => "com.amazonaws.cloudtraildata#ResultErrorEntry"}, + "traits" => %{"smithy.api#length" => %{"max" => 100, "min" => 0}}, + "type" => "list" + }, + "com.amazonaws.cloudtraildata#ResultErrorEntry" => %{ + "members" => %{ + "errorCode" => %{ + "target" => "com.amazonaws.cloudtraildata#ErrorCode", + "traits" => %{ + "smithy.api#documentation" => + "

The error code for events that could not be ingested by CloudTrail. Possible error codes include: FieldTooLong, FieldNotFound, \n InvalidChecksum, InvalidData, InvalidRecipient, InvalidEventSource, AccountNotSubscribed, \n Throttling, and InternalFailure.

", + "smithy.api#required" => %{} + } + }, + "errorMessage" => %{ + "target" => "com.amazonaws.cloudtraildata#ErrorMessage", + "traits" => %{ + "smithy.api#documentation" => + "

The message that describes the error for events that could not be ingested by CloudTrail.

", + "smithy.api#required" => %{} + } + }, + "id" => %{ + "target" => "com.amazonaws.cloudtraildata#Uuid", + "traits" => %{ + "smithy.api#documentation" => + "

The original event ID from the source event that could not be ingested by CloudTrail.

", + "smithy.api#required" => %{} + } + } + }, + "traits" => %{ + "smithy.api#documentation" => + "

Includes the error code and error message for events that could not be ingested by CloudTrail.

" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#UnsupportedOperationException" => %{ + "members" => %{"message" => %{"target" => "smithy.api#String"}}, + "traits" => %{ + "smithy.api#documentation" => + "

The operation requested is not supported in this region or account.

", + "smithy.api#error" => "client" + }, + "type" => "structure" + }, + "com.amazonaws.cloudtraildata#Uuid" => %{ + "traits" => %{ + "smithy.api#length" => %{"max" => 128, "min" => 1}, + "smithy.api#pattern" => "^[-_A-Za-z0-9]+$" + }, + "type" => "string" + } + }, + "smithy" => "2.0" + } end end