Coder Social home page Coder Social logo

graphql-clj's Introduction

graphql-clj

A Clojure library designed to provide GraphQL implementation.

Build Status

Demo

Demo Project with GraphiQL

What's new in version 0.2

  1. Simplified APIs
  2. Rewrite schema and query validator for simplicity and robostness.
  3. Separate parser and validator for schema and query.
  4. High performance java parser

Installation

Add the following dependency to your project.clj file:

[graphql-clj "0.2.9"]

Usage

Define schema

(def schema-str "type User {
    name: String
    age: Int
  }
  type QueryRoot {
    user: User
  }

  schema {
    query: QueryRoot
  }")

Define resolver functions

(defn resolver-fn [type-name field-name]
  (get-in {"QueryRoot" {"user" (fn [context parent args]
                                 {:name "test user name"
                                  :age 30})}}
          [type-name field-name]))

Execute query

    (require '[graphql-clj.executor :as executor])
    (def query-str "query {user {name age}}")

    (executor/execute nil schema-str resolver-fn query-str)
    ;; => {:data {"user" {"name" "test user name", "age" 30}}}

Caching validated schema and query for performance

    (require '[graphql-clj.schema-validator :as schema-validator])
    (require '[graphql-clj.query-validator :as query-validator])
    
    ;; Consider memoizing the result of parsing and validating the query before execution
    (def validated-schema (schema-validator/validate-schema schema-str)) ; throw ex-info with ex-data {:errors errors}
    (def validated-query (query-validator/validate-query validated-schema query-str)) ; return [errors validated-ast]

    (executor/execute nil validated-schema resolver-fn validated-query)
    ;; => {:data {"user" {"name" "test user name", "age" 30}}}

Migrating from 0.1.x to 0.2 version

  1. Separated parser api for schema and query
   parser/parse-schema for schema parsing
   parser/parse-query-document for query parsing
  1. Simplified validator api, it can take query string and schema string now.
   graphql-clj.schema-validator/validate-schema replaces validator/validate-schema
   graphql-clj.query-validator/validate-query replaces validator/validate-statement
  1. executor/execute function can take string and validated result for both schema and query string.

Deploy to local for development

$ lein install

Release to Clojars

$ lein deploy clojars

Test

$ lein test

License

Copyright © 2016 Lei Wang

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

graphql-clj's People

Contributors

aew avatar dounan avatar feifanzhou avatar jeffi avatar ku1ik avatar olegthecat avatar qwwwpp avatar tendant 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

graphql-clj's Issues

Support for default argument values

According to http://graphql.org/learn/schema/#arguments you can give arguments default values and then you can skip them in the query. I tried that but I'm getting {:error [{:message "Missing arguments: #{\"id\"}, for field (foo) in type (Query)."}]}. I briefly looked at the source and I don't see any code dealing with default values. What's the status of this? I may help with implementing this.

Cannot query field '__typename'

The GraphQL spec (https://github.com/facebook/graphql/blob/af50a0a7e4fec972a6b7e1f8aaa518e41b459116/README.md) mentions that the special field __typename is defined on every object, and it appears this is not supported in graphql-clj 0.1.20.

I would never have noticed except the Apollo client automatically adds the __typename field to queries, and my graphql-clj based app is returning:

{"errors":[{"error":"Cannot query field '__typename' on type 'Account'.","loc":{"line":1,"column":37}}]}

I'm a clojure noob so any suggestions much appreciated

Thanks

Can't seem to use aliases

Hi! I'll preface this with that I'm relatively new to graphql, so this could be just a noob mistake!

I'm putting together a simple project to play around with graphql-clj (and graphql in general), but I can't seem to alias my queries. I've looked at your tests and I know aliasing should work, but I can't seem to make it happen.

I've included a screenshot of the entirety of my codebase (super small!). Would you perhaps be able to point out an incorrect use of the library (or perhaps a mistake in the query/schema) that is preventing my attempts at aliasing? I want to make sure this is a mistake on my end, and not some bug.

If I reorder the aliased lines in the query, I always receive only the last-requested user, and the returned data is not aliased.

Thanks for your help! And thanks for the lib!

2016-10-11-154028_1361x1078_scrot

Tries to load spec under Clojure 1.8 and breaks

I'm trying to upgrade from 0.1.17 to 0.1.20 and I'm getting this:

Exception in thread "main" java.io.FileNotFoundException: Could not locate clojure/spec__init.class or clojure/spec.clj on classpath., compiling:(graphql_clj/spec.clj:1:1)

Is 0.1.20 no longer supporting Clojure < 1.9 ?

List type argument seems to become lost

Hi,

It seems that the list type argument isn't passed to the resolver function now. I wonder if I made a mistake or there is some regression in the code.

Below is the schema and mutation call I was testing. The resolver function createPerson is called with args :friend nil (the :name argument is fine):

type Person {
  id: String!
  name: String
  friends: [Person]
}

type Mutation {
  createPerson(name: String, friends: [String]): Person
}

schema {
  mutation: Mutation
}
mutation {
  createPerson(name: "John", friends: ["1"]) {
    name,
    id,
    friends {
      name
    }
  }
}

I wonder if the args-fn needs to handle the list type. https://github.com/tendant/graphql-clj/blob/master/src/graphql_clj/executor.clj#L13-L17

Thank you! And just to take to opportunity to say that this is a great tool you are making. The well designed and easy to use API makes it more of a joy to code with. Thank you very much!

Validator error: "clojure.spec$nilable_impl$reify__41165 cannot be cast to clojure.lang.Named"

(def type-schema (validator/validate-schema (parser/parse "type Query {
  people: String
}

type Mutation {
  createPeople(emails: [String!]): String
}

schema {
  query: Query
  mutation: Mutation
}")))
(validator/validate-statement (parser/parse "mutation($emails: [String!]) {
  createPeople(emails: $emails)
}") type-schema)
{:state {:errors [{:error "clojure.spec$nilable_impl$reify__41165 cannot be cast to clojure.lang.Named"}]}}

Benchmarks

First off, thanks for the work on this – it's exciting stuff! I'm aware that any optimisation efforts are probably still far on the horizon but I took the liberty of creating a small set of benchmarks in this repository, currently only for query parsing.

Unfortunately, as of now, it seems that parsing incurs nearly intolerable overhead. For example, the following query (which I don't feel is an unfairly contrived example) clocks in at around 50ms mean parsing time:

{
     newestUsers { name, image },
     topUser: firstUser (sort: "rank", order: "desc") {
       name,
       projects {
         __type,
         name,
         ...Spreadsheet,
         ...Painting
       }
     }
}

fragment Spreadsheet on SpreadsheetProject {
    rowCount,
    columnCount
}

fragment Paiting on PaintingProject {
    dominantColor { name, hexCode }
}

Raw Output for the :complex-query testcase (here):

Case:  :complex-query
Evaluation count : 1320 in 60 samples of 22 calls.
             Execution time mean : 47.453645 ms
    Execution time std-deviation : 2.097021 ms
   Execution time lower quantile : 45.597164 ms ( 2.5%)
   Execution time upper quantile : 52.667917 ms (97.5%)

Found 3 outliers in 60 samples (5.0000 %)
        low-severe       2 (3.3333 %)
        low-mild         1 (1.6667 %)
 Variance from outliers : 30.3294 % Variance is moderately inflated by outliers

Most of this seems to be instaparse, though, as shown by :complex-query-instaparse-only:

Case:  :complex-query-instaparse-only
Evaluation count : 1380 in 60 samples of 23 calls.
             Execution time mean : 45.664096 ms
    Execution time std-deviation : 333.091255 µs
   Execution time lower quantile : 45.152926 ms ( 2.5%)
   Execution time upper quantile : 46.305994 ms (97.5%)

Anyway, I just wanted to bring this to your attention. Things like query caching would surely help in a production context, but parsing overhead seems to me like something that can't hurt to reduce. Of course, if GraphQL is just a hard to parse language, there's nothing to be done.

Update: There was a problem with the benchmarks, causing a higher mean (~120ms) than the actual result – which is a lot better but 50ms might still contribute massively to resolution times.

The '!' doesn't work in the Input

The library will not throw exception if I pass null value to the required field.
For example, if I define

input TaskInput {
  name: String!,
}

I can set name to null or even don't provide this field at all.

Schema utilities

It would be super helpful if there were utilities for things like merging, diffing and exporting schema.

Don't silently proceed on parse failure

screen shot 2016-10-09 at 17 18 11

It's possible to create a schema despite a parse failure, which leads to more subtle failures ("Field does not exist on Type", etc) when trying to actually use the schema. Instead, the parse failure should throw an exception at some point (preferably the parser/parse or type/create-schema call).

(Incidentally, in this case the parse failure is because the open quote is immediately followed by a newline. Moving the open quote to the first line of the schema string fixes this failure)

Nested input object does not work

Whenever a schema has an input object within another input object, I get a "Expected 'SomeType', found not an object." error. Not sure what's going on.

Given schema:

schema {
  query: Query
}

input TextInput {
  value: String
}

input WorldInput {
  text: TextInput
}

type Query {
  hello(world: WorldInput): String
}

and query:

{
  hello(world: {text: "World"})
}

The response from the server is

  "errors": [
    {
      "error": "Argument 'world' of type 'WorldInput' has invalid value: {text: World}. Reason: Expected 'WorldInput', found not an object.",
      "loc": {
        "line": 2,
        "column": 16
      }
    }
  ]
}

Is it possible to define a custom scalar ?

Hi:

First of all, great work \o/

My question is about scalar types. Sometimes is annoying to be dealing with data conversion in my application logic and I'm wondering if it's possible to register a new scalar converter somehow.

Thanks

Resolver function

Resolver: Provide mapping from (Type, field) -> fn. We need figure out what is good definition of resolver-fn.

Current resolver-fn has definition:
(fn [context parent args])

context: is provided by caller of Executor. It contains any context from execution environment, it will be passed directly to resolver function.

parent: is the resolved result of parent Type.

args: it contains arguments of field, including default value of arguments combined with provided arguments.

Unknown argument ... on field ... of type ...

Given the following schema:

    (def schema-str "schema {
      query: Query
    }

    type Query {
      a: A
      x: X
    }

    type A {
      b: B
    }

    type B {
      c(foo: Int): String
    }

    type X {
      y(foo: Int): String
    }
    ")
(def type-schema (-> schema-str parser/parse validator/validate-schema))

I'm getting validation error for this query:

(-> "{ a { b { c(foo: 1) } } }" parser/parse (validator/validate-statement type-schema))
; => {:errors [{:error "Unknown argument 'foo' on field 'c' of type 'String'.", :loc {:line 1, :column 13}}]}

While this one works:

(-> "{ x { y(foo: 1) } }" parser/parse (validator/validate-statement type-schema))
; => {:document {:operation-definitions [{:section :operation-definitions, :node-type :operation-definition, :operation-type {:type "query"}, :selection-set [{:selection-set [{:field-name "y", :node-type :field, :args-fn #function[clojure.core/constantly/fn--4614], :kind :SCALAR, :parent-type-name "X"}], :field-name "x", :node-type :field, :kind :OBJECT, :parent-type-name "Query"}]}], :type-system-definitions nil, :fragment-definitions nil}}

This is on 0.1.20.

spec/format for "Transformed Map"

Ideally, the flow for graphql engine could be:

Query/Schema String -- Parser --> Parsed AST -- Transformation --> Transformed Map -- Validator --> Validation Error or Validated Map -- Executor --> Executor Error or Result

"Transformed Map" will be used by Validator and Executor for further processing. And also it will make validation and execution pluggable.

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.