Giter Club home page Giter Club logo

graphql's Introduction

GraphQL Elixir

Build Status Public Slack Discussion

An Elixir implementation of Facebook's GraphQL.

This is the core GraphQL query parsing and execution engine whose goal is to be transport, server and datastore agnostic.

In order to setup an HTTP server (ie Phoenix) to handle GraphQL queries you will need plug_graphql. Examples for Phoenix can be found at hello_graphql_phoenix, so look here for a starting point for writing your own schemas.

Other ways of handling queries will be added in due course.

Installation

First, add GraphQL to your mix.exs dependencies:

defp deps do
  [{:graphql, "~> 0.3"}]
end

Add GraphQL to your mix.exs applications:

def application do
  # Add the application to your list of applications.
  # This will ensure that it will be included in a release.
  [applications: [:logger, :graphql]]
end

Then, update your dependencies:

$ mix deps.get

Usage

First setup your schema

defmodule TestSchema do
  def schema do
    %GraphQL.Schema{
      query: %GraphQL.Type.ObjectType{
        name: "RootQueryType",
        fields: %{
          greeting: %{
            type: %GraphQL.Type.String{},
            resolve: &TestSchema.greeting/3,
            description: "Greeting",
            args: %{
              name: %{type: %GraphQL.Type.String{}, description: "The name of who you'd like to greet."},
            }
          }
        }
      }
    }
  end

  def greeting(_, %{name: name}, _), do: "Hello, #{name}!"
  def greeting(_, _, _), do: "Hello, world!"
end

Execute a simple GraphQL query

iex> GraphQL.execute(TestSchema.schema, "{greeting}")
{:ok, %{data: %{"greeting" => "Hello, world!"}}}

Status

This is a work in progress, right now here's what is done:

  • Parser for GraphQL (including Type definitions)
  • AST matching the graphql-js types as closely as possible
  • Schema definition
  • Query execution
    • Scalar types
    • Arguments
    • Multiple forms of resolution
    • Complex types (List, Object, etc)
    • Fragments in queries
    • Extract variable values
  • Introspection
  • [WIP] Query validation
  • Directives

Resources

Implementation

Tokenisation is done with leex and parsing with yecc. Both very useful Erlang tools for parsing. Yecc in particular is used by Elixir itself.

Some resources on using leex and yecc:

The Execution logic follows the GraphQL JS Reference Implementation pretty closely, as does the module structure of the project. Not to mention the naming of files and concepts.

If you spot anything that isn't following Elixir conventions though, that's a mistake. Please let us know by opening an issue or a PR and we'll fix it.

Developers

Getting Started

Clone the repo and fetch its dependencies:

$ git clone https://github.com/graphql-elixir/graphql.git
$ cd graphql
$ mix deps.get
$ mix test

Atom Editor Support

Using the language-erlang package? .xrl and .yrl files not syntax highlighting?

Syntax highlighting in Atom for leex (.xrl) and yecc (yrl) can be added by modifying grammars/erlang.cson.

Just open the atom-language-erlang package code in Atom and make the change described here:

jonathanmarvens/atom-language-erlang#11

however if that PR has been merged then just grab the latest version of the plugin!

Contributing

We actively welcome pull requests, bug reports, feedback, issues, questions. Come and chat in the #erlang channel on Slack

If you're planning to implement anything major, please let us know before you get too far so we can make sure your PR will be as mergable as possible. Oh, and don't forget to write tests.

License

BSD.

graphql's People

Contributors

1player avatar aweiker avatar bruce avatar freshtonic avatar markolson avatar markschmidt avatar mattludwigs avatar mynameisrufus avatar nickgartmann avatar peburrows avatar plasticine avatar seanabrahams avatar zhyu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql's Issues

Schema DSL

I've been lurking in this repo for a while and I've tried to come up with some kind of DSL to describe a schema at a high level. Has anybody already given some thought about this?

I've tried to replicate the Star Wars example from the reference graphql repository from Facebook (https://github.com/graphql/graphql-js/tree/master/src/__tests__), this is what I've come up with:

defmodule Character do
  use GraphQL.ObjectInterface

  field :id, null: false
  field :name
  field :friends, type: List.of(Character)
  field :appearsIn, type: List.of(Episode)
end

defmodule Human do
  use GraphQL.Object, deriving: Character

  field :homePlanet
end

defmodule Droid do
  use GraphQL.Object, deriving: Character

  field :primaryFunction
end

defmodule Schema do
  use GraphQL.Schema

  field :hero, type: Character do
    argument :episode, description: "foo"

    resolve %{episode: episode} do
      getHero(episode)
    end

    resolve do
      getHero(1000)
    end
  end

  field :human, type: Human do
    argument :id, description: "id of the human", null: false

    resolve %{id: id} do
      getHuman(id)
    end
  end

  field :droid, type: Droid do
    argument :id, description: "id of the droid", null: false

    resolve %{id: id} do
      getDroid(id)
    end
  end



  @humans [ 
    "1000": %Human{
      id: "1000",
      name: "Luke Skywalker",
      friends: ["1002", "1003", "2000", "2001"],
      appearsIn: [4, 5, 6],
      homePlanet: "Tatooine",
    },
    "1001": %Human{
      id: "1001",
      name: "Darth Vader",
      friends: [ "1004" ],
      appearsIn: [ 4, 5, 6 ],
      homePlanet: "Tatooine",
    },
    [ ... ]
  ]

  @droids [ ... ]

  defp getHero(5), do: @humans["1000"]
  defp getHero(_), do: @droids["2001"]

  defp getHuman(id), do: @humans[id]

  defp getDroid(id), do: @droids[id]
end

The Schema module defines the schema with its field. Each field has some arguments and one or more resolve functions, depending on whether some arguments are nullable.
Fields and arguments accept a "type" option, and nullability is expressed with a "null: false" option.

Interfaces are defined by using GraphQL.ObjectInterface, with a similar DSL to define the fields, while objects deriving the interface specify doing so when "using" GraphQL.Object (see Human and Droid).

Modules importing GraphQL.Object behave very similarly to structs, and inherit all the fields defined in the parent interface, if any.

That's just a proof of concept but I'd like to hear some comments and if there's any idea somebody's written down for a DSL.

Disclaimer: I'm quite new with Elixir, there surely are syntax errors in the POC code above.

Complex types under status

I was looking at the status and it's my understanding that lists aren't implemented but there is a test that looks like it is testing a list

    test "lists of things" do

I'm confused

Default values for arguments are not used

I setup a simple query that took set a default value, then did not set the input variable and it should have used the default value.

Query:

query name($name: String = "Joe")
{
  greeting(name: $name)
}

If I set $name then the query executes, but if I don't the value is empty.

I tested this against the node version and it performed as expected.

Upgrading to phoenix 1.2 causes error in node_interface

== Compilation error on file web/router.ex ==
** (ArgumentError) cannot escape #Function<1.97113962/1 in App.PublicSchema.node_interface/0>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, pids and remote functions in the format &Mod.fun/arity
    (elixir) src/elixir_quote.erl:119: :elixir_quote.argument_error/1
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:382: :elixir_quote.do_splice/5
    (elixir) src/elixir_quote.erl:259: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:382: :elixir_quote.do_splice/5
    (elixir) src/elixir_quote.erl:259: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:382: :elixir_quote.do_splice/5
    (elixir) src/elixir_quote.erl:259: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:382: :elixir_quote.do_splice/5
    (elixir) src/elixir_quote.erl:259: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:382: :elixir_quote.do_splice/5
    (elixir) src/elixir_quote.erl:259: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:382: :elixir_quote.do_splice/5
    (elixir) src/elixir_quote.erl:141: :elixir_quote.escape/2
    (elixir) lib/macro.ex:312: Macro.escape/2
  scope "/graphql" do
    pipe_through :api
    forward "/", GraphQL.Plug, schema: {App.PublicSchema, :schema}
  end
  def node_interface do
    Node.define_interface(fn(obj) ->
      case obj do
        %{start_unix_timestamp: _ } ->
          App.Event.Type
        %{comments: _, full_text: _} ->
          App.Article.Type
        %{members: _} ->
            App.Group.Type
        %{email: _, name: _} ->
          App.User.Type
        %{replys: _} ->
          App.Comments.Type
      end
    end)
  end

elixir 1.2.3

Structure specs a bit better

Lex specs are ok, but parser specs need to be split out into 'tokens -> output' and 'strings -> output' sections.

Specs could probably be better named to match the GraphQl spec too.

Executor: Ordering of response fields

As reported by @Bockit.

query:

mutation named {
    createTransaction(
        timestamp: "2016-01-18T21:37:30"
        amount: 50.0
        description: "That was my grandfather's haunch."
        tags: ["a","b","c"]
    ) {
        amount
        id
        description
        timestamp
        audited
        tags {
            tag
        }
    }
}

output:

{
  "data": {
    "createTransaction": {
      "timestamp": "2016-01-18 21:37:30",
      "tags": [
        {
          "tag": "a"
        },
        {
          "tag": "b"
        },
        {
          "tag": "c"
        }
      ],
      "id": "42",
      "description": "That was my grandfather's haunch.",
      "audited": false,
      "amount": 50
    }
  }
}

Support string fields and args

At the moment, the fields key in %GraphQL.Type.ObjectType{} and args key only take a map with atom keys. For example:

%GraphQL.Schema{
  query: %GraphQL.Type.ObjectType{
    name: "RootQueryType", 
    fields: %{
      greeting: %{
        type: %GraphQL.Type.String{},
        args: %{
          name: %{type: %GraphQL.Type.String{}, description: "The name of who you'd like to greet."}
        },
        resolve: fn(_,_,_) -> "Hello" end
      }
    }
  }
}

However, there are cases where those fields are generated from user's input. By using atoms, we can run out of atoms in the Erlang shell, since atoms are not garbage collected.

It would be ideal to also support this:

%GraphQL.Schema{
  query: %GraphQL.Type.ObjectType{
    name: "RootQueryType", 
    fields: %{
      "greeting" => %{
        type: %GraphQL.Type.String{},
        args: %{
          "name" => %{type: %GraphQL.Type.String{}, description: "The name of who you'd like to greet."}
        },
        resolve: fn(_,_,_) -> "Hello" end
      }
    }
  }
}

Resolve fails when it cannot find a matching function

When a function is specified in resolve and pattern matching is used, if the matching fails an ugly missing method exception is thrown instead of an helpful error message. Since user input can cause this to fail, a friendly validation error should be given instead.

For example:

This is the relevant schema definition:

%ObjectType{
        name: "Mutation",
        description: "Root object for performing data mutations",
        fields: %{
          set_rail_preferences: %{
            description: "Updates the rail preferences for a user.",
            args: Orchestration.Schema.User.RailPreferences.set_rail_preferences_args,
            type: Orchestration.Schema.User.RailPreferences.type,
            resolve: fn(_, args = %{id: id}, _) ->
              case Orchestration.Data.set_user_rail_preferences(id, args) do
                {:ok, user} -> user.preferences.rail
                :empty -> nil
              end
            end
          }
        }
      }

Here is the query document

mutation preferences{
  set_rail_preferences(seat: WINDOW) {
    seat
  }
}

error message:

** (FunctionClauseError) no function clause matching in anonymous fn/3 in Orchestration.Schema.schema/0

Recursive schema definition potentially causing an infinite loop

Hi. Firstly, thanks for all the work on graphql-elixir and plug-graphql.

I'm having trouble with a circular schema definition. Using plug_graphql in phoenix and the server freezes up no matter the query when the circular definitions are in play.

  • Recurring
  • Transaction
  • Tag

Relations are:

  • Tags many to many Recurring
  • Tags many to many Transaction

https://github.com/Bockit/budget/tree/master/apps/api-server/web/graphql is the graphql schema I have for this model. The root query provides a singular and list type query for each model. Each model has a resolve tag for its relations.

Before I added the relations to the Tag schema, things were great but once I added the recurrings and transactions relations to the Tag schema all requests freeze up in what appears to be an infinite loop. Even if you don't request any of the relation fields.

If you comment out the tags field on Transaction, and the recurrings field on Tag (breaking the circle) then you can do the following and it works as expected:

$ curl "http://127.0.0.1:4000/api?query=\{recurring(id:11)\{amount,tags\{tag,transactions\{amount\}\}\}\}"
{
  "data": {
    "recurring": {
      "tags": [
        {
          "transactions": [
            {
              "amount": 24.53
            }
          ],
          "tag": "Utilities"
        },
        {
          "transactions": [
            {
              "amount": 60.0
            }
          ],
          "tag": "Development"
        }
      ],
      "amount": 50.0
    }
  }
}

If this is confusing I can make a simpler, proof-of-concept reproduction tomorrow. If I'm correct and this is causing an infinite loop with a circular reference as described then a simple schema with 2 object types that both refer back to each other should cause the same effect.

Thanks for your time.

List input type

I'm having trouble with the list input type. Here's my root schema, you can ignore the query part, I'm only doing list inputs with the mutation. You can see I have the mutation field createTransaction. Here's where I define the createTransaction mutation field and I've highlighted where I'm defining the list type input arg.

If I run the following query:

http://127.0.0.1:4000/api?query=mutation+named{createTransaction(timestamp:timeitself,amount:50.0,description:hello,tags:[a,b,c]){amount}}

I get the following error:

[error] #PID<0.911.0> running BudgetApi.Endpoint terminated
Server: 127.0.0.1:4000 (http)
Request: GET /api?query=mutation%20named{createTransaction(timestamp:timeitself,amount:50.0,description:hello,tags:[a,b,c]){amount}}
** (exit) an exception was raised:
    ** (KeyError) key :value not found in: %{kind: :ListValue, loc: %{start: 0}, values: [%{kind: :EnumValue, loc: %{start: 0}, value: "a"}, %{kind: :EnumValue, loc: %{start: 0}, value: "b"}, %{kind: :EnumValue, loc: %{start: 0}, value: "c"}]}
        lib/graphql/execution/executor.ex:202: GraphQL.Execution.Executor.value_from_ast/3
        lib/graphql/execution/executor.ex:189: anonymous fn/4 in GraphQL.Execution.Executor.argument_values/3
        (stdlib) lists.erl:1262: :lists.foldl/3
        lib/graphql/execution/executor.ex:110: GraphQL.Execution.Executor.resolve_field/4
        lib/graphql/execution/executor.ex:91: anonymous fn/5 in GraphQL.Execution.Executor.execute_fields/4
        (stdlib) lists.erl:1262: :lists.foldl/3
        lib/graphql/execution/executor.ex:60: GraphQL.Execution.Executor.execute_operation/3
        lib/graphql/plug/endpoint.ex:44: GraphQL.Plug.Endpoint.execute/3
        (phoenix) lib/phoenix/router/route.ex:157: Phoenix.Router.Route.forward/4
        (budget_api) lib/phoenix/router.ex:255: BudgetApi.Router.dispatch/2
        (budget_api) web/router.ex:1: BudgetApi.Router.do_call/2
        (budget_api) lib/budget_api/endpoint.ex:1: BudgetApi.Endpoint.phoenix_pipeline/1
        (budget_api) lib/plug/debugger.ex:93: BudgetApi.Endpoint."call (overridable 3)"/2
        (budget_api) lib/phoenix/endpoint/render_errors.ex:34: BudgetApi.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

If I were to guess, I'd say the executor isn't aware of the listValue kind having the values key instead of value but I don't know for sure.

As an aside, are list input strings (and strings in general) supposed to have quotes around the values? Just noticing in that output it looks like it's saying the a b and c are EnumValues.

Thanks for your time.

TestHelper is mostly unnecessary

Instead of many permutations of assert_execute_*, let's just have our tests invoke GraphQL.execute, get the response (which may or may not include errors) and check assert on the response directly.

GraphQL.execute is now easier to use because of the use of keyword lists. It means we don't need all of the assert permutations to make it usable anymore.

Fix 1.3 imperative if warnings

warning: the variable "acc" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/lang/ast/parallel_visitor.ex:30

warning: the variable "accumulator" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/validation/rules/fields_on_correct_type.ex:27

warning: the variable "message" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/validation/rules/fields_on_correct_type.ex:91

warning: the variable "accumulator" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/lang/ast/type_info_visitor.ex:150

warning: the variable "accumulator" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/lang/ast/type_info_visitor.ex:171

warning: the variable "acc" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/validation/rules/no_fragment_cycles.ex:77

warning: the variable "acc" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  lib/graphql/validation/rules/no_fragment_cycles.ex:77

warning: function GraphQL.Lang.AST.TypeInfo.nullable_type/2 is undefined or private
  lib/graphql/lang/ast/type_info_visitor.ex:127

Incompatible with Distillery

Distillery (issue) is unable to archive a Phoenix app using graphql because some of the graphql .beam files' names exceed the 100 character limited imposed by erl_tar (source).

I was able to get Distillery working with this change.

I'm happy to submit it as a pull request if it is the correct fix.

Specifying an invalid operation name should return a friendly error message

Currently a nil is passed in causing code to crash.

Unit Test:

  test "returns an error when an operation name does not match an operation defined in the query" do
    {_, result} = execute(TestSchema.schema, "query a {greeting}", operation_name: "b")
    assert_has_error(result, %{message: "Must provide an operation name that exists in the query."})
  end

Fix dialyzer errors, and make dialyzer part of the CI build

leexinc.hrl:268: Function yyrev/2 will never be called
executor.ex:31: The specification for 'Elixir.GraphQL.Execution.Executor':execute/3 states that the function might also return {'ok','Elixir.Map'} but the inferred return is {'error',<<_:296,_:_*128>> | #{}} | {'ok',_,_}
executor.ex:43: The specification for 'Elixir.GraphQL.Execution.Executor':execute_operation/3 states that the function might also return {'ok','Elixir.Map'} but the inferred return is {'error',<<_:296,_:_*128>>} | {'ok',_,_}
executor.ex:88: Overloaded contract for 'Elixir.GraphQL.Execution.Executor':execute_fields/4 has overlapping domains; such contracts are currently unsupported and are simply ignored
executor.ex:137: The call 'Elixir.GraphQL.Execution.Executor':complete_value_catching_error(context@1::atom() | tuple() | #{},return_type@1::any(),field_asts@1::nonempty_maybe_improper_list(),info@1::#{},result@1::any()) breaks the contract ('Elixir.GraphQL.Execution.ExecutionContext':t(),any(),'Elixir.GraphQL.Document':t(),any(),#{}) -> {'Elixir.GraphQL.Execution.ExecutionContext':t(),#{} | 'nil'}
executor.ex:146: Invalid type specification for function 'Elixir.GraphQL.Execution.Executor':complete_value_catching_error/5. The success typing is (_,_,nonempty_maybe_improper_list(),#{},_) -> any()
executor.ex:152: Overloaded contract for 'Elixir.GraphQL.Execution.Executor':complete_value/5 has overlapping domains; such contracts are currently unsupported and are simply ignored
executor.ex:191: The created fun has no local return
executor.ex:192: The call 'Elixir.GraphQL.Execution.Executor':complete_value_catching_error(context@2::any(),list_type@1::any(),field_asts@1::nonempty_maybe_improper_list(),info@1::#{},item@1::any()) breaks the contract ('Elixir.GraphQL.Execution.ExecutionContext':t(),any(),'Elixir.GraphQL.Document':t(),any(),#{}) -> {'Elixir.GraphQL.Execution.ExecutionContext':t(),#{} | 'nil'}
resolvable.ex:2: The specification for 'Elixir.GraphQL.Execution.Resolvable':'__protocol__'/1 states that the function might also return 'true' but the inferred return is 'Elixir.GraphQL.Execution.Resolvable' | 'false' | [{'resolve',4},...]
type_info_visitor.ex:124: Call to missing or unexported function 'Elixir.GraphQL.Lang.AST.TypeInfo':nullable_type/2
visitor.ex:3: The specification for 'Elixir.GraphQL.Lang.AST.Visitor':'__protocol__'/1 states that the function might also return 'true' but the inferred return is 'Elixir.GraphQL.Lang.AST.Visitor' | 'false' | [{'enter',3} | {'leave',3},...]
visitor.ex:48: The specification for 'Elixir.GraphQL.Lang.AST.InitialisingVisitor':'__protocol__'/1 states that the function might also return 'true' but the inferred return is 'Elixir.GraphQL.Lang.AST.InitialisingVisitor' | 'false' | [{'init',2},...]
visitor.ex:64: The specification for 'Elixir.GraphQL.Lang.AST.PostprocessingVisitor':'__protocol__'/1 states that the function might also return 'true' but the inferred return is 'Elixir.GraphQL.Lang.AST.PostprocessingVisitor' | 'false' | [{'finish',2},...]
abstract_type.ex:1: The specification for 'Elixir.GraphQL.Type.AbstractType':'__protocol__'/1 states that the function might also return 'true' but the inferred return is 'Elixir.GraphQL.Type.AbstractType' | 'false' | [{'get_object_type',3} | {'possible_type?',2} | {'possible_types',2},...]
definition.ex:3: The specification for 'Elixir.GraphQL.Types':'__protocol__'/1 states that the function might also return 'true' but the inferred return is 'Elixir.GraphQL.Types' | 'false' | [{'parse_literal',2} | {'parse_value',2} | {'serialize',2},...]
Unknown functions:
  'Elixir.GraphQL.Execution.Resolvable.BitString':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.Float':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.Integer':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.List':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.Map':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.PID':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.Port':'__impl__'/1
  'Elixir.GraphQL.Execution.Resolvable.Reference':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Atom':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.BitString':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Float':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Function':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Integer':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.List':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Map':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.PID':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Port':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Reference':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.InitialisingVisitor.Tuple':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Atom':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.BitString':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Float':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Function':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Integer':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.List':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Map':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.PID':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Port':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Reference':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.PostprocessingVisitor.Tuple':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Atom':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.BitString':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Float':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Function':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Integer':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.List':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Map':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.PID':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Port':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Reference':'__impl__'/1
  'Elixir.GraphQL.Lang.AST.Visitor.Tuple':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Atom':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.BitString':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Float':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Function':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Integer':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.List':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Map':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.PID':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Port':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Reference':'__impl__'/1
  'Elixir.GraphQL.Type.AbstractType.Tuple':'__impl__'/1
  'Elixir.GraphQL.Types.Atom':'__impl__'/1
  'Elixir.GraphQL.Types.BitString':'__impl__'/1
  'Elixir.GraphQL.Types.Float':'__impl__'/1
  'Elixir.GraphQL.Types.Function':'__impl__'/1
  'Elixir.GraphQL.Types.Integer':'__impl__'/1
  'Elixir.GraphQL.Types.List':'__impl__'/1
  'Elixir.GraphQL.Types.Map':'__impl__'/1
  'Elixir.GraphQL.Types.PID':'__impl__'/1
  'Elixir.GraphQL.Types.Port':'__impl__'/1
  'Elixir.GraphQL.Types.Reference':'__impl__'/1
  'Elixir.GraphQL.Types.Tuple':'__impl__'/1
Unknown types:
  'Elixir.GraphQL.AbstractType':t/0
 done in 0m8.20s
done (warnings were emitted)

Erlang compatibility

Make it possible and easy to call GraphQL Elixir from pure Erlang (and LFE, etc).

Introspection doesn't show mutation

I believe you're already aware, but introspection (and therefore completion/docs on GraphiQL) don't show mutation/subscription, even when those are available.

I tracked the offending line to https://github.com/joshprice/graphql-elixir/blob/3171ad8af07cbe7e11c962bf9bd074516049d2da/lib/graphql/type/introspection.ex#L34

However, I don't know how to fix this to get it to report the mutation (beyond uncommenting, which didn't work). If I could get a little guidance, I'd love to help!

Bug in array_map.ex:59 when it patterns matches on a struct

Testing out master branch to cut a new release and ran into the following issue while testing.

[error] #PID<0.2294.0> running Together.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /graphql
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Enumerable not implemented for %Calendar.DateTime{abbr: "UTC", day: 3, hour: 22, min: 15, month: 9, sec: 38, std_off: 0, timezone: "Etc/UTC", usec: 629168, utc_off: 0, year: 2016}
        (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
        (elixir) lib/enum.ex:116: Enumerable.reduce/3
        (elixir) lib/enum.ex:1627: Enum.reduce/3
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/util/array_map.ex:54: anonymous fn/3 in GraphQL.Util.ArrayMap.expand_result/1
        (elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
        (graphql) lib/graphql/util/array_map.ex:53: GraphQL.Util.ArrayMap.expand_result/1
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/util/array_map.ex:59: anonymous fn/2 in GraphQL.Util.ArrayMap.expand_result/1
        (stdlib) lists.erl:1263: :lists.foldl/3

Mutation with map argument causes KeyError

Hey there, I came across this problem while attempting to issue this mutation via GraphiQL:

mutation M {
  upsertSpread(input: { user_id: 1 }) {
    id
    description
  }
}

My mutation function:

  def mutation do
    %ObjectType{
      name: "mutation",
      fields: %{
        upsertSpread: %{
          type: Spread,
          args: %{
            input: %{ type: SpreadInput }
          },
          resolve: fn(_, args, _) -> TeamBuilder.Data.upsert_spread(args) end
        }
      }
    }
  end

The SpreadInput type:

  defmodule SpreadInput do
    def type do
      %ObjectType{
        name: "SpreadInput",
        fields: %{
          user_id: %{type: %Int{}, description: "user id"}
        }
      }
    end
  end

Stack trace when I issue the mutation in GraphiQL:

Request: POST /graphql/user?query=mutation%20M%20%7B%0A%20%20upsertSpread(input%3A%20%7B%20user_id%3A%201%20%7D)%20%7B%0A%20%20%20%20id%0A%20%20%20%20description%0A%20%20%7D%0A%7D%0A&variables=%7B%0A%20%20%22data%22%3A%20%7B%0A%20%20%20%20%22user_id%22%3A%201%0A%20%20%7D%0A%7D
** (exit) an exception was raised:
    ** (KeyError) key :value not found in: %{fields: [%{kind: :ObjectField, loc: %{start: 0}, name: %{kind: :Name, loc: %{start: 0}, value: "user_id"}, value: %{kind: :IntValue, loc: %{start: 0}, value: 1}}], kind: :ObjectValue, loc: %{start: 0}}
        (graphql) lib/graphql/type/definition.ex:12: GraphQL.Types.Any.parse_literal/2
        (graphql) lib/graphql/execution/executor.ex:255: anonymous fn/4 in GraphQL.Execution.Executor.argument_values/3
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/execution/executor.ex:138: GraphQL.Execution.Executor.resolve_field/4
        (graphql) lib/graphql/execution/executor.ex:117: anonymous fn/4 in GraphQL.Execution.Executor.execute_fields/4
        (stdlib) lists.erl:1263: :lists.foldl/3
        (graphql) lib/graphql/execution/executor.ex:45: GraphQL.Execution.Executor.execute_operation/3
        (graphql) lib/graphql.ex:106: GraphQL.execute_with_optional_validation/3
        (plug_graphql) lib/graphql/plug/endpoint.ex:85: GraphQL.Plug.Endpoint.execute/2
        (plug_graphql) lib/graphql/plug.ex:54: GraphQL.Plug.call/2
        (hello_graphql) lib/phoenix/router.ex:261: HelloGraphQL.Router.dispatch/2
        (hello_graphql) web/router.ex:1: HelloGraphQL.Router.do_call/2
        (hello_graphql) lib/hello_graphql/endpoint.ex:1: HelloGraphQL.Endpoint.phoenix_pipeline/1
        (hello_graphql) lib/plug/debugger.ex:93: HelloGraphQL.Endpoint."call (overridable 3)"/2
        (hello_graphql) lib/hello_graphql/endpoint.ex:1: HelloGraphQL.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

Everything worked when I issued a mutation with a built-in type like Int or String. Is this the right way to issue a mutation that takes a custom type as an argument? I was under the impression that custom types for mutations should be created with %InputObjectType{} but I got the following error when trying to do that:

== Compilation error on file web/graphql/user_schema.ex ==
** (CompileError) web/graphql/user_schema.ex:8: GraphQL.Type.InputObjectType.__struct__/1 is undefined, cannot expand struct GraphQL.Type.InputObjectType
    web/graphql/user_schema.ex:6: (module)
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (stdlib) erl_eval.erl:122: :erl_eval.exprs/5

Thank you!

Need to figure out how to represent the parsed GraphQL structure so it can be useful

I've been focussing on getting lexing and parsing to work correctly, and have a simple but likely not very useful structure just so I can understand the output and check it is working as expected with tests.

So this needs to change but just not sure how yet.

Want to tackle this before writing too many more specs because the specs are getting harder to read, and there will be more to change if I leave this until later.

Field Property Mapping Support

Does the core support mapping from the Ecto field, first_name, to the GraphQL field, firstName? I have played with Ruby GraphQL server and it seems that there's a property attribute on the field.`. In GraphQL version for Ruby, one would do something like the following:

field :firstName, !types.String, property: :first_name

Resolve Should Accept a Map of String Keys

When I return a database document in the resolve function it only works if the map has symbol keys. Currently you have to convert them after every database query or the GraphQL response is null for each string key.

For example this works:

%{
  type: App.Types.User.get(),
  resolve: fn(_, _, _) ->
    %{id: "123", name: "Bob"}
  end
}

# returns
{
  "data": {
    "user": {
      "name": "Bob",
      "id": "123"
    }
  }
}

however this doesn't work

%{
  type: App.Types.User.get(),
  resolve: fn(_, _, _) ->
    %{"id" => "123", "name" => "Bob"}
  end
}

#returns
{
  "data": {
    "user": {
      "name": null,
      "id": null
    }
  }
}

How do i access queried fields from the resolver?

Ideally i do not want to select all the columns of my users table, if the client only want to access the name of a user.

Is there any way that i can, from my resolver, pick the requested fields, and make a more specific query, based on it?

Union help

Thanks for the awesome library, I am loving creating a GraphQL service in Elixir. I am running into an issue I hope you can shed some light on. I have a field on a value that is a string "type". This field is a string from the backing API, but I want expose it as a Union Type. I setup the "Banner" type as shown -

def type do
    %ObjectType{
      name: "Banner",
      description: "A Banner in the system",
      fields: %{
        id:
          %{type: %ID{}, description: "The ID of the banner"},
        avatar_image:
          %{type: Image, description: "The avatar image for the banner"},
        desktop_banner_image:
          %{type: Image, description: "The desktop image for the Banner (wide)"},
        mobile_banner_image:
          %{type: Image, description: "The avatar image for the Banner (square)"},
        type:
          %{
            type: banner_types,
            description: "The type for this banner",
            resolve: fn
              banner, params, ast -> Api.get_by_id(get_in(banner,["slug"]))
            end
          }
      }
    }
  end

and the "banner_types" union type as follows -

  def banner_types do
    GraphQL.Type.Union.new %{
      name: "BannerTypes",
      types: [Product],
      resolver: fn
        (product) -> Product
      end
    }
  end

I can see the initial resolver in the Banner make the API call, then pass the result to the the Union resolver, which resolves the Type Product. This correctly resolves the __typename in the query, but it fails on any fragments for Product

     ... on Product {
        id
      }

I get something like -

** (exit) an exception was raised:
    ** (KeyError) key :name not found in: %{..} # rest of Struct omitted

Any idea how to make this work with fragments? Thank you again!

Arguments should be treated as not present when there is no Input Variable

Reproduced Behavior in GraphQL-JS

When using arguments in a query, if the variable is not set the query should treat that argument like it does not exist.

Example Query

query hello($name: String, $episode: Episode) {
  hello(name: $name, episode: $episode)
}

Query variables

{
  "episode" : "JEDI"
}

In this case the value of name will be undefined when the resolve function is executed.

Experienced behavior in GraphQL-Elixir

When executing this same type of query in GraphQL-Elixir, it will use the default value for a string which is "".

When not specifying an Enum, it will try to parse nil and will throw an exception in the defimpl of GraphQL.Types.parse_value for GraphQL.Type.Enum.

undefined function: nil.type/0 when repoducing the example from docs [0.0.6]

I'm trying to reproduce the example from docs with some small changes, but I get an error when I try to execute sample request.

My schema:

defmodule BlogSchema do
  def schema do
    %GraphQL.Schema{
      query: %GraphQL.ObjectType{
        name: "RootQueryType",
        fields: [ 
          %GraphQL.FieldDefinition{name: :post, type: "String", resolve: &post/0}
        ]   
      }   
    }   
  end 

  def post do
    "test"
  end 
end

Request:

GraphQL.execute(BlogSchema.schema, "{post}")

Trace

             nil.type()
             lib/graphql/execution/executor.ex:79: GraphQL.Execution.Executor.resolve_field/4
             lib/graphql/execution/executor.ex:66: anonymous fn/5 in GraphQL.Execution.Executor.execute_fields/4
    (stdlib) lists.erl:1262: :lists.foldl/3
             lib/graphql/execution/executor.ex:37: GraphQL.Execution.Executor.execute_operation/3
             lib/graphql/execution/executor.ex:17: GraphQL.Execution.Executor.execute/5

I'm not sure, but it looks like executor.ex:79 expects fields to be a map, but gets an array instead.

But when I change my schema to

defmodule BlogSchema do
  def schema do
    %GraphQL.Schema{
      query: %GraphQL.ObjectType{
        name: "RootQueryType",
        fields: %{
          post: %GraphQL.FieldDefinition{name: :post, type: "String", resolve: &post/0}
        }
      }   
    }   
  end 

  def post do
    "test"
  end 
end

I get this error

** (BadArityError) #Function<0.112413074/0 in BlogSchema.schema/0> with arity 0 called with 3 arguments (%{}, %{}, %{field_asts: [%{kind: :Field, loc: %{start: 0}, name: "post"}], field_name: "post", fragments: %{}, operation: %{kind: :OperationDefinition, loc: %{start: 0}, operation: :query, selectionSet: %{kind: :SelectionSet, loc: %{start: 0}, selections: [%{kind: :Field, loc: %{start: 0}, name: "post"}]}}, parent_type: %GraphQL.ObjectType{description: "", fields: [post: %GraphQL.FieldDefinition{args: %{}, name: :post, resolve: #Function<0.112413074/0 in BlogSchema.schema/0>, type: "String"}], name: "RootQueryType"}, return_type: "String", root_value: %{}, schema: %GraphQL.Schema{mutation: nil, query: %GraphQL.ObjectType{description: "", fields: [post: %GraphQL.FieldDefinition{args: %{}, name: :post, resolve: #Function<0.112413074/0 in BlogSchema.schema/0>, type: "String"}], name: "RootQueryType"}}, variable_values: %{}})
             lib/graphql/execution/executor.ex:94: GraphQL.Execution.Executor.resolve_field/4
             lib/graphql/execution/executor.ex:66: anonymous fn/5 in GraphQL.Execution.Executor.execute_fields/4
    (stdlib) lists.erl:1262: :lists.foldl/3
             lib/graphql/execution/executor.ex:37: GraphQL.Execution.Executor.execute_operation/3
             lib/graphql/execution/executor.ex:17: GraphQL.Execution.Executor.execute/5

So now resolve function we've got from field definition is called with three arguments.

What am I doing wrong?

Error nil.type/0 is undefined is hard to diagnose

As reported by @puruzio on Slack. The stacktrace from this error makes it very hard to pin down where the error is occurring in the schema.

Ideally this error should be pre-empted with a useful warning to avoid the problem.

 ** (UndefinedFunctionError) function nil.type/0 is undefined or private                                                                                                                        
    nil.type()                                                                                                                                                                                 
    (elixir) lib/enum.ex:1188: anonymous fn/3 in Enum.map/2                                                                                                                                    
    (stdlib) lists.erl:1263: :lists.foldl/3                                                                                                                                                    
    (elixir) lib/enum.ex:1188: Enum.map/2                                                                                                                                                      
    lib/graphql/type/schema.ex:111: GraphQL.Schema._reduce_arguments/2                                                                                                                         
    lib/graphql/type/schema.ex:76: anonymous fn/2 in GraphQL.Schema.reduce_types/2                                                                                                             
    (stdlib) lists.erl:1263: :lists.foldl/3                                                                                                                                                    
    lib/graphql/type/schema.ex:75: GraphQL.Schema.reduce_types/2                                                                                                                               
    (stdlib) lists.erl:1263: :lists.foldl/3                                                                                                                                                    
    lib/graphql/type/schema.ex:75: GraphQL.Schema.reduce_types/2                                                                                                                               
    lib/graphql/type/schema.ex:49: GraphQL.Schema.do_reduce_types/2                                                                                                                            
    lib/graphql/type/schema.ex:30: GraphQL.Schema.new/1                                                                                                                                        
    lib/graphql/validation/validator.ex:58: GraphQL.Validation.Validator.validate_with_rules/3                                                                                                 
    lib/graphql.ex:103: GraphQL.execute_with_optional_validation/3                                                                                                                             
    lib/graphql_relay.ex:22: GraphQL.Relay.introspection/0                                                                                                                                     
    lib/graphql_relay.ex:17: GraphQL.Relay.generate_schema_json!/0                                                                                                                             
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6                                                                                                                                            
    (elixir) lib/code.ex:168: Code.eval_string/3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.