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 #108

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion 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 Down
12 changes: 11 additions & 1 deletion 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,6 +61,7 @@ defmodule AWS.CodeGen.PostService do
service = spec.api["shapes"][spec.shape_name]
traits = service["traits"]
actions = collect_actions(language, spec.api)
shapes = Shapes.collect_shapes(language, spec.api)
endpoint_prefix = traits["aws.api#service"]["endpointPrefix"] || traits["aws.api#service"]["arnNamespace"]
endpoint_info = endpoints_spec["services"][endpoint_prefix]
is_global = not is_nil(endpoint_info) and not Map.get(endpoint_info, "isRegionalized", true)
Expand Down Expand Up @@ -89,6 +94,7 @@ defmodule AWS.CodeGen.PostService do
language: language,
module_name: spec.module_name,
protocol: protocol |> to_string() |> String.replace("_", "-"),
shapes: shapes,
signing_name: signing_name,
signature_version: AWS.CodeGen.Util.get_signature_version(service),
service_id: AWS.CodeGen.Util.get_service_id(service),
Expand Down Expand Up @@ -137,10 +143,14 @@ defmodule AWS.CodeGen.PostService do
),
function_name: AWS.CodeGen.Name.to_snake_case(operation),
host_prefix: operation_spec["traits"]["smithy.api#endpoint"]["hostPrefix"],
name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, "")
name: String.replace(operation, ~r/com\.amazonaws\.[^#]+#/, ""),
input: operation_spec["input"],
output: operation_spec["output"],
errors: operation_spec["errors"]
}
end)
|> Enum.sort(fn a, b -> a.function_name < b.function_name end)
|> Enum.uniq()
end

end
61 changes: 58 additions & 3 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 @@ -145,7 +148,8 @@ defmodule AWS.CodeGen.RestService do
signing_name: signing_name,
signature_version: AWS.CodeGen.Util.get_signature_version(service),
service_id: AWS.CodeGen.Util.get_service_id(service),
target_prefix: nil, ##TODO: metadata["targetPrefix"]
target_prefix: nil, ##TODO: metadata["targetPrefix"],
shapes: Shapes.collect_shapes(language, spec.api)
}
end

Expand All @@ -157,6 +161,54 @@ defmodule AWS.CodeGen.RestService do
function_parameters(action, true)
end

def required_function_parameter_types(action) do
function_parameter_types(action.method, action, true)
end

def required_query_map_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.join(
Enum.map(
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

@doc """
Render function parameters, if any, in a way that can be inserted directly
into the code template. It can be asked to only return the required ones.
Expand Down Expand Up @@ -275,7 +327,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 Down
33 changes: 32 additions & 1 deletion lib/aws_codegen/shapes.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
defmodule AWS.CodeGen.Shapes do
@moduledoc false

defmodule Shape do
defstruct name: nil,
type: nil,
members: [],
member: [],
enum: [],
min: nil,
required: [],
is_input: nil
end

def get_input_shape(operation_spec) do
get_in(operation_spec, ["input", "target"])
Expand All @@ -9,6 +19,23 @@ defmodule AWS.CodeGen.Shapes do
get_in(operation_spec, ["output", "target"])
end

def collect_shapes(_language, api_spec) do
api_spec["shapes"]
|> Enum.sort(fn {name_a, _}, {name_b, _} -> name_a < name_b end)
|> 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`?
Expand All @@ -23,4 +50,8 @@ defmodule AWS.CodeGen.Shapes do
end
end

def is_input?(shape) do
!Map.has_key?(shape, "traits") or Map.has_key?(shape["traits"], "smithy.api#input")
end

end
96 changes: 96 additions & 0 deletions lib/aws_codegen/types.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
defmodule AWS.CodeGen.Types do

alias AWS.CodeGen.Shapes.Shape

# Unfortunately, gotta patch over auto-defining types that already exist in Elixir

def shape_to_type(:elixir, "String", _), do: "String.t()"
def shape_to_type(:erlang, "String", _), do: "string()"
def shape_to_type(:elixir, "string", _), do: "String.t()"
def shape_to_type(:erlang, "string", _), do: "string()"
def shape_to_type(:elixir, "Identifier", _), do: "String.t()"
def shape_to_type(:erlang, "Identifier", _), do: "string()"
def shape_to_type(:elixir, "identifier", _), do: "String.t()"
def shape_to_type(:erlang, "identifier", _), do: "string()"
def shape_to_type(:elixir, "XmlString" <> _rest, _), do: "String.t()"
def shape_to_type(:erlang, "XmlString" <> _rest, _), do: "string"
def shape_to_type(:elixir, "NullablePositiveInteger", _), do: "nil | non_neg_integer()"
def shape_to_type(:erlang, "NullablePositiveInteger", _), do: "undefined | non_neg_integer()"
def shape_to_type(_, %Shape{type: type}, _module_name) when type in ["float", "double", "long"], do: "float()"
def shape_to_type(_, %Shape{type: "timestamp"}, _module_name), do: "non_neg_integer()"
def shape_to_type(_, %Shape{type: "map"}, _module_name), do: "map()"
def shape_to_type(_, %Shape{type: "blob"}, _module_name), do: "binary()"
def shape_to_type(:elixir, %Shape{type: "string"}, _module_name), do: "String.t()"
def shape_to_type(:erlang, %Shape{type: "string"}, _module_name), do: "string()"
def shape_to_type(_, %Shape{type: "integer"}, _module_name), do: "integer()"
def shape_to_type(_, %Shape{type: "boolean"}, _module_name), do: "boolean()"
def shape_to_type(_, %Shape{type: "enum"}, _module_name), do: "list(any())"
def shape_to_type(_, %Shape{type: "union"}, _module_name), do: "list()"
def shape_to_type(_, %Shape{type: "document"}, _module_name), do: "any()"

def 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 AWS.CodeGen.Util.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 AWS.CodeGen.Util.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
end
Loading
Loading