Changing value to camelCase with middleware in absinthe

I’m trying to change the values of some fields to camelCase with middleware like this:

defmodule Middlewares.CamelCaseField do
  @behaviour Absinthe.Middleware
  def call(resolution, _) do
    %{resolution | errors: Enum.flat_map(resolution.errors, &handle_error/1)}
  end

  defp handle_error(%{field: field} = error) do
    %{error | field: camel(field)}
  end

  defp handle_error([]), do: []

  defp handle_error(error) when is_list(error) do
    case Keyword.get(error, :field) do
      nil -> error
      field -> Keyword.put(error, :field, camel(field))
    end
  end

  defp handle_error(error), do: [error]

  def camel(f), do: Atom.to_string(f) |> Recase.to_camel()
end
...
defmodule Schema do
  ....

  def middleware(middleware, field, object) do
    middleware ++ [Middlewares.CamelCaseField]
  end

But now when I run my code I get errors like these:

** (Protocol.UndefinedError) protocol String.Chars not implemented for {:code, :unknown_error}. This protocol is implemented for: Postgrex.Copy, Postgrex.Query, Decimal, DateTime, Date, Version, NaiveDateTime, List, Atom, Time, BitString, Integer, URI, Version.Requirement, Float.

If I remove my middleware everything works, so I’m probably doing something wrong with the middleware, but don’t know what exactly.

Found my mistake. I needed to wrap the result of handle_error(error) when is_list(error) in a list as well, so:

  defp handle_error(error) when is_list(error) do
    error = case Keyword.get(error, :field) do
      nil -> error
      field -> Keyword.put(error, :field, camel(field))
    end
    [error]
  end
3 Likes

@tcoopman Have you tried Absinthe.Adapter.LanguageConventions ( https://github.com/absinthe-graphql/absinthe/blob/master/guides/adapters.md) ?

You have to add this configuration in your config.exs file. Example:
config :absinthe, adapter: Absinthe.Adapter.LanguageConventions

I’m using it and works very fine.

Note that you can no longer set the adapter in the configuration, but instead need to do it in the router: https://github.com/absinthe-graphql/absinthe/pull/575

1 Like

or in the endpoint.ex when the entire API is base on GraphQL

plug Absinthe.Plug,
    schema: ContentProxyGraphql.Schema,
    adapter: Absinthe.Adapter.LanguageConventions,
    json_codec: Jason

I’m not sure the adapter can help translating values in error results? If I have %{errors: [%{field: "my_field"}]} translate the my_field value to myField?

hum… probably I misunderstood your question :smiley:
The LanguageConventions adapter only changes the field name case.

However, in my opinion, sounds strange to change user values in output moment, except for date format or enumeration values.

If your values are statics, perhaps would be more expressive to use Graphl Enum (https://github.com/absinthe-graphql/absinthe/blob/master/guides/tutorial/complex-arguments.md)

If enum doesn’t fit your needs, the code you have made is nice!

my use case is that I let use ecto changeset to validate parts of the code. But this returns fields in snake case. So before I send it to the user if map my errors into output that is more consistent with the input query.

You can access the adapter within the third argument to the resolution function, so you could use that adapter’s to_external_name function on your own error information to do the transformation.

1 Like