absinthe-graphql / absinthe_plug

Plug support for Absinthe, the GraphQL toolkit for Elixir
https://hex.pm/packages/absinthe_plug
MIT License
261 stars 163 forks source link

:boolean false value coerced to null #288

Closed fairbairn closed 10 months ago

fairbairn commented 10 months ago

This seems to be a problem with the document presentation that is delivered back from the plug..

Given a schema of..

query do 
    field :test, :test_boolean do
      resolve(fn _, _, _ ->
        {:ok,
         %{
           true_value: true,
           false_value: false
         }}
      end)
    end
end

And an object type of ...

  object :test_boolean do
    field :true_value, :boolean
    field :false_value, :boolean
  end

And a query of ...

query {
  test {
    true_value
    false_value
  }
}

The result returned is ...

{
  "data": {
    "test": {
      "false_value": null,
      "true_value": true
    }
  }
}

the false value is never returned as a boolean in the document, but instead as a null.

However, if we inspect the Blueprint source values from Absinthe itself, it reflects the false value in the field, but when returned within the JSON response, it coerces to a null instead.

benwilson512 commented 10 months ago

Hi @fairbairn I ran this test module on main:

defmodule IssueTest do
  use Absinthe.Plug.TestCase
  alias Absinthe.Plug.TestSchema
  alias Absinthe.Plug.TestPubSub

  defmodule TestSchema do
    use Absinthe.Schema

    query do
      field :test, :test_boolean do
        resolve(fn _, _, _ ->
          {:ok,
           %{
             true_value: true,
             false_value: false
           }}
        end)
      end
    end

    object :test_boolean do
      field :true_value, :boolean
      field :false_value, :boolean
    end
  end

  test "does it work?" do
    opts = Absinthe.Plug.init(schema: TestSchema)

    body =
      Jason.encode!(%{
        query: """
        query {
          test {
            true_value
            false_value
          }
        }
        """
      })

    assert %{status: 200, resp_body: resp_body} =
             conn(:post, "/", body)
             |> put_req_header("content-type", "application/json")
             |> plug_parser
             |> Absinthe.Plug.call(opts)

    resp_body == """
    {"data":{"test":{"false_value":false,"true_value":true}}}\
    """
  end
end

And it passed. Can you provide a runnable example that demonstrates your issue?

fairbairn commented 10 months ago

You're a rockstar, thanks so much for the test harness.

After some trials, I recognized that my Schema had a middleware that I added to allow it to look up values by string keys when the atom keys might have failed (this is because we can return records with mixed key types).

When I disabled this middleware, it resolved properly.

Now I need to alter my middleware ;) which was as follows...

  def middleware(middleware, field, object) do
    # new_middleware = {Absinthe.Middleware.MapGet, to_string(field.identifier)}
    new_middleware = {{__MODULE__, :find_data_from_key}, field.identifier}

    middleware |> Absinthe.Schema.replace_default(new_middleware, field, object)
  end

  def find_data_from_key(%{source: source} = res, key) do
    %{res | state: :resolved, value: Map.get(source, key) || Map.get(source, to_string(key))}
  end

The || above is the culprit as Map.get(:false_value, false) would "or" into the next stage and subsequently resolve to null!!

I'll fix that on my end (duh).

Thanks again.