GithubHelp home page GithubHelp logo

jlouis / graphql-erlang Goto Github PK

View Code? Open in Web Editor NEW
314.0 30.0 50.0 935 KB

GraphQL implementation in Erlang.

License: Other

Makefile 0.11% Erlang 99.30% Shell 0.03% Ruby 0.45% Python 0.05% Nix 0.07%
graphql erlang graph facebook

graphql-erlang's People

Contributors

ahf avatar benoitc avatar bullno1 avatar callumroberts avatar gausby avatar getong avatar goertzenator avatar jlouis avatar lpil avatar madscoaducom avatar overbryd avatar ptrf avatar tudborg avatar valberg 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

graphql-erlang's Issues

5.7.5 All Variables Used

This task depends on Issue #107. Once that is done, then this is trivial to satisfy. You walk the document, and build a union set over the variables in it. A fragment contains a type signature which transitively knows about its variable uses.

Once the full set is created, you can use it to subtract from the set of variables. If that set becomes the empty set, then every variable has at least one use.

Support user defined enum variant values?

Commit 6bdc1b6 introduces the variants/1 function in the graphql_schema_parse, which assings a numeric value to each enum variant, starting with 0 and incrementing by 1 for each consecutive variant. This reduces boilerplate code dramatically, but unfortunately, this setup:

  1. Makes the ordering of variants in the .graphql schema file important
  2. Requires the maintenance of a separate enum value "translation" for components that does not interact with objects through the graph.

Currently, this affects moose, as moose needs to be able to distinguish and search among different authentication method representation, that are parametrized by the AuthenticationMethodType enum. Prior to the new way of doing things(TM), variant values were explicitly defined and could be
canonicalized as this example shows

{enum, { ..., 
    values => #{ 
                'AUTHENTICATION_METHOD_FACEBOOK' =>
                    #{ value => gryphon_person:authentication_method_type(facebook),
                       description => "Authentication method facilitated by Facebook."
                    },
                  ...
        }
    }
}

@jlouis Can we come up with a solution allowing variant representation values to be determined or looked up outside of the graphql?

Proposal: Rename atom null -> nil

Hi,

this might seem a little counter-intuitive at first, but hear me out:

Using graphql-erlang from Elixir is currently slightly pesky, since one has to either traverse the whole tree/list of results to replace null with nil or nastily patch JSON encoding to treat null as null instead of the default Atom -> binary "null".

I have been going back and forth with some hacky solutions, but thought maybe its worth having this discussion upstream here.

I am happy to help by sending in a PR that is currently representing a project-wide rename of the null-Atom to nil. It would give the folks over in Elixir land instant compatibility to bind to this excellent library.

Here is something to have a laugh about while pondering. (It's all ALGOL W's fault.)

5.7.4 - All variable uses defined

Variable uses should propagate into Fragments as well. This is related to #70 and should probably be handled in the same patch. Many of the things can be pushed around in the type checker if you start building "function specifications" for fragments.

Graphql builtins suffer from encoding issues (breaks server/clients)

Hi everyone,

I am running against the latest master version.

And I am seeing strange encoding problems when querying the default responses with GraphiQL.
For demonstration purposes I used your graphql-erlang-tutorial application.

So this is the response header that cowboy sends for the introspection query.
It states that content will be utf-8 encoded:

HTTP/1.1 200 OK
server: Cowboy
date: Tue, 19 Dec 2017 18:44:19 GMT
content-encoding: gzip
content-length: 4069
content-type: application/json; charset=utf-8
vary: accept

snip

But inside the response, documentation annotations can be found that have encoding issues.
Here is one example of the introspection of the SCALAR type Float:

{"description":"Floating point values, IEEE 754, but not �infty, nor NaN","enumValues":null,"fields":null,"inputFields":null,"interfaces":null,"kind":"SCALAR","name":"Float","possibleTypes":null}

When looking up the source for this string, I found this in src/graphql_builtins.erl:15:

    	description => "Floating point values, IEEE 754, but not ±infty, nor NaN" }},

So there is definitely something fishy going on. Unfortunately I do not know how to fix this quickly.
I think maybe there is an encoding issue when fetching graphql-erlang as a dependency (it definitely happens on a vanilla graphql-erlang-tutorial application).

This issue prevents some clients, and also my server, to render introspection queries/results.

Resolvers should be able to return auxiliary data

We used to be able to send auxiliary data back from a resolver like so:

{ok, Result, [AuxiliaryData]}

This data would show up under the aux field in the query result map, but this functionality seems to be lost in a recent commit. We need to:

  • Make a unit test
  • Implement it for the new resolver code

Wrong (deep) variable expansion

Consider the following query:

mutation C($n : String) { createUser(input:{name: $n}) { user { id name }}}

This erroneously makes a call like the following:

#{<<"name">> => {var,{name,34,<<"n">>}}}

which should have been expanded. To fix this, we need to understand what is happening in the expansion system inside the type checker.

Validation Tracking

Background

We need to have a place where we track validations. We begin this work by building the complete list, then mark what we already have covered. Things not covered are pulled into their own issues for further tracking.

Checklist

From the Oct2016 specification:

  • 5.1.1 Named Operation Definitions
  • 5.1.1.1 Operation Name Uniqueness
  • 5.1.2 Anonymous Operation Definitions
  • 5.1.2.1 Lone Anonymous Operation
  • 5.2.1 Field Selections on Objects, Interfaces, and Unions Types
  • 5.2.2 Field Selection Merging
  • 5.2.3 Leaf Field Selections
  • 5.3.1 Argument Names
  • 5.3.2 Argument Uniqueness
  • 5.3.3 Argument Values Type Correctness
  • 5.3.3.1 Compatible Values
  • 5.3.3.2 Required Non-Null Arguments
  • 5.4.1 Fragment Declarations
  • 5.4.1.1 Fragment Name Uniqueness
  • 5.4.1.2 Fragment Spread Type Existence
  • 5.4.1.3 Fragments On Composite Types
  • 5.4.1.4 Fragments Must Be Used
  • 5.4.2 Fragment Spreads
  • 5.4.2.1 Fragment spread target defined
  • 5.4.2.2 Fragment spreads must not form cycles
  • 5.4.2.3 Fragment spread is possible
  • 5.4.2.3.1 Object Spreads In Object Scope
  • 5.4.2.3.2 Abstract Spreads in Object Scope
  • 5.4.2.3.3 Object Spreads In Abstract Scope
  • 5.4.2.3.4 Abstract Spreads in Abstract Scope
  • 5.5.1 Input Object Field Uniqueness
  • 5.6.1 Directives Are Defined
  • 5.6.2 Directives Are In Valid Locations
  • 5.6.3 Directives Are Unique Per Location
  • 5.7.1 Variable Uniqueness
  • 5.7.2 Variable Default Values Are Correctly Typed
  • 5.7.3 Variables Are Input Types
  • 5.7.4 All Variable Uses Defined
  • 5.7.5 All Variables Used
  • 5.7.6 All Variable Usages are Allowed

Get on hex.pm

Publish this package (and the tutorial) on hex.pm.

N+1 SQL queries

The following query will require 1 SQL query to get the books and N queries to get each author.

{
    books(genre: "comedy") {
        title
        author {
            name
        }
    }
}

Is there a way to make only 1 or 2 SQL queries with graphql-erlng?

Handle Fragment expansion with variables fully

Background

Suppose we have a fragment

fragment M on Monster {
  ...
  hitPoints(above: $foo)
  ...
}

Then this fragment is valid as a spread in any query Q which defines

query Q($foo : Int) { ... }

But not in a query

query Q($foo: String) { ... }

Also, note that if we have another fragment

fragment R on Room {
    description(language: $foo)
}

Then if language : Locale you are not allowed to mix fragments M and R in the same query.

Other observations

  • This holds transitively. If M refers to a subfragment I on Iventory and that fragment refers to $bar, then M also refers to $bar.
  • These tests must happen in the type checking phase.

Implementation

The reason this has been held off for a while is that it isn't that simple to implement:

  • Introduce the notion of a type signature for fragment. This allows us to ask if a fragment "fits" when it is called by a fragment spread.
  • Inline spreads can just produce their signature and we can then check it afterwards.
  • A fragment is implemented as a function from its signature to its expansion. Thus, the function signature is verified by checking if we can "call" the fragment from the spread with a valid type.
  • When handling fragments, they can refer to other fragments. When this happens, we must run fragception and DFS the fragment world. We track already handled fragments in order to detect if we have a cycle in the fragment expansion. This runs in linear time over the fragments and is going to be reasonably fast. Also, it moves the cycle validation into the type checker where it belongs.
  • Value coercion still happens at the execution phase for variables. So this will still work as expected.

Concurrent execution: add timeouts

Our (experimental) concurrent execution currently waits 750ms for some event to happen. This is arbitrarily chosen and if the deadline is met, we fail the query. We'd like for each defer statement to tell the engine what it expects is the deadline. This will allow us to manage deadlines in the system and run with higher deadlines.

This issue tracks what there is to be done in order to solve this problem.

  • Introduce a deadline timeout to defer results
  • Gather all timeouts, pick the maximal one
  • Handle timeout extension if parts of the query requires a larger timeout
  • Resolve deadlines by removing appropriate closures: invoke an {error, timeout} as the response result and feed that to the closure. It will then resolve itself properly, but as if an error occurred.

Split the graph schema file in one file per object

Add the possibility to split the graph schema in diferentes files, that files must be joined and evaluated as a singe schema file.
As @jlouis suggested , we could use a directive at top of file like package my-graphql-erlang, for example where every file that references that package will be into my-graphql-erlang.graphql

5.4.2.3 Fragment spread is possible

A fragment spread is ...Frag. They are written as fragment Frag on Dog { ... }. The fragment has a type, (in this case Dog) and the context in which the spread occurs has a type (not shown here). For a query to be sensible, they must match. In short, there must be a way for the fragment to eventually match. Otherwise the fragment is wrong.

There is code for this in the system already. It would be obvious to check this in the type checking phase: look up the fragment type and expand it into its possible types (unions, interfaces, objects). Do the same with the spread and look for a valid overlap.

However, it isn't clear that this is currently handled, so:

  • Provide a test case for the validator
  • Eventually implement the validator, preferably as part of a type check

5.5.1 Input Object Field Uniqueness

The system must reject e.g.,

{
  field(arg: { field: true, field: false })
}

Because it isn't unique. This belongs as a check in type-checking when checking input objects, if it is not already there.

Crash while creating error message for type error in named query without operationName

That was a handful to type.

Basically, given a named query:

query FindSomething($input: Input!) {
  find(input: $input)
}

$input contains a type error (e.g: negative number for a uint type).
operationName is not given by the client and following graphql-erlang-tutorial, it is set to undefined.

Given this query, graphql will crash with:

#{class => error,
  error => {case_clause,{undefined}},
  module => my_web_handler_module,
  stacktrace =>
      [{graphql_err,'-path/1-F/1-0-',1,
                    [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_err.erl"},
                     {line,43}]},
       {graphql_err,'-path/1-lc$^2/1-2-',2,
                    [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_err.erl"},
                     {line,60}]},
       {graphql_err,path,1,
                    [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_err.erl"},
                     {line,60}]},
       {graphql_err,mk,3,
                    [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_err.erl"},
                     {line,20}]},
       {graphql_err,abort,3,
                    [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_err.erl"},
                     {line,16}]},
       {graphql_type_check,check_input_object_fields,4,
                           [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_type_check.erl"},
                            {line,232}]},
       {graphql_type_check,'-tc_params/3-fun-0-',4,
                           [{file,"/home/bullno1/Projects/nm-exchange-api/_build/default/lib/graphql/src/graphql_type_check.erl"},
                            {line,131}]},
       {lists,foldl,3,[{file,"lists.erl"},{line,1263}]}]}

Which I guess is while it's trying to build the error path, hence the undefined case clause.
After I give operationName a correct value, graphql reports the type error correctly without crashing.

GraphQL commit: 135657f745254198f3d07e35fd3a63623144b119

5.7.1 Variable Uniqueness

A fairly straightforward case. We already have a uniqueness checker in the code base, we just have to call it before we proceed. So I think this task is among the low-hanging fruit.

Batch loading support

Not an issue per se, I'm just not sure how to do batch loading in graphql-erlang.

I'm trying to use this optimization: dataloader to batch up individual queries into a big one.

Basically, in dataloader, a call to dataloader.load(id) will return a promise. At the "next tick", all ids will be sent in a single batch query to save round trip time and all promises will be resolved.

While deferring is already possible in graphql-erlang, "joining" is not as the concept of a "next tick" doesn't make much sense in Erlang.

Is it possible to register a callback which will be called here: https://github.com/shopgun/graphql-erlang/blob/develop/src/graphql_execute.erl#L912? It can inform the batch loader to start sending.

Alternatively, is there a better way to implement batch loading?

5.4.1.4 - Fragments must be used

The system must reject the following query, which has a fragment which is never used in the system.

fragment nameFragment on Dog { # unused
  name
}

{
  dog {
    name
  }
}

Passing null variable into fields with default argument

Not sure if this is a bug or a feature.

Given the schema:

type Query {
  articles(includeDeleted: Boolean = false): [Article]
}

And the query:

query ListArticles($includeDeleted: Boolean) {
   articles(includeDeleted: $includeDeleted) { title }
}

Calling the query without providing $includeDeleted will result in null being passed into articles( i.e: articles(includeDeleted: null) while includeDeleted was specified to default to false. Changing the above query to:

query ListArticles($includeDeleted: Boolean = false) {
   articles(includeDeleted: $includeDeleted) { title }
}

will achieve the desired result but that's a repetition of default value.

GraphQL commit: 135657f745254198f3d07e35fd3a63623144b119

heavy lager dependencies

I am exploring using graphql on an embedded device, and the dependence on lager pulls in approximately 2MB of .beam (lager->goldrush->compiler), whereas graphql itself is only 0.38MB.

I see lager is used in only 4 places. Is depending on lager crucial here? I've never used lager before so I don't know what it is bringing to the table.

tag 0.8.0 ?

Maybe you could tag the 0.8.0 version? Is there any reason why not?

5.2.2 Field Selection merging

The validator for this is not yet implemented (it is somewhat contrived and complex). This issues tracks the field selector.

Support Query Handles

Rather than supplying a query document each time, support caching a query handle on the server side and let queries refer to this in the future. This speeds up queries as we can skip a good part of the parsing and validation each time.

Invalid description results in unhelpful error message

Given this invalid annotation:

# Notice the uppercase "Text"
+description(Text: "Fail")

This makes graphql:load_schema fails with {error, {schema_canonicalize, {error, badarg}}} which does not help the user to track down the error.
I'm not sure what's the right fix for this one.

5.7.6 - All Variable uses are allowed

In operations you can have variables $var1, $var2 and so on. When they are used, transitively, it is important that they are used in ways that doesn't break the type system. We are currently doing some of the checking work, but not the transitive follows into fragments. This has to be done.

Resolver error should propagate its failure correctly

The current system resolves the error but makes the error propagate too far. It should turn that error into a null value. This is a rather simple fix, which should improve error handling by quite a lot in the responses.

I found the problem toying with the star wars tutorial yesterday.

Make it possible to serve multiple graphql schemas on a node

Currently it is only possible to serve one GraphQL schema on an Erlang node. It would be useful to be able to run multiple GraphQL schemas on a single node for a couple of reasons:

  • In a HTTP context different schemas could be served from different endpoints
  • In a test context tests could be run in parallel

This would require all the internal lookups to be name-spaced.

Parser bugs in anonymous invocation

Execution of a test such as

query { hello }

Currently fails to get through the parser. This is wrong and should be remedied. It is currently not known when this error came into existence.

  • Reproduce the error in a test case
  • Write a fix for the parser

The bug is clearly in the parser since the parser rejects the above document. But it shouldn't reject the above document as it is valid. The document

{ hello }

is accepted and this could be a hint as to what is going on here.

Some times, `__typename` is not valid in introspection

We need to investigate this. There are a couple of places where you cannot expand __typename as you should because the introspection part reports it as an invalid field. This is obviously a bug.

  • Reproduce the bug in a test case so we make sure we nail it for good.
  • Fix the bug.

Enum output coercion: Make sure the enum is valid

Currently, there is a test in the enum SUITE, no_correct_internal_value, which is there to test we correctly handle output rules for enumerated values. We currently don't.

The fix is in the execution layer. When you return from a resolver of enum type, we must check that the returned value indeed matches the valid possible enum values. Otherwise, this is an error. Also, while here, we must lock down what the correct return value for an enum type is in the case where you are using the default resolver. Before this has gone in, we cannot document how enum resolution works.

Handle queries which have parameters by no operation name

A query such as

query ($n : Int) {
    grabStuff(size: $n) {
       id
    }
}

Is produced by Apollo. It fails because we expect something like query N(...) { ... } where the query has a name. Check:

  • This is allowed in the first place in the specification (Both 2016 and the current draft spec)
  • Write a test case for the right behaviour
  • Implement the change in the parser
  • Verify that the semantics are still correct in this case (Type checker and execution phase has to process parameters like normally in this case).

Subscription support

Is there any reason why subscription is not supported? All the codes are already there and it seems to be intentionally ignored in graphql_execute and graphql_schema_canonicalize.

Would a trivial pull request to enable it to have identical semantics to query be accepted? Or did I overlook something non-obvious with subscription?

Make it possible to define the format of null returns

Currently when a node resolve to no data we return {ok, null}; this is fine for an Erlang and JSON context, but if we want to use another output format, or use graphql-erlang as an interface to another system that require something else (like {ok, undefined}) we could benefit from making this configurable.

This will have to happen when the graphql-execute finalise a value.

Annotating values in an enum results in a error

Annotating a field of an enum in a schema will result in a crash when evaluating the graphql schema:

How to reproduce:

Add something like this to the schema:

enum Status {
  +description(text: "This will make the parser crash")
  SUCCESS
  FAILURE
}

Reload the graphql schema in an interface like graphiql.

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.