Skip to content

Commit b19b14a

Browse files
committed
Initial work on Erlang rest-json clients.
1 parent 0486bd0 commit b19b14a

File tree

6 files changed

+209
-44
lines changed

6 files changed

+209
-44
lines changed

lib/aws_codegen.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ defmodule AWS.CodeGen do
6060
{:json, "aws_support", "support/2013-04-15", "aws_support.erl"},
6161
{:json, "aws_swf", "swf/2012-01-25", "aws_swf.erl"},
6262
{:json, "aws_workspaces", "workspaces/2015-04-08", "aws_workspaces.erl"},
63+
{:rest_json, "aws_cloud_search_domain", "cloudsearchdomain/2013-01-01", "aws_cloud_search_domain.erl"},
64+
{:rest_json, "aws_cognito_sync", "cognito-sync/2014-06-30", "aws_cognito_sync.erl"},
65+
{:rest_json, "aws_efs", "elasticfilesystem/2015-02-01", "aws_efs.erl"},
66+
{:rest_json, "aws_glacier", "glacier/2012-06-01", "aws_glacier.erl"},
67+
{:rest_json, "aws_lambda", "lambda/2015-03-31", "aws_lambda.erl"},
68+
{:rest_json, "aws_mobile_analytics", "mobileanalytics/2014-06-05", "aws_mobile_analytics.erl"},
69+
{:rest_json, "aws_transcoder", "elastictranscoder/2012-09-25", "aws_transcoder.erl"},
6370
]
6471

6572
def generate(language, spec_base_path, template_base_path, output_base_path) do

lib/aws_codegen/rest_json_service.ex

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ defmodule AWS.CodeGen.RestJSONService do
1313
end
1414

1515
defmodule Action do
16-
defstruct docstring: nil,
16+
defstruct arity: nil,
17+
docstring: nil,
1718
method: nil,
1819
request_uri: nil,
1920
success_status_code: nil,
@@ -23,17 +24,35 @@ defmodule AWS.CodeGen.RestJSONService do
2324
request_header_parameters: [],
2425
response_header_parameters: []
2526

26-
def method(action) do
27+
def method(:elixir, action) do
2728
":#{action.method |> String.downcase |> String.to_atom}"
2829
end
2930

30-
def url(action) do
31+
def method(:erlang, action) do
32+
"#{action.method |> String.downcase |> String.to_atom}"
33+
end
34+
35+
def url(:elixir, action) do
3136
Enum.reduce(action.url_parameters, action.request_uri,
3237
fn(parameter, acc) ->
3338
name = Enum.join([~S(#{), "URI.encode(", parameter.code_name, ")", ~S(})])
3439
String.replace(acc, "{#{parameter.name}}", "#{name}")
3540
end)
3641
end
42+
43+
def url(:erlang, action) do
44+
# Enum.reduce(action.url_parameters, action.request_uri,
45+
# fn(parameter, acc) ->
46+
# [head, tail] = String.split(
47+
# end)
48+
49+
# url = Enum.reduce(action.url_parameters, action.request_uri,
50+
# fn(parameter, acc) ->
51+
# name = Enum.join([~S(#{), "URI.encode(", parameter.code_name, ")", ~S(})])
52+
# String.replace(acc, "{#{parameter.name}}", "#{name}")
53+
# end)
54+
""
55+
end
3756
end
3857

3958
defmodule Parameter do
@@ -64,7 +83,12 @@ defmodule AWS.CodeGen.RestJSONService do
6483
Render function parameter, if any, in a way that can be inserted directly
6584
into the code template.
6685
"""
67-
def function_parameters(action) do
86+
def function_parameters(:elixir, action) do
87+
Enum.join([join_parameters(action.url_parameters),
88+
join_header_parameters(action)])
89+
end
90+
91+
def function_parameters(:erlang, action) do
6892
Enum.join([join_parameters(action.url_parameters),
6993
join_header_parameters(action)])
7094
end
@@ -101,41 +125,43 @@ defmodule AWS.CodeGen.RestJSONService do
101125

102126
defp collect_actions(language, api_spec, doc_spec) do
103127
Enum.map(api_spec["operations"], fn({operation, _metadata}) ->
104-
%Action{docstring: Docstring.format(language,
128+
url_parameters = collect_url_parameters(language, api_spec, operation)
129+
%Action{arity: 3 + length(url_parameters),
130+
docstring: Docstring.format(language,
105131
doc_spec["operations"][operation]),
106132
method: api_spec["operations"][operation]["http"]["method"],
107133
request_uri: api_spec["operations"][operation]["http"]["requestUri"],
108134
success_status_code: api_spec["operations"][operation]["http"]["responseCode"],
109135
function_name: AWS.CodeGen.Name.to_snake_case(operation),
110136
name: operation,
111-
url_parameters: collect_url_parameters(api_spec, operation),
112-
request_header_parameters: collect_request_header_parameters(api_spec, operation),
113-
response_header_parameters: collect_response_header_parameters(api_spec, operation)}
137+
url_parameters: url_parameters,
138+
request_header_parameters: collect_request_header_parameters(language, api_spec, operation),
139+
response_header_parameters: collect_response_header_parameters(language, api_spec, operation)}
114140
end)
115141
|> Enum.sort(fn(a, b) -> a.function_name < b.function_name end)
116142
end
117143

118-
defp collect_url_parameters(api_spec, operation) do
144+
defp collect_url_parameters(language, api_spec, operation) do
119145
shape_name = api_spec["operations"][operation]["input"]["shape"]
120146
shape = api_spec["shapes"][shape_name]
121-
Enum.filter_map(shape["members"], filter_fn("uri"), &build_parameter/1)
147+
Enum.filter_map(shape["members"], filter_fn("uri"), build_parameter_fn(language))
122148
end
123149

124-
defp collect_request_header_parameters(api_spec, operation) do
125-
collect_header_parameters(api_spec, operation, "input")
150+
defp collect_request_header_parameters(language, api_spec, operation) do
151+
collect_header_parameters(language, api_spec, operation, "input")
126152
end
127153

128-
defp collect_response_header_parameters(api_spec, operation) do
129-
collect_header_parameters(api_spec, operation, "output")
154+
defp collect_response_header_parameters(language, api_spec, operation) do
155+
collect_header_parameters(language, api_spec, operation, "output")
130156
end
131157

132-
defp collect_header_parameters(api_spec, operation, data_type) do
158+
defp collect_header_parameters(language, api_spec, operation, data_type) do
133159
shape_name = api_spec["operations"][operation][data_type]["shape"]
134160
shape = api_spec["shapes"][shape_name]
135161
case shape do
136162
nil -> []
137163
^shape -> Enum.filter_map(shape["members"], filter_fn("header"),
138-
&build_parameter/1)
164+
build_parameter_fn(language))
139165
end
140166
end
141167

@@ -148,8 +174,20 @@ defmodule AWS.CodeGen.RestJSONService do
148174
end
149175
end
150176

151-
defp build_parameter({name, _}) do
177+
defp build_parameter_fn(language) do
178+
case language do
179+
:elixir -> &build_elixir_parameter/1
180+
:erlang -> &build_erlang_parameter/1
181+
end
182+
end
183+
184+
defp build_elixir_parameter({name, _}) do
152185
%Parameter{code_name: AWS.CodeGen.Name.to_snake_case(name),
153186
name: name}
154187
end
188+
189+
defp build_erlang_parameter({name, _}) do
190+
%Parameter{code_name: name,
191+
name: name}
192+
end
155193
end

priv/json.erl.eex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
<%= action.function_name %>(Client, Input)
1717
when is_map(Client), is_map(Input) ->
1818
<%= action.function_name %>(Client, Input, []).
19-
<%= action.function_name %>(Client, Input, Options)
20-
when is_map(Client), is_map(Input), is_list(Options) ->
21-
request(Client, <<"<%= action.name %>">>, Input, Options).
19+
<%= action.function_name %>(Client, Input, HTTPOptions)
20+
when is_map(Client), is_map(Input), is_list(HTTPOptions) ->
21+
request(Client, <<"<%= action.name %>">>, Input, HTTPOptions).
2222
<% end %>
2323
%%====================================================================
2424
%% Internal functions
2525
%%====================================================================
2626

27-
request(Client, Action, Input, Options) ->
27+
request(Client, Action, Input, HTTPOptions) ->
2828
Client1 = Client#{service => <<"<%= context.endpoint_prefix %>">>},
2929
Host = aws_util:binary_join([<<"<%= context.endpoint_prefix %>.">>,
3030
maps:get(region, Client1),
@@ -37,7 +37,7 @@ request(Client, Action, Input, Options) ->
3737
{<<"X-Amz-Target">>, << <<"<%= context.target_prefix %>.">>/binary, Action/binary>>}],
3838
Payload = jsx:encode(Input),
3939
Headers1 = aws_request:sign_request(Client1, <<"POST">>, URL, Headers, Payload),
40-
Response = hackney:request(post, URL, Headers1, Payload, Options),
40+
Response = hackney:request(post, URL, Headers1, Payload, HTTPOptions),
4141
handle_response(Response).
4242

4343
handle_response({ok, 200, ResponseHeaders, Client}) ->

priv/json.ex.eex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ defmodule <%= context.module_name %> do
99
@doc """
1010
<%= action.docstring %>
1111
"""
12-
def <%= action.function_name %>(client, input, options \\ []) do
13-
request(client, "<%= action.name %>", input, options)
12+
def <%= action.function_name %>(client, input, http_options \\ []) do
13+
request(client, "<%= action.name %>", input, http_options)
1414
end
1515
<% end %>
16-
defp request(client, action, input, options) do
16+
defp request(client, action, input, http_options) do
1717
client = %{client | service: "<%= context.endpoint_prefix %>"}
1818
host = "<%= context.endpoint_prefix %>.#{client.region}.#{client.endpoint}"
1919
url = "https://#{host}/"
@@ -22,12 +22,12 @@ defmodule <%= context.module_name %> do
2222
{"X-Amz-Target", "<%= context.target_prefix %>.#{action}"}]
2323
payload = Poison.Encoder.encode(input, [])
2424
headers = AWS.Request.sign_v4(client, "POST", url, headers, payload)
25-
case HTTPoison.post(url, payload, headers, options) do
25+
case HTTPoison.post(url, payload, headers, http_options) do
2626
{:ok, response=%HTTPoison.Response{status_code: 200, body: body}} ->
2727
{:ok, Poison.Parser.parse!(body), response}
28-
{:ok, _response=%HTTPoison.Response{body: body}} ->
28+
{:ok, response=%HTTPoison.Response{body: body}} ->
2929
reason = Poison.Parser.parse!(body)["__type"]
30-
{:error, reason}
30+
{:error, reason, response}
3131
{:error, %HTTPoison.Error{reason: reason}} ->
3232
{:error, %HTTPoison.Error{reason: reason}}
3333
end

priv/rest_json.erl.eex

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# WARNING: DO NOT EDIT, AUTO-GENERATED CODE!
2+
# See https://github.com/jkakar/aws-codegen for more details.
3+
4+
<%= if context.docstring != "%% @doc" do %><%= context.docstring %><%= end %>
5+
-module(<%= context.module_name %>).
6+
7+
-export([<%= Enum.map(context.actions, fn(action) -> ["#{action.function_name}/#{action.arity - 1}", "#{action.function_name}/#{action.arity}"] end) |> List.flatten |> Enum.join(",\n ") %>]).
8+
9+
-include_lib("hackney/include/hackney_lib.hrl").
10+
11+
%%====================================================================
12+
%% API
13+
%%====================================================================
14+
<%= for action <- context.actions do %>
15+
<%= action.docstring %><%= if action.method =="GET" do %>
16+
<%= action.function_name %>(Client<%= AWS.CodeGen.RestJSONService.function_parameters(:erlang, action) %>)
17+
when is_map(Client), is_map(Input), is_list(HTTPOptions) ->
18+
<%= action.function_name %>(Client<%= AWS.CodeGen.RestJSONService.function_parameters(:erlang, action) %>, []).
19+
<%= action.function_name %>(Client<%= AWS.CodeGen.RestJSONService.function_parameters(:erlang, action) %>, HTTPOptions)
20+
when is_map(Client), is_map(Input), is_list(HTTPOptions) ->
21+
URL = "<%= AWS.CodeGen.RestJSONService.Action.url(:erlang, action) %>"
22+
Headers = []<%= for parameter <- action.request_header_parameters do %>
23+
if !is_nil(<%= parameter.code_name %>) do
24+
Headers = [{"<%= parameter.name %>", <%= parameter.code_name %>}|headers]
25+
end<%= end %><%= if length(action.response_header_parameters) > 0 do %>
26+
case request(Client, get, URL, Headers, undefined, HTTPOptions, <%= inspect(action.success_status_code) %>) do
27+
{:ok, body, response} -><%= for parameter <- action.response_header_parameters do %>
28+
if !is_nil(response.headers["<%= parameter.name %>"]) do
29+
body = %{body | "<%= parameter.name %>" => response.headers["<%= parameter.name %>"]}
30+
end<%= end %>
31+
{:ok, body, response}
32+
result ->
33+
result
34+
end<% else %>
35+
request(Client, get, URL, Headers, undefined, HTTPOptions, <%= inspect(action.success_status_code) %>)<% end %><% else %>
36+
<%= action.function_name %>(Client<%= AWS.CodeGen.RestJSONService.function_parameters(:erlang, action) %>, Input)
37+
when is_map(Client), is_map(Input)) ->
38+
<%= action.function_name %>(Client<%= AWS.CodeGen.RestJSONService.function_parameters(:erlang, action) %>, Input, []).
39+
<%= action.function_name %>(Client<%= AWS.CodeGen.RestJSONService.function_parameters(:erlang, action) %>, Input, HTTPOptions) ->
40+
when is_map(Client), is_map(Input), is_list(HTTPOptions) ->
41+
URL = "<%= AWS.CodeGen.RestJSONService.Action.url(:erlang, action) %>"
42+
{Input1, Headers} = extract_request_headers(Input, [
43+
44+
45+
Headers = []<%= for parameter <- action.request_header_parameters do %>
46+
if Dict.has_key?(input, "<%= parameter.name %>") do
47+
Headers = [{"<%= parameter.name %>", input["<%= parameter.name %>"]}|headers]
48+
input = Dict.delete(input, "<%= parameter.name %>")
49+
end<%= end %><%= if length(action.response_header_parameters) > 0 do %>
50+
case request(client, <%= AWS.CodeGen.RestJSONService.Action.method(:erlang, action) %>, url, headers, input, options, <%= inspect(action.success_status_code) %>) do
51+
{:ok, body, response} -><%= for parameter <- action.response_header_parameters do %>
52+
if !is_nil(response.headers["<%= parameter.name %>"]) do
53+
body = %{body | "<%= parameter.name %>" => response.headers["<%= parameter.name %>"]}
54+
end<%= end %>
55+
{:ok, body, response}
56+
result ->
57+
result
58+
end<% else %>
59+
request(Client, <%= AWS.CodeGen.RestJSONService.Action.method(:erlang, action) %>, URL, Headers, Input, HTTPOptions, <%= inspect(action.success_status_code) %>)<% end %><% end %>
60+
<% end %>
61+
%%====================================================================
62+
%% Internal functions
63+
%%====================================================================
64+
65+
extract_request_headers(Input, Names) ->
66+
extract_request_headers(Input, [], Names).
67+
extract_request_headers(Input, Headers, []) ->
68+
{Input, Headers}.
69+
extract_request_headers(Input, Headers, [Name|Rest]) ->
70+
Headers1 = case maps:get(Name, Input, undefined) of
71+
undefined -> Headers;
72+
Value -> [{Name, Value}|Headers]
73+
end,
74+
extract_request_headers(maps:remove(Name, Input), Headers1, Rest).
75+
76+
request(Client, Method, URL, Headers, Input, HTTPOptions, SuccessStatusCode) ->
77+
Client1 = Client#{service => <<"<%= context.endpoint_prefix %>">>},
78+
Host = aws_util:binary_join([<<"<%= context.endpoint_prefix %>.">>,
79+
maps:get(region, Client1),
80+
<<".">>,
81+
maps:get(endpoint, Client1)],
82+
<<"">>),
83+
URL = aws_util:binary_join([<<"https://">>, Host, <<"/">>], <<"">>),
84+
Headers = [{<<"Host">>, Host},
85+
{<<"Content-Type">>, <<"application/x-amz-json-<%= context.json_version %>">>},
86+
{<<"X-Amz-Target">>, << <<"<%= context.target_prefix %>.">>/binary, Action/binary>>}],
87+
Payload = enode_payload(Input),
88+
Headers1 = aws_request:sign_request(Client1, <<"POST">>, URL, Headers, Payload),
89+
perform_request(Method, URL, Payload, Headers1, HTTPOptions, SuccessStatusCode).
90+
91+
perform_request(Method, URL, Payload, Headers, HTTPOptions, undefined) ->
92+
case hackney:request(Method, URL, Headers, Payload, HTTPOptions) of
93+
Response = {ok, 200, ResponseHeaders, Client} ->
94+
{ok, Body} = hackney:body(Client),
95+
{ok, jsx:decode(Body, [return_maps]), Response};
96+
Response = {ok, 202, ResponseHeaders, Client} ->
97+
{ok, Body} = hackney:body(Client),
98+
{ok, jsx:decode(Body, [return_maps]), Response};
99+
Response = {ok, 204, ResponseHeaders, Client} ->
100+
{ok, Body} = hackney:body(Client),
101+
{ok, jsx:decode(Body, [return_maps]), Response};
102+
Response = {ok, _StatusCode, ResponseHeaders, Client} ->
103+
{ok, Body} = hackney:body(Client),
104+
Reason = maps:get(<<"__type">>, jsx:decode(Body, [return_maps])),
105+
{error, Reason, {StatusCode, ResponseHeaders, Client}};
106+
{error, Reason} ->
107+
{error, Reason}
108+
end;
109+
perform_request(Method, URL, Payload, Headers, HTTPOptions, SuccessStatusCode) ->
110+
case hackney:request(Method, URL, Headers, Payload, HTTPOptions) of
111+
Response = {ok, SuccessStatusCode, ResponseHeaders, Client} ->
112+
{ok, Body} = hackney:body(Client),
113+
{ok, jsx:decode(Body, [return_maps]), Response};
114+
Response = {ok, _StatusCode, ResponseHeaders, Client} ->
115+
{ok, Body} = hackney:body(Client),
116+
Reason = maps:get(<<"__type">>, jsx:decode(Body, [return_maps])),
117+
{error, Reason, {StatusCode, ResponseHeaders, Client}};
118+
{error, Reason} ->
119+
{error, Reason}
120+
end.

0 commit comments

Comments
 (0)