diff --git a/lib/aws_codegen.ex b/lib/aws_codegen.ex index a614383..858dccf 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 @@ -107,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}") @@ -149,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 c32e4bd..576ea3e 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,17 +61,24 @@ defmodule AWS.CodeGen.PostService do service = spec.api["shapes"][spec.shape_name] traits = service["traits"] actions = collect_actions(language, spec.api) - endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"] + 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) + 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.", "") @@ -89,6 +100,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), @@ -102,7 +114,9 @@ defmodule AWS.CodeGen.PostService do |> case do {key, _} -> String.replace(key, ~r/.*#/, "") - nil -> nil + + nil -> + nil end end @@ -113,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 @@ -128,6 +152,7 @@ defmodule AWS.CodeGen.PostService do Enum.map(operations, fn operation -> operation_spec = shapes[operation] + %Action{ arity: 3, docstring: @@ -137,7 +162,10 @@ 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) diff --git a/lib/aws_codegen/rest_service.ex b/lib/aws_codegen/rest_service.ex index 1b91d69..cfe723e 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() @@ -117,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) @@ -145,7 +151,9 @@ 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 @@ -163,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 @@ -210,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 @@ -253,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: @@ -275,7 +295,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) @@ -291,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 @@ -311,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} -> @@ -349,6 +381,7 @@ defmodule AWS.CodeGen.RestService do required: required } end + defp build_parameter(language, {name, data}, required) do %Parameter{ code_name: @@ -362,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 946f9cc..96d6af4 100644 --- a/lib/aws_codegen/shapes.ex +++ b/lib/aws_codegen/shapes.ex @@ -1,5 +1,14 @@ 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,10 +18,27 @@ 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`? 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"] @@ -23,4 +49,7 @@ 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/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 new file mode 100644 index 0000000..dba1bb9 --- /dev/null +++ b/lib/aws_codegen/types.ex @@ -0,0 +1,324 @@ +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..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 @@ -30,4 +34,32 @@ 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..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,36 +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", 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
.
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
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.
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.
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
.
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
.
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
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.
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.
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
.
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 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,