Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate types and type specs for all generated functions in aws-elixir and aws-erlang #109

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions lib/aws_codegen.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ defmodule AWS.CodeGen do
protocol: nil,
signature_version: nil,
service_id: nil,
shapes: %{},
signing_name: nil,
target_prefix: nil
end
Expand Down Expand Up @@ -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
Expand All @@ -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}")
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions lib/aws_codegen/docstring.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -304,7 +307,7 @@ defmodule AWS.CodeGen.Docstring do

{_, _attrs, children} ->
"#{Floki.text(children)}"
end)
end)
|> Floki.raw_html(encode: true)
end

Expand Down
38 changes: 33 additions & 5 deletions lib/aws_codegen/post_service.ex
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.", "")
Expand All @@ -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),
Expand All @@ -102,7 +114,9 @@ defmodule AWS.CodeGen.PostService do
|> case do
{key, _} ->
String.replace(key, ~r/.*#/, "")
nil -> nil

nil ->
nil
end
end

Expand All @@ -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
Expand All @@ -128,6 +152,7 @@ defmodule AWS.CodeGen.PostService do

Enum.map(operations, fn operation ->
operation_spec = shapes[operation]

%Action{
arity: 3,
docstring:
Expand All @@ -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)
Expand Down
52 changes: 42 additions & 10 deletions lib/aws_codegen/rest_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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} ->
Expand Down Expand Up @@ -349,6 +381,7 @@ defmodule AWS.CodeGen.RestService do
required: required
}
end

defp build_parameter(language, {name, data}, required) do
%Parameter{
code_name:
Expand All @@ -362,5 +395,4 @@ defmodule AWS.CodeGen.RestService do
required: required
}
end

end
31 changes: 30 additions & 1 deletion lib/aws_codegen/shapes.ex
Original file line number Diff line number Diff line change
@@ -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"])
Expand All @@ -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"]
Expand All @@ -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
Loading
Loading