graphql 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|