• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

ziprandom/graphql-crystal: a graphql implementation for crystal

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

ziprandom/graphql-crystal

开源软件地址(OpenSource Url):

https://github.com/ziprandom/graphql-crystal

开源编程语言(OpenSource Language):

Crystal 100.0%

开源软件介绍(OpenSource Introduction):

graphql-crystal Build Status

An implementation of GraphQL for the crystal programming language inspired by graphql-ruby & go-graphql & graphql-parser.

The library is in beta state atm. Should already be usable but expect to find bugs (and open issues about them). pull-requests, suggestions & criticism are very welcome!

Find the api docs here.

Installation

Add this to your application's shard.yml:

dependencies:
  graphql-crystal:
    github: ziprandom/graphql-crystal

Usage

Complete source here.

Given this simple domain model of users and posts

class User
  property name
  def initialize(@name : String); end
end

class Post
  property :title, :body, :author
  def initialize(@title : String, @body : String, @author : User); end
end

POSTS = [] of Post
USERS = [User.new("Alice"), User.new("Bob")]

We can instantiate a GraphQL schema directly from a graphql schema definition string

schema = GraphQL::Schema.from_schema(
  %{
    schema {
      query: QueryType,
      mutation: MutationType
    }

    type QueryType {
      posts: [PostType]
      users: [UserType]
      user(name: String!): UserType
    }

    type MutationType {
      post(post: PostInput) : PostType
    }

    input PostInput {
      author: String!
      title: String!
      body: String!
    }

    type UserType {
      name: String
      posts: [PostType]
    }

    type PostType {
      author: UserType
      title: String
      body: String
    }
  }
)

Then we create the backing types by including the GraphQL::ObjectType and defining the fields using the field macro

# reopening User and Post class
class User
  include GraphQL::ObjectType

  # defaults to the method of
  # the same name without block
  field :name

  field :posts do
    POSTS.select &.author.==(self)
  end
end

class Post
  include GraphQL::ObjectType
  field :title
  field :body
  field :author
end

Now we define the top level queries

# extend self when using a module or a class (not an instance)
# as the actual Object

module QueryType
  include GraphQL::ObjectType
  extend self

  field :users do
    USERS
  end

  field :user do |args|
    USERS.find( &.name.==(args["name"].as(String)) ) || raise "no user by that name"
  end

  field :posts do
    POSTS
  end
end

module MutationType
  include GraphQL::ObjectType
  extend self

  field :post do |args|

    user = USERS.find &.name.==(
      args["post"].as(Hash)["author"].as(String)
    )
    raise "author doesn't exist" unless user

    (
      POSTS << Post.new(
        args["post"].as(Hash)["title"].as(String),
        args["post"].as(Hash)["body"].as(String),
        user
      )
    ).last
  end
end

Finally set the top level Object Types on the schema

schema.query_resolver = QueryType
schema.mutation_resolver = MutationType

And we are ready to run some tests

describe "my graphql schema" do
  it "does queries" do
    schema.execute("{ users { name posts } }")
      .should eq ({
                    "data" => {
                      "users" => [
                        {
                          "name" => "Alice",
                          "posts" => [] of String
                        },
                        {
                          "name" => "Bob",
                          "posts" => [] of String
                        }
                      ]
                    }
                  })
  end

  it "does mutations" do

    mutation_string = %{
      mutation post($post: PostInput) {
        post(post: $post) {
          author {
            name
            posts { title }
          }
          title
          body
        }
      }
    }

    payload = {
      "post" => {
        "author" =>  "Alice",
        "title" => "the long and windy road",
        "body" => "that leads to your door"
      }
    }

    schema.execute(mutation_string, payload)
      .should eq ({
                    "data" => {
                      "post" => {
                        "title" => "the long and windy road",
                        "body" => "that leads to your door",
                        "author" => {
                          "name" => "Alice",
                          "posts" => [
                            {
                              "title" => "the long and windy road"
                            }
                          ]
                        }
                      }
                    }
                  })
  end
end

Automatic Parsing of JSON Query & Mutation Variables into InputType Structs

To ease working with input parameters custom structs can be registered to be instantiated from the json params of query and mutation requests. Given the schema from above one can define a PostInput struct as follows

struct PostInput < GraphQL::Schema::InputType
  JSON.mapping(
    author: String,
    title: String,
    body: String
  )
end

and register it in the schema like:

schema.add_input_type("PostInput", PostInput)

Now the argument post which is expected to be a GraphQL InputType PostInput will be automatically parsed into a crystal PostInput-struct. Thus the code in the post mutation callback becomes more simple:

module MutationType
  include GraphQL::ObjectType
  extend self

  field :post do |args|
    input = args["post"].as(PostInput)

    author = USERS.find &.name.==(input.author) ||
           raise "author doesn't exist"

    POSTS << Post.new(input.title, input.body, author)
    POSTS.last
  end
end

Custom Context Types

Custom context types can be used to pass additional information to the object type's field resolves. An example can be found here.

A custom context type should inherit from GraphQL::Schema::Context and therefore be initialized with the served schema and a max_depth.

GraphQL::Schema::Schema#execute(query_string, query_arguments = nil, context = GraphQL::Schema::Context.new(self, max_depth))

accepts a context type as its third argument.

Field resolver callbacks on object types (including top level query & mutation types) get called with the context as their second argument:

field :users do |args, context|
  # casting to your custom type
  # is necessary here
  context = context.as(CustomContext)
  unless context.authenticated
    raise "Authentication Error"
  end
  ...
end

Serving over HTTP

For an example of how to serve a schema over a webserver(kemal) see kemal-graphql-example.

Development

run tests with

crystal spec

Contributing

  1. Fork it ( https://github.com/ziprandom/graphql-crystal/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors




鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Volst/graphql-authentication: 发布时间:2022-07-10
下一篇:
jscomplete/graphql-in-action: A GraphQL project with a backend component written ...发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap