Better design pattern for creating a polymorphic API

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?