The other day I wrote a small lib to interact with AWS IAM (on top of ExAws). I wanted the internal API to have one Parser.parse/2
function that would handle any response. ExAws executes the request and calls this function with the response and the action name as the two arguments. For example:
parse.call(response, action)
Where response
is {:ok, %{body: xml, status_code: status}}
and action
is the AWS IAM operation (“CreateUser”, “DeleteUser”, etc…).
I created a single module with a parse/2
action that dispatched to a function, which in turn called the right parser in the right module (pattern matching on the action
name).
defmodule ExAws.Iam.Parser do
alias ExAws.Iam.Parsers.{AccessKey, User}
def parse({:ok, %{body: xml, status_code: status} = resp}, action) when status in 200..299 do
parsed_body = dispatch(xml, action)
{:ok, %{resp | body: parsed_body}}
end
def parse(resp, _), do: resp
@user_actions ~w[ListUsers CreateUser]
defp dispatch(xml, action) when action in @user_actions do
User.parse(xml, action)
end
@access_key_actions ~w[ListAccessKeys GetAccessKeyLastUsed]
defp dispatch(xml, action) when action in @access_key_actions do
AccessKey.parse(xml, action)
end
end
I then created individual parsing modules for each entity:
defmodule ExAws.Iam.Parsers.User do
def parse(xml, "ListUsers") do
# parse the xml
end
def parse(xml, "GetUser") do
# parse the xml
end
# etc...
end
defmodule ExAws.Iam.Parsers.AccessKey do
# ...
end
I’m not a fan, though. The Parsers.AccessKey
and Parsers.User
should be private (which we don’t have in Elixir). I can’t import
the functions because the names would clash. I also feel like I’m abusing pattern matching.
Protocols won’t work because the data types don’t change. What other patterns can I use to achieve the same API?