Absinthe result has all null values

649 views Asked by At

I'm totally new to Elixir, Phoenix, and Absinthe...so go easy on me. :)

I'm experimenting with a graph database called Dgraph using a library called Dlex. I've written a simple query designed to look up and return a list of users (I only have two users at the moment):

def list_users() do
  query = """
    {
      users(func: type(User)) {
        uid
        email
        name
      }
    }
  """

  {:ok, %{"users" => result}} = Dlex.query(:dlex, query)

  IO.inspect(result)

  {:ok, result}
end

The output from IO.inspect(result) is precisely what I expect and want—a list of my two users:

[
  %{"email" => "[email protected]", "name" => "Rob", "uid" => "0x1"},
  %{"email" => "[email protected]", "name" => "Bridget", "uid" => "0x2"}
]

However, when I run this query with GraphiQL, all the values in the result are null for some reason:

{
  "data": {
    "users": [
      {
        "email": null,
        "name": null,
        "uid": null
      },
      {
        "email": null,
        "name": null,
        "uid": null
      }
    ]
  }
}

Any idea what I'm doing wrong?

1

There are 1 answers

1
Brett Beatty On BEST ANSWER

It looks to me like you could be returning the appropriate data in your user resolver, with one exception: when Absinthe goes to resolve the fields for each user, the default resolver only looks for the fields as atom keys. When it can't find :uid, :email, or :name in the parent maps, it returns nil.

You could convert the result to have atoms as keys. One option if you want to go that route would just be to map over each user and explicitly copy what you want.

users = Enum.map(users, &%{uid: &1["uid"], email: &1["email"], name: &1["name"]})

That is one more place you'd need to update when keys are added, though. And a lot of attempts at more dynamic solutions don't follow best practices (they open the app up to creating new atoms at runtime, which is a bad idea, or throwing errors when they see unrecognized keys).

One solution I've used in the past is creating my own default MapGet middleware that checks for both atom and string keys.

defmodule MyAppWeb.GraphQL.Middleware.MapGet do
  @moduledoc """
  Default resolver that checks for both atom and string keys.
  """
  @behaviour Absinthe.Middleware

  @impl Absinthe.Middleware
  def call(%{source: source} = info, key) do
    value =
      with :error <- Map.fetch(source, key),
           :error <- Map.fetch(source, to_string(key)) do
        nil
      else
        {:ok, value} ->
          value
      end

    %{info | state: :resolved, value: value}
  end
end

The Absinthe docs describe swapping out the default middleware. In my app it was pretty simple.

defmodule MyAppWeb.GraphQL.Schema do
  use Absinthe.Schema
  # ...

  def middleware(middleware, field, object) do
    map_get = {{MyAppWeb.GraphQL.Middleware.MapGet, :call}, field.identifier}
    Absinthe.Schema.replace_default(middleware, map_get, field, object)
  end

  # ...
end