tendant / graphql-clj

A Clojure library that provides GraphQL implementation.
Eclipse Public License 1.0
285 stars 22 forks source link

Schema and statement validation prior to execution #40

Closed aew closed 7 years ago

aew commented 7 years ago

Fixes a number of outstanding bugs and incompatibilities between validation output and execution input. Backwards compatible.

Changes include:

This is a large change because the parser was eliminating line and column metadata necessary for producing validation error messages. The executor was coupled to parser output rather than validator output. This PR proposes that the executor take the validator output as an input rather than the parser output.

Related discussion: https://github.com/tendant/graphql-clj/issues/22

tendant commented 7 years ago

To maintain backward compatibility, it is better to create new version of type/create-schema with validation build-in.

aew commented 7 years ago

Initially, I didn't add a new version of create-schema because this creates a circular dependency. Maybe there is another way around this - will take a look.

[EDIT] - actually it's quite easy to avoid the circular dependency - I've made this change.

tendant commented 7 years ago

I am testing the new version. Looks like the introspection query is not working properly.

I am using https://github.com/tendant/graphql-clj-starter for testing introspection query.

aew commented 7 years ago

I'm seeing the error: Cannot query field 'subscriptionType' on type '__Schema'. I don't see the subscriptionType field on the introspection schema. Trying to understand where it's coming from.

https://github.com/graphql/graphiql/blob/0172a425036299b1d99dbd1ae82aa37f31781866/src/utility/introspectionQueries.js#L12

tendant commented 7 years ago

GraphiQL sends first query with subscriptionType, if server does not support subscriptionType, it will send out second query with subscriptionType commented out.

Subscription type is a future feature still in development. It is not in the GraphqQL Spec yet.

aew commented 7 years ago

I think I understand the issue - a query failing validation (because of this subscriptionType issue) is returning more than just the errors key, including things that don't serialize to JSON. I'll patch this.

aew commented 7 years ago

There were actually additional bugs hidden by a bug in the fragment cycles validator which was inappropriately short-circuiting the DFS. I've fixed these bugs and added a couple missing unit tests for things that were breaking the Graphiql starter project.

tendant commented 7 years ago

Just had simple test in GraphiQL, looks like it work fine now.

Thanks a lot for your great work. The validation really makes huge difference on usability of the library. It helps provide much better error messages.

tendant commented 7 years ago

Do you have more commits coming? If not, I might release a new version tonight.

aew commented 7 years ago

I'm not planning to commit any more unless we discover more bugs, so feel free to release a new version. Thanks!

tendant commented 7 years ago

Found a new issue in inspector:

https://github.com/tendant/graphql-clj-starter/tree/testing-validation-loc

Error result:

{
  "errors": [
    {
      "error": "Cannot query field 'kind' on type 'FullType'.",
      "loc": {
        "line": 21,
        "column": 1
      }
    }
  ]
}
aew commented 7 years ago

Ok - taking a look at this. Can you clarify which query you used to produce this error?

tendant commented 7 years ago

It came from introspection query.

Just open graphiql, the error will show up in graphql result.

tendant commented 7 years ago

Problem might relate to reloading of schema.

Looks like after updating schema in dev environment. Introspection query returns:

{
  "errors": [
    {
      "error": "Cannot query field 'kind' on type 'FullType'.",
      "loc": {
        "line": 21,
        "column": 1
      }
    }
  ]
}

When try to run any query after changing schema in dev server, I got below error for all root query fields:

{:errors [{:node-type :field, :parent {:node-type :operation-definition, :v/parent {:node-type :statement-root}, :v/path [Query]}, :node {:node-type :field, :field-name #object[graphql_clj.box.Box 0x231e19a3 {:status :ready, :val chapters}], :selection-set [{:node-type :field, :field-name #object[graphql_clj.box.Box 0x702e24d5 {:status :ready, :val id}], :v/path [Query chapters id], :spec :graphql-clj.1623193917/id}], :v/path [Query chapters], :spec :graphql-clj.1623193917/chapters, :v/parent {:node-type :operation-definition, :v/parent {:node-type :statement-root}, :v/path [Query]}, :v/parentk :selection-set}, :error Parent type not found}]}
tendant commented 7 years ago

BTW: I tried to use validator/validate-schema* function, it still has the same issue.

tendant commented 7 years ago

The problem goes away when all memoize function calls are removed.

Also, I would like to propose to expose execute function to be as simple as we can:

(defn execute
  [context schema-or-state resolver-fn statement-or-state variables]
    ...))

User can cache validated schema, validated statement and resolver-fn as they wish.

tendant commented 7 years ago

Another issue with the validated schema and statement: validation result is more than 10x bigger than parsing result.

Below are size comparison for schema "graphql-clj-starter.graphql/starter-schema"

6.2K  parsed-schema
70K   validated-schema

Below are size comparison for statement "{droid}"

242B parsed-statement
71K    validated-statement

Looks like the validated data contains the schema in addition to the result.

aew commented 7 years ago

Sounds good re: removing memoize and the single arity for the execute function - I'll make those changes.

Re: size of the validation output - I'm waiting to see how validation output interacts with a simplified execution phase to understand exactly what we need to keep, then we can remove everything else and it will be small.

aew commented 7 years ago

I've made the changes discussed above - @tendant worth taking another look.

For additional context, there are a lot of outstanding issues that concern me, but I don't know how many of them we want to fix in a single PR:

Given all of this, we could either merge this and hope for the best, or remove validation as a requirement for the execution phase, and perhaps add a different execution function for previously "prepared" inputs. WDYT?

tendant commented 7 years ago

I will test it and merge it, if there is no major issue.

As long as we keep the public API simple and consistent, it is not too hard to change the internal implementation if needed.

tendant commented 7 years ago

Merged. Thanks you @aew for your great contribution!