Search code examples
elixirphoenix-frameworkabsinthe

How to accept JSON in Absinthe GraphQL requests


I'm attempting to receive a string of JSON in my graphql implementation but keep getting errors with the custom scalar I've defined to handle the JSON.

I've defined a custom scalar to properly serialize the JSON to an elixir map. I'm getting errors that I have invalid data types before my code reaches the parsing stage of the custom scalar. I'm attempting to use https://github.com/absinthe-graphql/absinthe/wiki/Scalar-Recipes#json-using-jason to create the scalar however I've modified to use Poison instead of Jason.

My absinthe mutator using the :json scalar type I created.

@desc "Update user"
field :update_user, type: :user do
  arg(:email, :string)
  arg(:password, :string)
  arg(:first_name, :string)
  arg(:last_name, :string)
  arg(:age, :integer)
  arg(:client_store, :json)

  resolve(handle_errors(&Resolvers.User_Resolver.update_user/3))
end

My scalar definition and graphql schema definition

    scalar :json, name: "Json" do
        description("""
        The `Json` scalar type represents arbitrary json string data, represented as UTF-8
        character sequences. The Json type is most often used to represent a free-form
        human-readable json string.
        """)
        serialize(&encode/1)
        parse(&decode/1)
    end

    # @spec decode(Absinthe.Blueprint.Input.String.t) :: {:ok, :string} | :error
    # @spec decode(Absinthe.Blueprint.Input.Null.t) :: {:ok, nil}
    defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
        Logger.info "decoded input value:"
        case Poison.decode(value) do
        {:ok, result} -> {:ok, result}
        _ -> :error
        end
    end

    defp decode(%Absinthe.Blueprint.Input.Null{}) do
        {:ok, nil}
    end

    defp decode(_) do
        :error
    end

    defp encode(value), do: value

    object :user do
        field(:id, :id)
        field(:email, :string)
        field(:password, :string)
        field(:first_name, :string)
        field(:last_name, :string)
        field(:age, :integer)
        field(:client_store, :json)
    end

when sending the following queries:

    mutation updateUser{
      updateUser(client_store: "{"key":"value"}"){
        id
      }
    }

I receive a syntax error before: \"\\\":\\\"\"

    mutation updateUser{
      updateUser(client_store: "hello"){
        id
      }
    }

I receive a "Argument \"client_store\" has invalid value \"hello\"

Sending the GraphQL query through a unit test via Phoenix.ConnTest

query = """
    mutation updateUser{
      updateUser(client_store: "{\"key\":\"value\"}"){
        id
      }
    }
  """

res =
  context.conn
  |> put_req_header("content-type", "text")
  |> put_req_header("authorization", token)
  |> post("/api", query)

Solution

  • The problem is with your mutation, you need to escape the quotes inside the string, like this:

    mutation updateUser{
      updateUser(client_store: "{\"key\": \"value\"}"){
        id
      }
    }
    

    Otherwise, the interpretation is that the second quote marks the end of the string, so you basically have the string "{" followed by key":"value"}", which is syntatically invalid.

    Note that when sending the mutation in an unit test, you are defining the mutation as a string already, so you need to further escape the quotes:

    query = """
        mutation updateUser{
          updateUser(client_store: "{\\\"key\\\":\\\"value\\\"}"){
            id
          }
        }
      """
    

    This may be a little tricky to understand at first, but printing it makes it clearer:

    iex(1)> """
    ...(1)>     mutation updateUser{
    ...(1)>       updateUser(client_store: "{\"key\":\"value\"}"){
    ...(1)>         id
    ...(1)>       }
    ...(1)>     }
    ...(1)> """ |> IO.puts()
        mutation updateUser{
          updateUser(client_store: "{"key":"value"}"){
            id
          }
        }
    
    iex(2)> """
    ...(2)>     mutation updateUser{
    ...(2)>       updateUser(client_store: "{\\\"key\\\":\\\"value\\\"}"){
    ...(2)>         id
    ...(2)>       }
    ...(2)>     }
    ...(2)> """ |> IO.puts() 
        mutation updateUser{
          updateUser(client_store: "{\"key\":\"value\"}"){
            id
          }
        }
    

    So the last one works because when the string is evaluated, the result is the original mutation, which escapes the quotes.