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
.
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