graphql 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql.rb +2 -0
- data/lib/graphql/base_type.rb +2 -16
- data/lib/graphql/introspection/arguments_field.rb +1 -1
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/input_fields_field.rb +1 -1
- data/lib/graphql/introspection/input_value_type.rb +1 -1
- data/lib/graphql/introspection/interfaces_field.rb +1 -1
- data/lib/graphql/introspection/possible_types_field.rb +1 -1
- data/lib/graphql/introspection/type_type.rb +2 -2
- data/lib/graphql/list_type.rb +4 -0
- data/lib/graphql/non_null_type.rb +4 -0
- data/lib/graphql/query/arguments.rb +0 -2
- data/lib/graphql/query/serial_execution/field_resolution.rb +31 -11
- data/lib/graphql/schema.rb +11 -2
- data/lib/graphql/schema/middleware_chain.rb +27 -0
- data/lib/graphql/schema/printer.rb +145 -0
- data/lib/graphql/schema/rescue_middleware.rb +53 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +11 -3
- data/spec/graphql/query/executor_spec.rb +25 -0
- data/spec/graphql/schema/middleware_chain_spec.rb +31 -0
- data/spec/graphql/schema/printer_spec.rb +178 -0
- data/spec/graphql/schema/rescue_middleware_spec.rb +33 -0
- data/spec/graphql/schema_spec.rb +16 -0
- data/spec/support/dairy_app.rb +4 -1
- metadata +14 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e860d8ee6cbcf24b38c43ea7182153e9e1ea6c17
|
4
|
+
data.tar.gz: 6ffcb982ea45a2e3c16fb68726a1c583849f5184
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6990e5ed872ebf7efed61846915a57ed7e21b76ab82ea9c33233bb4747cdb49c435c2e2d2f0a7b8942dbded68800ca54fe517643a9b1bb4a1fb3cfc0f1cd98c4
|
7
|
+
data.tar.gz: a4740639e811cd70e43b01fa22e8a9d35005b5825778bfa1b5ce31940cf1fc9dc8bb2fc2c262579cd2548645bfc982054fa055eae4ea3d9e4c941015f789def5
|
data/lib/graphql.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "json"
|
2
2
|
require "parslet"
|
3
3
|
require "singleton"
|
4
|
+
require "forwardable"
|
4
5
|
|
5
6
|
module GraphQL
|
6
7
|
class ParseError < StandardError
|
@@ -55,6 +56,7 @@ require 'graphql/introspection'
|
|
55
56
|
require 'graphql/language'
|
56
57
|
require 'graphql/directive'
|
57
58
|
require 'graphql/schema'
|
59
|
+
require 'graphql/schema/printer'
|
58
60
|
|
59
61
|
# Order does not matter for these:
|
60
62
|
|
data/lib/graphql/base_type.rb
CHANGED
@@ -59,25 +59,11 @@ module GraphQL
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
# Print the human-readable name of this type
|
62
|
+
# Print the human-readable name of this type using the query-string naming pattern
|
63
63
|
def to_s
|
64
|
-
|
64
|
+
name
|
65
65
|
end
|
66
66
|
|
67
67
|
alias :inspect :to_s
|
68
|
-
|
69
|
-
# Print a type, using the query-style naming pattern
|
70
|
-
class Printer
|
71
|
-
include Singleton
|
72
|
-
def print(type)
|
73
|
-
if type.kind.non_null?
|
74
|
-
"#{print(type.of_type)}!"
|
75
|
-
elsif type.kind.list?
|
76
|
-
"[#{print(type.of_type)}]"
|
77
|
-
else
|
78
|
-
type.name
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
68
|
end
|
83
69
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
GraphQL::Introspection::ArgumentsField = GraphQL::Field.define do
|
2
2
|
description "Arguments allowed to this object"
|
3
|
-
type GraphQL::ListType.new(of_type: GraphQL::Introspection::InputValueType)
|
3
|
+
type !GraphQL::ListType.new(of_type: !GraphQL::Introspection::InputValueType)
|
4
4
|
resolve -> (target, a, c) { target.arguments.values }
|
5
5
|
end
|
@@ -2,7 +2,7 @@ GraphQL::Introspection::FieldType = GraphQL::ObjectType.define do
|
|
2
2
|
name "__Field"
|
3
3
|
description "Field on a GraphQL type"
|
4
4
|
field :name, !types.String, "The name for accessing this field"
|
5
|
-
field :description,
|
5
|
+
field :description, types.String, "The description of this field"
|
6
6
|
field :type, !GraphQL::Introspection::TypeType, "The return type of this field"
|
7
7
|
field :isDeprecated, !types.Boolean, "Is this field deprecated?" do
|
8
8
|
resolve -> (obj, a, c) { !!obj.deprecation_reason }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
GraphQL::Introspection::InputFieldsField = GraphQL::Field.define do
|
2
2
|
name "inputFields"
|
3
3
|
description "fields on this input object"
|
4
|
-
type types[GraphQL::Introspection::InputValueType]
|
4
|
+
type types[!GraphQL::Introspection::InputValueType]
|
5
5
|
resolve -> (target, a, c) {
|
6
6
|
if target.kind.input_object?
|
7
7
|
target.input_fields.values
|
@@ -3,6 +3,6 @@ GraphQL::Introspection::InputValueType = GraphQL::ObjectType.define do
|
|
3
3
|
description "An input for a field or InputObject"
|
4
4
|
field :name, !types.String, "The key for this value"
|
5
5
|
field :description, types.String, "What this value is used for"
|
6
|
-
field :type, -> { GraphQL::Introspection::TypeType }, "The expected type for this value"
|
6
|
+
field :type, -> { !GraphQL::Introspection::TypeType }, "The expected type for this value"
|
7
7
|
field :defaultValue, types.String, "The value applied if no other value is provided", property: :default_value
|
8
8
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
GraphQL::Introspection::InterfacesField = GraphQL::Field.define do
|
2
2
|
description "Interfaces which this object implements"
|
3
|
-
type -> {
|
3
|
+
type -> { types[!GraphQL::Introspection::TypeType] }
|
4
4
|
resolve -> (target, a, c) { target.kind.object? ? target.interfaces : nil }
|
5
5
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
GraphQL::Introspection::PossibleTypesField = GraphQL::Field.define do
|
2
2
|
description "Types which compose this Union or Interface"
|
3
|
-
type -> { types[GraphQL::Introspection::TypeType] }
|
3
|
+
type -> { types[!GraphQL::Introspection::TypeType] }
|
4
4
|
resolve -> (target, a, c) { target.kind.resolves? ? target.possible_types : nil }
|
5
5
|
end
|
@@ -2,11 +2,11 @@ GraphQL::Introspection::TypeType = GraphQL::ObjectType.define do
|
|
2
2
|
name "__Type"
|
3
3
|
description "A type in the GraphQL schema"
|
4
4
|
|
5
|
-
field :name,
|
5
|
+
field :name, types.String, "The name of this type"
|
6
6
|
field :description, types.String, "What this type represents"
|
7
7
|
|
8
8
|
field :kind do
|
9
|
-
type GraphQL::Introspection::TypeKindEnum
|
9
|
+
type !GraphQL::Introspection::TypeKindEnum
|
10
10
|
description "The kind of this type"
|
11
11
|
resolve -> (target, a, c) { target.kind.name }
|
12
12
|
end
|
data/lib/graphql/list_type.rb
CHANGED
@@ -22,6 +22,8 @@ module GraphQL
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
+
# After getting the value from the field's resolve method,
|
26
|
+
# continue by "finishing" the value, eg. executing sub-fields or coercing values
|
25
27
|
def get_finished_value(raw_value)
|
26
28
|
if raw_value.nil?
|
27
29
|
nil
|
@@ -38,20 +40,38 @@ module GraphQL
|
|
38
40
|
end
|
39
41
|
|
40
42
|
|
43
|
+
# Get the result of:
|
44
|
+
# - Any middleware on this schema
|
45
|
+
# - The field's resolve method
|
41
46
|
def get_raw_value
|
42
|
-
query.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
steps = query.schema.middleware + [get_middleware_proc_from_field_resolve]
|
48
|
+
chain = GraphQL::Schema::MiddlewareChain.new(
|
49
|
+
steps: steps,
|
50
|
+
arguments: [parent_type, target, field, arguments, query.context]
|
51
|
+
)
|
52
|
+
chain.call
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Execute the field's resolve method
|
57
|
+
# then handle the DEFAULT_RESOLVE
|
58
|
+
# @return [Proc] suitable to be the last step in a middleware chain
|
59
|
+
def get_middleware_proc_from_field_resolve
|
60
|
+
-> (_parent_type, parent_object, field_definition, field_args, context, _next) {
|
61
|
+
context.ast_node = ast_node
|
62
|
+
value = field_definition.resolve(parent_object, field_args, context)
|
63
|
+
context.ast_node = nil
|
64
|
+
|
65
|
+
if value == GraphQL::Query::DEFAULT_RESOLVE
|
66
|
+
begin
|
67
|
+
value = target.public_send(ast_node.name)
|
68
|
+
rescue NoMethodError => err
|
69
|
+
raise("Couldn't resolve field '#{ast_node.name}' to #{parent_object.class} '#{parent_object}' (resulted in #{err})")
|
70
|
+
end
|
51
71
|
end
|
52
|
-
end
|
53
72
|
|
54
|
-
|
73
|
+
value
|
74
|
+
}
|
55
75
|
end
|
56
76
|
end
|
57
77
|
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# A GraphQL schema which may be queried with {GraphQL::Query}.
|
2
2
|
class GraphQL::Schema
|
3
|
+
extend Forwardable
|
4
|
+
|
3
5
|
DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective]
|
4
6
|
DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
|
5
7
|
|
@@ -7,6 +9,8 @@ class GraphQL::Schema
|
|
7
9
|
# Override these if you don't want the default executor:
|
8
10
|
attr_accessor :query_execution_strategy, :mutation_execution_strategy
|
9
11
|
|
12
|
+
# @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
|
13
|
+
attr_reader :middleware
|
10
14
|
|
11
15
|
# @param query [GraphQL::ObjectType] the query root for the schema
|
12
16
|
# @param mutation [GraphQL::ObjectType, nil] the mutation root for the schema
|
@@ -15,13 +19,16 @@ class GraphQL::Schema
|
|
15
19
|
@mutation = mutation
|
16
20
|
@directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m }
|
17
21
|
@static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
|
22
|
+
@rescue_middleware = GraphQL::Schema::RescueMiddleware.new
|
23
|
+
@middleware = [@rescue_middleware]
|
18
24
|
# Default to the built-in execution strategy:
|
19
25
|
self.query_execution_strategy = GraphQL::Query::SerialExecution
|
20
26
|
self.mutation_execution_strategy = GraphQL::Query::SerialExecution
|
21
27
|
end
|
22
28
|
|
23
|
-
|
24
|
-
|
29
|
+
def_delegators :@rescue_middleware, :rescue_from, :remove_handler
|
30
|
+
|
31
|
+
# @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
|
25
32
|
def types
|
26
33
|
@types ||= TypeReducer.find_all([query, mutation, GraphQL::Introspection::SchemaType].compact)
|
27
34
|
end
|
@@ -61,6 +68,8 @@ end
|
|
61
68
|
require 'graphql/schema/each_item_validator'
|
62
69
|
require 'graphql/schema/field_validator'
|
63
70
|
require 'graphql/schema/implementation_validator'
|
71
|
+
require 'graphql/schema/middleware_chain'
|
72
|
+
require 'graphql/schema/rescue_middleware'
|
64
73
|
require 'graphql/schema/type_reducer'
|
65
74
|
require 'graphql/schema/type_map'
|
66
75
|
require 'graphql/schema/type_validator'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Schema
|
3
|
+
# Given {steps} and {arguments}, call steps in order, passing `(*arguments, next_step)`.
|
4
|
+
#
|
5
|
+
# Steps should call `next_step.call` to continue the chain, or _not_ call it to stop the chain.
|
6
|
+
class MiddlewareChain
|
7
|
+
# @return [Array<#call(*args)>] Steps in this chain, will be called with arguments and `next_middleware`
|
8
|
+
attr_reader :steps
|
9
|
+
|
10
|
+
# @return [Array] Arguments passed to steps (followed by `next_middleware`)
|
11
|
+
attr_reader :arguments
|
12
|
+
|
13
|
+
def initialize(steps:, arguments:)
|
14
|
+
# We're gonna destroy this array, so copy it:
|
15
|
+
@steps = steps.dup
|
16
|
+
@arguments = arguments
|
17
|
+
end
|
18
|
+
|
19
|
+
# Run the next step in the chain, passing in arguments and handle to the next step
|
20
|
+
def call
|
21
|
+
next_step = steps.shift
|
22
|
+
next_middleware = self
|
23
|
+
next_step.call(*arguments, next_middleware)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module GraphQL
|
2
|
+
# Used to convert your {GraphQL::Schema} to a GraphQL schema string
|
3
|
+
#
|
4
|
+
# @example print your schema to standard output
|
5
|
+
# Schema = GraphQL::Schema.new(query: QueryType)
|
6
|
+
# puts GraphQL::Schema::Printer.print_schema(Schema)
|
7
|
+
#
|
8
|
+
module Schema::Printer
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# Return a GraphQL schema string for the defined types in the schema
|
12
|
+
# @param schema [GraphQL::Schema]
|
13
|
+
def print_schema(schema)
|
14
|
+
print_filtered_schema(schema, method(:is_defined_type))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the GraphQL schema string for the introspection type system
|
18
|
+
def print_introspection_schema
|
19
|
+
query_root = ObjectType.define do
|
20
|
+
name "Query"
|
21
|
+
end
|
22
|
+
schema = Schema.new(query: query_root)
|
23
|
+
print_filtered_schema(schema, method(:is_introspection_type))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def print_filtered_schema(schema, type_filter)
|
29
|
+
types = schema.types.values.select{ |type| type_filter.call(type) }.sort_by(&:name)
|
30
|
+
types.map{ |type| print_type(type) }.join("\n\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
BUILTIN_SCALARS = Set.new(["String", "Boolean", "Int", "Float", "ID"])
|
34
|
+
private_constant :BUILTIN_SCALARS
|
35
|
+
|
36
|
+
def is_introspection_type(type)
|
37
|
+
type.name.start_with?("__")
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_defined_type(type)
|
41
|
+
!is_introspection_type(type) && !BUILTIN_SCALARS.include?(type.name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def print_type(type)
|
45
|
+
TypeKindPrinters::STRATEGIES.fetch(type.kind).print(type)
|
46
|
+
end
|
47
|
+
|
48
|
+
module TypeKindPrinters
|
49
|
+
module FieldPrinter
|
50
|
+
def print_fields(type)
|
51
|
+
type.fields.values.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_args(field)
|
55
|
+
return if field.arguments.empty?
|
56
|
+
"(#{field.arguments.values.map{ |arg| print_input_value(arg) }.join(", ")})"
|
57
|
+
end
|
58
|
+
|
59
|
+
def print_input_value(arg)
|
60
|
+
default_string = " = #{print_value(arg.default_value, arg.type)}" unless arg.default_value.nil?
|
61
|
+
"#{arg.name}: #{arg.type.to_s}#{default_string}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def print_value(value, type)
|
65
|
+
case type
|
66
|
+
when FLOAT_TYPE
|
67
|
+
value.to_f.inspect
|
68
|
+
when INT_TYPE
|
69
|
+
value.to_i.inspect
|
70
|
+
when BOOLEAN_TYPE
|
71
|
+
(!!value).inspect
|
72
|
+
when ScalarType, ID_TYPE, STRING_TYPE
|
73
|
+
value.to_s.inspect
|
74
|
+
when EnumType
|
75
|
+
value.to_s
|
76
|
+
when InputObjectType
|
77
|
+
fields = value.to_h.map{ |field_name, field_value|
|
78
|
+
field_type = type.input_fields.fetch(field_name.to_s).type
|
79
|
+
"#{field_name}: #{print_value(field_value, field_type)}"
|
80
|
+
}.join(", ")
|
81
|
+
"{ #{fields} }"
|
82
|
+
when NonNullType
|
83
|
+
print_value(value, type.of_type)
|
84
|
+
when ListType
|
85
|
+
"[#{value.to_a.map{ |v| print_value(v, type.of_type) }.join(", ")}]"
|
86
|
+
else
|
87
|
+
raise NotImplementedError, "Unexpected value type #{type.inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class ScalarPrinter
|
93
|
+
def self.print(type)
|
94
|
+
"scalar #{type.name}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class ObjectPrinter
|
99
|
+
extend FieldPrinter
|
100
|
+
def self.print(type)
|
101
|
+
implementations = " implements #{type.interfaces.map(&:to_s).join(", ")}" unless type.interfaces.empty?
|
102
|
+
"type #{type.name}#{implementations} {\n#{print_fields(type)}\n}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class InterfacePrinter
|
107
|
+
extend FieldPrinter
|
108
|
+
def self.print(type)
|
109
|
+
"interface #{type.name} {\n#{print_fields(type)}\n}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class UnionPrinter
|
114
|
+
def self.print(type)
|
115
|
+
"union #{type.name} = #{type.possible_types.map(&:to_s).join(" | ")}\n}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class EnumPrinter
|
120
|
+
def self.print(type)
|
121
|
+
values = type.values.values.map{ |v| " #{v.name}" }.join("\n")
|
122
|
+
"enum #{type.name} {\n#{values}\n}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class InputObjectPrinter
|
127
|
+
extend FieldPrinter
|
128
|
+
def self.print(type)
|
129
|
+
fields = type.input_fields.values.map{ |field| " #{print_input_value(field)}" }.join("\n")
|
130
|
+
"input #{type.name} {\n#{fields}\n}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
STRATEGIES = {
|
135
|
+
GraphQL::TypeKinds::SCALAR => ScalarPrinter,
|
136
|
+
GraphQL::TypeKinds::OBJECT => ObjectPrinter,
|
137
|
+
GraphQL::TypeKinds::INTERFACE => InterfacePrinter,
|
138
|
+
GraphQL::TypeKinds::UNION => UnionPrinter,
|
139
|
+
GraphQL::TypeKinds::ENUM => EnumPrinter,
|
140
|
+
GraphQL::TypeKinds::INPUT_OBJECT => InputObjectPrinter,
|
141
|
+
}
|
142
|
+
end
|
143
|
+
private_constant :TypeKindPrinters
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Schema
|
3
|
+
# - Store a table of errors & handlers
|
4
|
+
# - Rescue errors in a middleware chain, then check for a handler
|
5
|
+
# - If a handler is found, use it & return a {GraphQL::ExecutionError}
|
6
|
+
# - If no handler is found, re-raise the error
|
7
|
+
class RescueMiddleware
|
8
|
+
# @return [Hash] `{class => proc}` pairs for handling errors
|
9
|
+
attr_reader :rescue_table
|
10
|
+
def initialize
|
11
|
+
@rescue_table = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# @example Rescue from not-found by telling the user
|
15
|
+
# MySchema.rescue_from(ActiveRecord::NotFound) { "An item could not be found" }
|
16
|
+
#
|
17
|
+
# @param [Class] a class of error to rescue from
|
18
|
+
# @yield [err] A handler to return a message for this error instance
|
19
|
+
# @yieldparam [Exception] an error that was rescued
|
20
|
+
# @yieldreturn [String] message to put in GraphQL response
|
21
|
+
def rescue_from(error_class, &block)
|
22
|
+
rescue_table[error_class] = block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Remove the handler for `error_class`
|
26
|
+
# @param [Class] the error class whose handler should be removed
|
27
|
+
def remove_handler(error_class)
|
28
|
+
rescue_table.delete(error_class)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Implement the requirement for {GraphQL::Schema::MiddlewareChain}
|
32
|
+
def call(*args, next_middleware)
|
33
|
+
begin
|
34
|
+
next_middleware.call
|
35
|
+
rescue StandardError => err
|
36
|
+
attempt_rescue(err)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def attempt_rescue(err)
|
43
|
+
handler = rescue_table[err.class]
|
44
|
+
if handler
|
45
|
+
message = handler.call(err)
|
46
|
+
GraphQL::ExecutionError.new(message)
|
47
|
+
else
|
48
|
+
raise(err)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -95,11 +95,19 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
95
95
|
- Raise if you try to configure an attribute which doesn't suit the type
|
96
96
|
- ie, if you try to define `resolve` on an ObjectType, it should somehow raise
|
97
97
|
- Incoming enums should be exposed as `EnumValue`s, not `Nodes::Enum`s
|
98
|
+
- Overriding `!` on types breaks ActiveSupport `.blank?`
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
my_type = GraphQL::ObjectType.define { name("MyType") }
|
102
|
+
# => MyType
|
103
|
+
my_type.present?
|
104
|
+
# => MyType!!
|
105
|
+
my_type.blank?
|
106
|
+
# => MyType!
|
107
|
+
```
|
108
|
+
|
98
109
|
- Big ideas:
|
99
110
|
- Use [graphql-parser](https://github.com/shopify/graphql-parser) (Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser)) instead of Parslet
|
100
|
-
- Add instrumentation
|
101
|
-
- Some way to expose what queries are run, what types & fields are accessed, how long things are taking, etc
|
102
|
-
- before-hooks for every field?
|
103
111
|
|
104
112
|
## Goals
|
105
113
|
|
@@ -131,5 +131,30 @@ describe GraphQL::Query::Executor do
|
|
131
131
|
assert_raises(RuntimeError) { result }
|
132
132
|
end
|
133
133
|
end
|
134
|
+
|
135
|
+
describe "if the schema has a rescue handler" do
|
136
|
+
before do
|
137
|
+
schema.rescue_from(RuntimeError) { "Error was handled!" }
|
138
|
+
end
|
139
|
+
|
140
|
+
after do
|
141
|
+
# remove the handler from the middleware:
|
142
|
+
schema.remove_handler(RuntimeError)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "adds to the errors key" do
|
146
|
+
expected = {
|
147
|
+
"data" => {"error" => nil},
|
148
|
+
"errors"=>[
|
149
|
+
{
|
150
|
+
"message"=>"Error was handled!",
|
151
|
+
"locations" => [{"line"=>1, "column"=>17}]
|
152
|
+
}
|
153
|
+
]
|
154
|
+
}
|
155
|
+
assert_equal(expected, result)
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
134
159
|
end
|
135
160
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema::MiddlewareChain do
|
4
|
+
let(:step_1) { -> (step_values, next_step) { step_values << 1; next_step.call } }
|
5
|
+
let(:step_2) { -> (step_values, next_step) { step_values << 2; next_step.call } }
|
6
|
+
let(:step_3) { -> (step_values, next_step) { step_values << 3; :return_value } }
|
7
|
+
let(:steps) { [step_1, step_2, step_3] }
|
8
|
+
let(:step_values) { [] }
|
9
|
+
let(:arguments) { [step_values] }
|
10
|
+
let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: steps, arguments: arguments)}
|
11
|
+
|
12
|
+
describe "#call" do
|
13
|
+
it "runs steps in order" do
|
14
|
+
middleware_chain.call
|
15
|
+
assert_equal([1,2,3], step_values)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns the value of the last middleware" do
|
19
|
+
assert_equal(:return_value, middleware_chain.call)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "when a step returns early" do
|
23
|
+
let(:early_return_step) { -> (step_values, next_step) { :early_return } }
|
24
|
+
it "doesn't continue the chain" do
|
25
|
+
steps.insert(2, early_return_step)
|
26
|
+
assert_equal(:early_return, middleware_chain.call)
|
27
|
+
assert_equal([1,2], step_values)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema::Printer do
|
4
|
+
let(:schema) {
|
5
|
+
node_type = GraphQL::InterfaceType.define do
|
6
|
+
name "Node"
|
7
|
+
|
8
|
+
field :id, !types.ID
|
9
|
+
end
|
10
|
+
|
11
|
+
choice_type = GraphQL::EnumType.define do
|
12
|
+
name "Choice"
|
13
|
+
|
14
|
+
value "FOO"
|
15
|
+
value "BAR"
|
16
|
+
end
|
17
|
+
|
18
|
+
sub_input_type = GraphQL::InputObjectType.define do
|
19
|
+
name "Sub"
|
20
|
+
input_field :string, types.String
|
21
|
+
end
|
22
|
+
|
23
|
+
variant_input_type = GraphQL::InputObjectType.define do
|
24
|
+
name "Varied"
|
25
|
+
input_field :id, types.ID
|
26
|
+
input_field :int, types.Int
|
27
|
+
input_field :float, types.Float
|
28
|
+
input_field :bool, types.Boolean
|
29
|
+
input_field :enum, choice_type
|
30
|
+
input_field :sub, types[sub_input_type]
|
31
|
+
end
|
32
|
+
|
33
|
+
comment_type = GraphQL::ObjectType.define do
|
34
|
+
name "Comment"
|
35
|
+
description "A blog comment"
|
36
|
+
interfaces [node_type]
|
37
|
+
|
38
|
+
field :id, !types.ID
|
39
|
+
end
|
40
|
+
|
41
|
+
post_type = GraphQL::ObjectType.define do
|
42
|
+
name "Post"
|
43
|
+
description "A blog post"
|
44
|
+
|
45
|
+
field :id, !types.ID
|
46
|
+
field :title, !types.String
|
47
|
+
field :body, !types.String
|
48
|
+
field :comments, types[!comment_type]
|
49
|
+
end
|
50
|
+
|
51
|
+
query_root = GraphQL::ObjectType.define do
|
52
|
+
name "Query"
|
53
|
+
description "The query root of this schema"
|
54
|
+
|
55
|
+
field :post do
|
56
|
+
type post_type
|
57
|
+
argument :id, !types.ID
|
58
|
+
argument :varied, variant_input_type, default_value: { id: "123", int: 234, float: 2.3, enum: "FOO", sub: [{ string: "str" }] }
|
59
|
+
resolve -> (obj, args, ctx) { Post.find(args["id"]) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
GraphQL::Schema.new(query: query_root)
|
64
|
+
}
|
65
|
+
|
66
|
+
describe ".print_introspection_schema" do
|
67
|
+
it "returns the schema as a string for the introspection types" do
|
68
|
+
expected = <<SCHEMA
|
69
|
+
type __Directive {
|
70
|
+
name: String!
|
71
|
+
description: String
|
72
|
+
args: [__InputValue!]!
|
73
|
+
onOperation: Boolean!
|
74
|
+
onFragment: Boolean!
|
75
|
+
onField: Boolean!
|
76
|
+
}
|
77
|
+
|
78
|
+
type __EnumValue {
|
79
|
+
name: String!
|
80
|
+
description: String
|
81
|
+
deprecationReason: String
|
82
|
+
isDeprecated: Boolean!
|
83
|
+
}
|
84
|
+
|
85
|
+
type __Field {
|
86
|
+
name: String!
|
87
|
+
description: String
|
88
|
+
type: __Type!
|
89
|
+
isDeprecated: Boolean!
|
90
|
+
args: [__InputValue!]!
|
91
|
+
deprecationReason: String
|
92
|
+
}
|
93
|
+
|
94
|
+
type __InputValue {
|
95
|
+
name: String!
|
96
|
+
description: String
|
97
|
+
type: __Type!
|
98
|
+
defaultValue: String
|
99
|
+
}
|
100
|
+
|
101
|
+
type __Schema {
|
102
|
+
types: [__Type!]!
|
103
|
+
directives: [__Directive!]!
|
104
|
+
queryType: __Type!
|
105
|
+
mutationType: __Type
|
106
|
+
}
|
107
|
+
|
108
|
+
type __Type {
|
109
|
+
name: String
|
110
|
+
description: String
|
111
|
+
kind: __TypeKind!
|
112
|
+
fields(includeDeprecated: Boolean = false): [__Field!]
|
113
|
+
ofType: __Type
|
114
|
+
inputFields: [__InputValue!]
|
115
|
+
possibleTypes: [__Type!]
|
116
|
+
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
|
117
|
+
interfaces: [__Type!]
|
118
|
+
}
|
119
|
+
|
120
|
+
enum __TypeKind {
|
121
|
+
SCALAR
|
122
|
+
OBJECT
|
123
|
+
INTERFACE
|
124
|
+
UNION
|
125
|
+
ENUM
|
126
|
+
INPUT_OBJECT
|
127
|
+
LIST
|
128
|
+
NON_NULL
|
129
|
+
}
|
130
|
+
SCHEMA
|
131
|
+
assert_equal expected.chomp, GraphQL::Schema::Printer.print_introspection_schema
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe ".print_schema" do
|
136
|
+
it "returns the schema as a string for the defined types" do
|
137
|
+
expected = <<SCHEMA
|
138
|
+
enum Choice {
|
139
|
+
FOO
|
140
|
+
BAR
|
141
|
+
}
|
142
|
+
|
143
|
+
type Comment implements Node {
|
144
|
+
id: ID!
|
145
|
+
}
|
146
|
+
|
147
|
+
interface Node {
|
148
|
+
id: ID!
|
149
|
+
}
|
150
|
+
|
151
|
+
type Post {
|
152
|
+
id: ID!
|
153
|
+
title: String!
|
154
|
+
body: String!
|
155
|
+
comments: [Comment!]
|
156
|
+
}
|
157
|
+
|
158
|
+
type Query {
|
159
|
+
post(id: ID!, varied: Varied = { id: \"123\", int: 234, float: 2.3, enum: FOO, sub: [{ string: \"str\" }] }): Post
|
160
|
+
}
|
161
|
+
|
162
|
+
input Sub {
|
163
|
+
string: String
|
164
|
+
}
|
165
|
+
|
166
|
+
input Varied {
|
167
|
+
id: ID
|
168
|
+
int: Int
|
169
|
+
float: Float
|
170
|
+
bool: Boolean
|
171
|
+
enum: Choice
|
172
|
+
sub: [Sub]
|
173
|
+
}
|
174
|
+
SCHEMA
|
175
|
+
assert_equal expected.chomp, GraphQL::Schema::Printer.print_schema(schema)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class SpecExampleError < StandardError; end
|
4
|
+
|
5
|
+
describe GraphQL::Schema::RescueMiddleware do
|
6
|
+
let(:error_middleware) { -> (next_middleware) { raise(error_class) } }
|
7
|
+
|
8
|
+
let(:rescue_middleware) do
|
9
|
+
middleware = GraphQL::Schema::RescueMiddleware.new
|
10
|
+
middleware.rescue_from(SpecExampleError) { |err| "there was an example error: #{err.class.name}" }
|
11
|
+
middleware
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:steps) { [rescue_middleware, error_middleware] }
|
15
|
+
|
16
|
+
let(:middleware_chain) { GraphQL::Schema::MiddlewareChain.new(steps: steps, arguments: [])}
|
17
|
+
|
18
|
+
describe "known errors" do
|
19
|
+
let(:error_class) { SpecExampleError }
|
20
|
+
it "handles them as execution errors" do
|
21
|
+
result = middleware_chain.call
|
22
|
+
assert_equal("there was an example error: SpecExampleError", result.message)
|
23
|
+
assert_equal(GraphQL::ExecutionError, result.class)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "unknown errors" do
|
28
|
+
let(:error_class) { RuntimeError }
|
29
|
+
it "re-raises them" do
|
30
|
+
assert_raises(RuntimeError) { middleware_chain.call }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema do
|
4
|
+
let(:schema) { DummySchema }
|
5
|
+
|
6
|
+
describe "#rescue_from" do
|
7
|
+
let(:rescue_middleware) { schema.middleware.first }
|
8
|
+
|
9
|
+
it "adds handlers to the rescue middleware" do
|
10
|
+
assert_equal(1, rescue_middleware.rescue_table.length)
|
11
|
+
# normally, you'd use a real class, not a symbol:
|
12
|
+
schema.rescue_from(:error_class) { "my custom message" }
|
13
|
+
assert_equal(2, rescue_middleware.rescue_table.length)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/support/dairy_app.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative './dairy_data'
|
2
2
|
|
3
|
+
class NoSuchDairyError < StandardError; end
|
4
|
+
|
3
5
|
EdibleInterface = GraphQL::InterfaceType.define do
|
4
6
|
name "Edible"
|
5
7
|
description "Something you can eat, yum"
|
@@ -42,7 +44,7 @@ CheeseType = GraphQL::ObjectType.define do
|
|
42
44
|
# get the strings out:
|
43
45
|
sources = a["source"].map(&:name)
|
44
46
|
if sources.include?("YAK")
|
45
|
-
|
47
|
+
raise NoSuchDairyError.new("No cheeses are made from Yak milk!")
|
46
48
|
else
|
47
49
|
CHEESES.values.find { |c| sources.include?(c.source) }
|
48
50
|
end
|
@@ -218,3 +220,4 @@ MutationType = GraphQL::ObjectType.define do
|
|
218
220
|
end
|
219
221
|
|
220
222
|
DummySchema = GraphQL::Schema.new(query: QueryType, mutation: MutationType)
|
223
|
+
DummySchema.rescue_from(NoSuchDairyError) { |err| err.message }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|
@@ -224,6 +224,9 @@ files:
|
|
224
224
|
- lib/graphql/schema/each_item_validator.rb
|
225
225
|
- lib/graphql/schema/field_validator.rb
|
226
226
|
- lib/graphql/schema/implementation_validator.rb
|
227
|
+
- lib/graphql/schema/middleware_chain.rb
|
228
|
+
- lib/graphql/schema/printer.rb
|
229
|
+
- lib/graphql/schema/rescue_middleware.rb
|
227
230
|
- lib/graphql/schema/type_map.rb
|
228
231
|
- lib/graphql/schema/type_reducer.rb
|
229
232
|
- lib/graphql/schema/type_validator.rb
|
@@ -275,8 +278,12 @@ files:
|
|
275
278
|
- spec/graphql/query/type_resolver_spec.rb
|
276
279
|
- spec/graphql/query_spec.rb
|
277
280
|
- spec/graphql/schema/field_validator_spec.rb
|
281
|
+
- spec/graphql/schema/middleware_chain_spec.rb
|
282
|
+
- spec/graphql/schema/printer_spec.rb
|
283
|
+
- spec/graphql/schema/rescue_middleware_spec.rb
|
278
284
|
- spec/graphql/schema/type_reducer_spec.rb
|
279
285
|
- spec/graphql/schema/type_validator_spec.rb
|
286
|
+
- spec/graphql/schema_spec.rb
|
280
287
|
- spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb
|
281
288
|
- spec/graphql/static_validation/rules/arguments_are_defined_spec.rb
|
282
289
|
- spec/graphql/static_validation/rules/directives_are_defined_spec.rb
|
@@ -321,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
321
328
|
version: '0'
|
322
329
|
requirements: []
|
323
330
|
rubyforge_project:
|
324
|
-
rubygems_version: 2.
|
331
|
+
rubygems_version: 2.4.5
|
325
332
|
signing_key:
|
326
333
|
specification_version: 4
|
327
334
|
summary: A GraphQL implementation for Ruby
|
@@ -346,8 +353,12 @@ test_files:
|
|
346
353
|
- spec/graphql/query/type_resolver_spec.rb
|
347
354
|
- spec/graphql/query_spec.rb
|
348
355
|
- spec/graphql/schema/field_validator_spec.rb
|
356
|
+
- spec/graphql/schema/middleware_chain_spec.rb
|
357
|
+
- spec/graphql/schema/printer_spec.rb
|
358
|
+
- spec/graphql/schema/rescue_middleware_spec.rb
|
349
359
|
- spec/graphql/schema/type_reducer_spec.rb
|
350
360
|
- spec/graphql/schema/type_validator_spec.rb
|
361
|
+
- spec/graphql/schema_spec.rb
|
351
362
|
- spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb
|
352
363
|
- spec/graphql/static_validation/rules/arguments_are_defined_spec.rb
|
353
364
|
- spec/graphql/static_validation/rules/directives_are_defined_spec.rb
|