ziprandom / graphql-crystal Goto Github PK
View Code? Open in Web Editor NEWa graphql implementation for crystal
License: MIT License
a graphql implementation for crystal
License: MIT License
Hey, I'm wondering if it can be used in production and if it's faster than the go version 1F9VowF8vVFcCHoNdaTZAXGT5H4gJ9mo35
Also, if the parser is x50 times slower, why isn't changed with the c bindings to libgraphqlparser??
Please add a version 0.1.0 release due to shards specs.
When libraries are installed from Git repositories, the repository is expected to have version tags following the semver format, prefixed with a v. Examples: v1.2.3 or v2.0.0-rc1.
I use https://github.com/skevy/graphiql-app
request with params
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
...
}
fragment InputValue on __InputValue {
...
}
fragment TypeRef on __Type {
...
}
return
{"data":{"__schema":{...}, "errors":[{"message":"field not defined.","path":["__schema","subscriptionType"]}]}
Should be without the "errors" key or with the "errors" key but without the "__schema" key
See please here and here
Validate struct should be before execution code in app
also method max_depth should ignore this request
Sorry for my English
While working on a PR for this issue, I found another issue. When running this in a test:
TestSchema::Schema.execute(
"query getAddresses($city: [City]!) { addresses(city: $city) { city } }", {
"city" => ["London"]
}
)
I get this error:
{
"data" => nil,
"errors" => [{
"message" => "variable $city is expected to be of type [City]!", "path" => []
}]
}
I guess somewhere the raw string values of variables needs to be converted to enum values before the type check, or else the type check for enum needs to allow String.
I'm having trouble seeing where this would be fixed. If you can point me in the right direction, I can add this fix to my upcoming PR.
I'm just getting started with graphql-crystal and have created a simple kemal app based on the README sample here. Do I need to do something special to get introspection working?
With this query:
{
__schema {
types {
name
}
}
}
I get this result:
{
"data": {
"__schema": null
},
"errors": [
{
"message": "field __schema is not defined for Class",
"path": [
"__schema"
]
}
]
}
Here's the execution:
post "/graph" do |env|
query = env.params.json["query"].as(String)
SCHEMA.execute(query).to_json
end
Thanks for the help.
AFAIK Crystal allows to bind C libraries; according to the benchmarks in README, current implementation is kinda slow. Maybe reimplement with C bindings?
hi,
using both graphql-crystal
and crecto
generates conflicts on the class.
It looks like they both add FIELDS
to the model
error:
already initialized constant User::FIELDS
Is there a way to avoid this conflict?
This might be more of a feature request than a bug. I'm not sure.
I'm trying to use graphql-crystal
with jennifer ORM. My type looks like this:
class Post < Jennifer::Model::Base
mapping(
id: Primary32,
title: String,
body: String
user_id: Int32,
)
belongs_to :user, User
has_many :comments, Comment
include GraphQL::ObjectType
field :id
field :title
field :body
field :comments
end
And I get this compile time error:
Error in src/app.cr:33: instantiating 'GraphQL::Schema::Schema#execute(String, Hash(String, JSON::Type)+)'
SCHEMA.execute query, variables.as(Hash)
^~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:34: instantiating 'execute(String, Hash(String, JSON::Type)+, Nil, GraphQL::Schema::Context)'
def execute(document : String, params = nil, operation_name = nil, context = Context.new(self, max_depth))
^
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:35: instantiating 'execute(GraphQL::Language::Document, Hash(String, JSON::Type)+, Nil, GraphQL::Schema::Context)'
execute(Language.parse(document), params, operation_name, context)
^~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:78: instantiating '(GraphQL::ObjectType | Nil)#try()'
{query_resolver, @types[query_resolver.try &.graphql_type]}
^~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:78: undefined method 'graphql_type' for Jennifer::Migration::Version (compile-time type is Jennifer::Model::Base+)
{query_resolver, @types[query_resolver.try &.graphql_type]}
^~~~~~~~~~~~
I've tried various things like re-ordering the graphql and jennifer bits. I also tried closing and re-opening the class to add the graphql bits (as shown in the readme), but to no avail.
and ... this project can not even compiled now ...
macro didn't expand to a valid program, it expanded to:
================================================================================
--------------------------------------------------------------------------------
1.
2. private def __typename_field(nil, context)
3.
4. context.with_self(nil) do
5. self.graphql_type
6. end
7.
8. end
9.
--------------------------------------------------------------------------------
Syntax error in expanded macro: field:2: cannot use 'nil' as an argument name
private def __typename_field(nil, context)
I think it should be allowed to omit optional fields from variables. It's seems it's not possible.
Query:
query getShows($date: String!, $location: LocationInput, $favorites: Boolean) {
shows(date: $date, location: $location, favorites: $favorites) {
id
}
}
Variables:
{
"date": "2018-03-08"
}
Server Schema
type Query {
shows(date: String!, location: LocationInput, favorites: Boolean): [Show]
}
Result:
{
"data": null,
"errors": [
{
"message": "missing variable location, missing variable favorites",
"path": []
}
]
}
The query succeeds with the following:
{
"date": "2018-03-08",
"location": null,
"favorites": null
}
From the docs:
When default values are provided for all variables, you can call the query without passing any variables.
Okay, so I maybe I have to specify defaults of null:
Query:
query getShows($date: String!, $location: LocationInput = null, $favorites: Boolean = null) {
shows(date: $date, location: $location, favorites: $favorites) {
id
}
}
Nope:
{
"data": null,
"errors": [
{
"message": "variable $location is expected to be of type LocationInput, variable $favorites is expected to be of type Boolean",
"path": []
}
]
}
Hello! I was wondering if there is anyway to pass context until blocks in SCHEMA.resolve
?
To be clearer, here is my use case, depending on the bearer passed in the request header, i know my user and his role, so i can allow him to acceed to the mutation or query called.
or, maybe the answer is to handle authentication in another way in graphql.
The following code does not work because "field :dir do ... " is set to return a class (::MyTest::Dir
) instead of an instance (::MyTest::Dir.new
).
module MyTest
schema = GraphQL::Schema.from_schema(%{
schema {
query: RootType,
mutation: RootType
}
type RootType {
dir: DirType
}
type DirType {
current: String
}
})
class Dir
include GraphQL::ObjectType
field :current do
::Dir.current
end
end
module RootType
include GraphQL::ObjectType
extend self
field :dir do
::MyTest::Dir
end
end
schema.query_resolver = ::MyTest::RootType
schema.mutation_resolver = ::MyTest::RootType
print schema.execute %Q{
query {
dir { current }
}
}
end
The behavior is the same if the class is converted to a module, but it is in any case unexpected because on the top of the tree RootType
is a module and it works there.
Is this a design decision or unhandled case?
https://graphql.github.io/graphql-spec/June2018/#sec-Descriptions
The example throws a CLTK::Parser::Exceptions::NotInLanguage
exception.
We already have add_input_types
to the schema which allows us to use structs to define inputs. Can we possibly do this for all other things? Such as types, interfaces & fragments.
Before I go diving in the code base, I wanted to see if there a reason it wasn't implemented such as certain limitations.
Hello. Maybe i missed it in the documentation, is there a way to customise error adding fields?
At some point, I realized I was using a pretty outdated version of this lib. The type conversion from JSON::Any
didn't work for me but I saw an example where it supposed to work. Anyway, I figured out I can let it run if I point to master in dependencies config like below. There are quite useful commits in the latest master. How about making a release?
dependencies:
graphql-crystal:
github: ziprandom/graphql-crystal
branch: master
Crystal 0.24.1 (2017-12-22)
LLVM: 4.0.0
Default target: x86_64-unknown-linux-gnu
Empty project,
running crystal spec produces:
can't cast CLTK::Type to GraphQL::Language::ArgumentValue
Hi @ziprandom,
First of all thanks for the great work on this!
I'm trying to use it for a project but i'm having a rough time understanding what i'm doing wrong.
So the idea is i'm integrating your lib and fetching data from a database. I'm using Crecto as the database wrapper and i'm defining my models as such:
module Breaktime::Api
class University < Crecto::Model
include GraphQL::ObjectType
property id
field :id
property name
field :name
property short_name
field :short_name
field :bands do
uni = Repo.get(University, self.id).as(University)
bands = Repo.get_association(uni, :bands).as(Array(Band))
bands
end
schema "universities" do
field :name, String
field :short_name, String
has_many :bands, Band, dependent: :destroy
end
end
end
module Breaktime::Api
class Band < Crecto::Model
include GraphQL::ObjectType
property id
field :id
property name
field :name
property bio
field :bio
property university
field :university
schema "bands" do
field :name, String
field :bio, String
belongs_to :university, University
end
end
end
If i declare just the University class without the Band class and association i'm having no issues and i'm able to fetch that data without problems from the database.
But when i add the Band class/association then i start getting into these errors:
Error in src/breaktime-api.cr:44: instantiating 'GraphQL::Schema::Schema#execute(Tuple(String, Hash(String, JSON::Type) | Nil))'
SCHEMA.execute(
^~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:34: instantiating 'execute(String, (Hash(String, JSON::Type) | Nil), Nil, GraphQL::Schema::Context)'
def execute(document : String, params = nil, operation_name = nil, context = Context.new(self, max_depth))
^
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:35: instantiating 'execute(GraphQL::Language::Document, (Hash(String, JSON::Type) | Nil), Nil, GraphQL::Schema::Context)'
execute(Language.parse(document), params, operation_name, context)
^~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:83: instantiating 'resolve_selections_for(GraphQL::Language::TypeDefinition+, Array(GraphQL::Language::AbstractNode), (GraphQL::ObjectType | Nil), GraphQL::Schema::Context)'
result, errors = resolve_selections_for(
^~~~~~~~~~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:138: instantiating '_resolve_selections_for(GraphQL::Language::TypeDefinition+, Array(GraphQL::Language::AbstractNode), (GraphQL::ObjectType | Nil), GraphQL::Schema::Context)'
_resolve_selections_for(field_definition, _selections, resolved, context)
^~~~~~~~~~~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:216: instantiating 'resolve_selections_for((GraphQL::Language::FieldDefinition | Nil), Array(GraphQL::Language::Field), GraphQL::ObjectType, GraphQL::Schema::Context)'
_result, _errors = resolve_selections_for(
^~~~~~~~~~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:138: instantiating '_resolve_selections_for((GraphQL::Language::FieldDefinition | Nil), Array(GraphQL::Language::AbstractNode), GraphQL::ObjectType, GraphQL::Schema::Context)'
_resolve_selections_for(field_definition, _selections, resolved, context)
^~~~~~~~~~~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:272: instantiating 'resolve_selections_for(GraphQL::Language::AbstractNode+, Array(GraphQL::Language::AbstractNode), (Array(Breaktime::Api::Band) | Array(Breaktime::Api::University) | Array(GraphQL::Language::Directive) | Array(GraphQL::Language::DirectiveDefinition) | Array(GraphQL::Language::EnumValueDefinition) | Array(GraphQL::Language::FieldDefinition) | Array(GraphQL::Language::InputValueDefinition) | Array(GraphQL::Language::InterfaceTypeDefinition) | Array(GraphQL::Language::TypeDefinition) | Array(String) | Bool | Crecto::Model | GraphQL::Language::AbstractNode | GraphQL::Schema::Schema | Int32 | Int64 | String | Time | Nil), GraphQL::Schema::Context)'
resolve_selections_for(
^~~~~~~~~~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:138: instantiating '_resolve_selections_for(GraphQL::Language::AbstractNode+, Array(GraphQL::Language::AbstractNode), (Array(Breaktime::Api::Band) | Array(Breaktime::Api::University) | Array(GraphQL::Language::Directive) | Array(GraphQL::Language::DirectiveDefinition) | Array(GraphQL::Language::EnumValueDefinition) | Array(GraphQL::Language::FieldDefinition) | Array(GraphQL::Language::InputValueDefinition) | Array(GraphQL::Language::InterfaceTypeDefinition) | Array(GraphQL::Language::TypeDefinition) | Array(String) | Bool | Crecto::Model | GraphQL::Language::AbstractNode | GraphQL::Schema::Schema | Int32 | Int64 | String | Time | Nil), GraphQL::Schema::Context)'
_resolve_selections_for(field_definition, _selections, resolved, context)
^~~~~~~~~~~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:210: instantiating 'run_directives(GraphQL::Language::Field, GraphQL::Language::FieldDefinition, Array(GraphQL::Language::AbstractNode), (Crecto::Model | GraphQL::Language::AbstractNode | GraphQL::Schema::Schema), GraphQL::Schema::Context)'
run_directives(
^~~~~~~~~~~~~~
in lib/graphql-crystal/src/graphql-crystal/schema/schema_execute.cr:119: can't cast Tuple(GraphQL::Language::FieldDefinition, Array(GraphQL::Language::AbstractNode), Crecto::Model | GraphQL::Language::AbstractNode | GraphQL::Schema::Schema, GraphQL::Schema::Context) to Tuple(GraphQL::Language::AbstractNode, Array(GraphQL::Language::AbstractNode), GraphQL::ResolveCBReturnType, GraphQL::Schema::Context)
block.call(*args.as(Args))
Funny thing is if i declare an extra model for Band (the Bnd class seen below) just with your stuff and without Crecto it works (i just have to transfer the data from the Band to the Bnd model on every query:
class Bnd
include GraphQL::ObjectType
property id
field :id
property name
field :name
property bio
field :bio
property university
field :university
def initialize(
@id : Int64? | Int32?,
@name : String | Nil,
@bio : String | Nil,
@university : University?); end
end
I'm kinda new to Crystal and even GraphQL so it's possible i'm doing something wrong. What strikes me as odd is that it works with the University model isolated (and inheriting from Crecto::Model) and works when i declare the temp class Bnd. But not with the Band (inheriting from Crecto::Model).
What am i doing wrong? If you need access to the full source code let me know.
Once again thank you for your time!
Can we declare schema in one place?
Your kemal example has schema_string and type declaration:
# A User
type User implements UniqueId {
# users first name
firstName: String!
# users last name
lastName: String!
# full name string for the user
fullName: String! @deprecated(reason: "no need to construct this serverside..")
# users role
role: UserRole!
# posts published
# by this user
posts: [Post!]
# total number of posts
# published by this user
postsCount: Int!
}
class User
include ::GraphQL::ObjectType
include UniqueId
field :firstName { first_name }
field :lastName { last_name }
field :fullName { "#{@first_name} #{@last_name}" }
field :posts { POSTS.select &.author.==(self)}
field :postsCount { POSTS.select( &.author.==(self) ).size }
field :role
end
rmosolgo/graphql-ruby uses some LateBoundType but it is magic.
Can we add to schema.cr some simple method like:
from_paths(paths : Array)
::GraphQL::Schema.from_paths(['path1', 'path2'])
Wouldn't porting the ruby implementaion to crystal be better? There's no need to port the go parser since it doesn't offer the same parity as ruby.
It appears that even though GraphQL supports case-sensitivity, creating fields with first letter uppercase does not work here because it generates invalid function names:
field :Dir do ... end
Results in:
Error in src/x.cr:32: expanding macro
field :"Dir" do
^
in macro 'field' expanded macro: injection:1, line 1:
> 1. field(:Dir, "", args, "") do
2. ::MyTest::Dir.new
3. end
4.
macro didn't expand to a valid program, it expanded to:
================================================================================
--------------------------------------------------------------------------------
1.
2. private def Dir_field(args, context)
3.
4. context.with_self(args) do
5. ::MyTest::Dir.new
6. end
7.
8. end
9.
--------------------------------------------------------------------------------
Syntax error in expanded macro: field:2: unexpected token: (
private def Dir_field(args, context)
Would a simple fix be to generate functions named field_NAME
instead of NAME_field
?
Hi, im using your shard in a private project and so far it works great (errors in fields are little difficult to track down though). A thing that is a bit annoying is the casting of the context in every field. Would it be possible to make the Schema-class or the ObjectType-class generics so one does not need to cast the context?
I have an issue when I try to make a mutation with an empty string as a value of a create/update.
This is the Schema =>
GRAPHQL_SCHEMA = GraphQL::Schema.from_schema(
%{
schema {
query: QueryType,
mutation: MutationType
}
type QueryType {
meeting(id: String!): Meeting
}
type Meeting {
id: String!
title: String
}
type MutationType {
update_meeting(id: String!, meeting: MeetingInputType!): Meeting
}
}
)
Everything works like a charm =>
GRAPHQL_SCHEMA.execute("mutation { update_meeting(id: \"11\", meeting: {title: \"a\"}) {id}}", nil, nil, graphql_context)
=> {"data" => {"update_meeting" => {"id" => "11"}}}
even
GRAPHQL_SCHEMA.execute("mutation { update_meeting(id: \"11\", meeting: {title: null}) {id}}", nil, nil, graphql_context)
=> {"data" => {"update_meeting" => {"id" => "11"}}}
but when it comes to empty strings, I got
GRAPHQL_SCHEMA.execute("mutation { update_meeting(id: \"11\", meeting: {title: \"\"}) {id}}", nil, nil, graphql_context)
Unhandled exception: (CLTK::Parser::Exceptions::NotInLanguage)
from GraphQL::Language::Parser@CLTK::Parser::_parse<Hash(Int32, Tuple(CLTK::Parser::ProdProc, Int32)), Hash(Int32, String), Array(String), Array(CLTK::Parser::State), Hash(String, Array(Proc(CLTK::Parser::Environment, Nil))), Array(CLTK::Token), NamedTuple()>:(Array(CLTK::Type) | Bool | Float64 | GraphQL::Language::AbstractNode+ | Int32 | String | Tuple(String, String) | Nil)
from GraphQL::Language::Parser@CLTK::Parser::parse<Array(CLTK::Token), NamedTuple()>:(Array(CLTK::Type) | Bool | Float64 | GraphQL::Language::AbstractNode+ | Int32 | String | Tuple(String, String) | Nil)
from GraphQL::Language::parse<String, NamedTuple()>:GraphQL::Language::Document
from GraphQL::Language::parse<String>:GraphQL::Language::Document
from GraphQL::Schema::Schema#execute<String, Nil, Nil, CustomContext>:(Hash(String, Array(GraphQL::ReturnType) | Array(Hash(String, Array(Int32 | String) | String) | Nil) | Bool | Float64 | GraphQL::Schema::InputType+ | Hash(String, GraphQL::ReturnType) | Int32 | Int64 | String | Nil) | Hash(String, Array(GraphQL::ReturnType) | Bool | Float64 | GraphQL::Schema::InputType+ | Hash(String, GraphQL::ReturnType) | Int32 | Int64 | String | Nil) | Hash(String, Array(Hash(String, Array(String) | String | Nil)) | Nil) | Hash(String, Array(Hash(String, Array(String) | String))))
from __icr_exec__:(Hash(String, Array(GraphQL::ReturnType) | Array(Hash(String, Array(Int32 | String) | String) | Nil) | Bool | Float64 | GraphQL::Schema::InputType+ | Hash(String, GraphQL::ReturnType) | Int32 | Int64 | String | Nil) | Hash(String, Array(GraphQL::ReturnType) | Bool | Float64 | GraphQL::Schema::InputType+ | Hash(String, GraphQL::ReturnType) | Int32 | Int64 | String | Nil) | Hash(String, Array(Hash(String, Array(String) | String | Nil)) | Nil) | Hash(String, Array(Hash(String, Array(String) | String))))
from __crystal_main
from Crystal::main_user_code<Int32, Pointer(Pointer(UInt8))>:Nil
from Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
from main
Is this a bug ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.