graphql 0.11.1 → 0.12.0
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 +5 -2
- data/lib/graphql/argument.rb +8 -3
- data/lib/graphql/base_type.rb +5 -3
- data/lib/graphql/boolean_type.rb +6 -1
- data/lib/graphql/define.rb +8 -0
- data/lib/graphql/define/assign_argument.rb +19 -0
- data/lib/graphql/define/assign_enum_value.rb +16 -0
- data/lib/graphql/define/assign_object_field.rb +19 -0
- data/lib/graphql/define/assignment_dictionary.rb +26 -0
- data/lib/graphql/define/defined_object_proxy.rb +32 -0
- data/lib/graphql/define/instance_definable.rb +79 -0
- data/lib/graphql/{definition_helpers → define}/non_null_with_bang.rb +1 -1
- data/lib/graphql/{definition_helpers → define}/type_definer.rb +1 -1
- data/lib/graphql/directive.rb +7 -2
- data/lib/graphql/enum_type.rb +12 -6
- data/lib/graphql/execution_error.rb +1 -1
- data/lib/graphql/field.rb +26 -23
- data/lib/graphql/float_type.rb +2 -3
- data/lib/graphql/id_type.rb +9 -1
- data/lib/graphql/input_object_type.rb +11 -8
- data/lib/graphql/int_type.rb +2 -1
- data/lib/graphql/interface_type.rb +7 -2
- data/lib/graphql/introspection/input_value_type.rb +10 -1
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/object_type.rb +27 -25
- data/lib/graphql/query.rb +5 -8
- data/lib/graphql/query/serial_execution/field_resolution.rb +0 -10
- data/lib/graphql/scalar_type.rb +4 -3
- data/lib/graphql/schema.rb +1 -1
- data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +5 -4
- data/lib/graphql/static_validation/validator.rb +3 -3
- data/lib/graphql/string_type.rb +2 -1
- data/lib/graphql/union_type.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +4 -10
- data/spec/graphql/boolean_type_spec.rb +20 -0
- data/spec/graphql/define/instance_definable_spec.rb +55 -0
- data/spec/graphql/field_spec.rb +14 -2
- data/spec/graphql/float_type_spec.rb +15 -0
- data/spec/graphql/id_type_spec.rb +9 -0
- data/spec/graphql/int_type_spec.rb +15 -0
- data/spec/graphql/introspection/input_value_type_spec.rb +36 -0
- data/spec/graphql/introspection/type_type_spec.rb +0 -23
- data/spec/graphql/query_spec.rb +16 -0
- data/spec/graphql/static_validation/complexity_validator_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/document_does_not_exceed_max_depth_spec.rb +9 -1
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +3 -2
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +5 -4
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +4 -3
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +4 -3
- data/spec/graphql/static_validation/type_stack_spec.rb +3 -2
- data/spec/graphql/static_validation/validator_spec.rb +3 -25
- data/spec/graphql/string_type_spec.rb +15 -0
- data/spec/support/dairy_app.rb +2 -0
- metadata +25 -9
- data/lib/graphql/definition_helpers.rb +0 -4
- data/lib/graphql/definition_helpers/defined_by_config.rb +0 -123
- data/lib/graphql/definition_helpers/string_named_hash.rb +0 -22
data/lib/graphql/float_type.rb
CHANGED
data/lib/graphql/id_type.rb
CHANGED
@@ -8,12 +8,15 @@
|
|
8
8
|
# end
|
9
9
|
#
|
10
10
|
class GraphQL::InputObjectType < GraphQL::BaseType
|
11
|
-
|
12
|
-
defined_by_config :name, :description, :input_fields
|
13
|
-
alias :arguments :input_fields
|
11
|
+
accepts_definitions input_field: GraphQL::Define::AssignArgument
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
# @return [Hash<String, GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations
|
14
|
+
attr_accessor :arguments
|
15
|
+
|
16
|
+
alias :input_fields :arguments
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@arguments = {}
|
17
20
|
end
|
18
21
|
|
19
22
|
def kind
|
@@ -25,13 +28,13 @@ class GraphQL::InputObjectType < GraphQL::BaseType
|
|
25
28
|
|
26
29
|
# Items in the input that are unexpected
|
27
30
|
input.each do |name, value|
|
28
|
-
if
|
31
|
+
if arguments[name].nil?
|
29
32
|
result.add_problem("Field is not defined on #{self.name}", [name])
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
33
36
|
# Items in the input that are expected, but have invalid values
|
34
|
-
invalid_fields =
|
37
|
+
invalid_fields = arguments.map do |name, field|
|
35
38
|
field_result = field.type.validate_input(input[name])
|
36
39
|
if !field_result.valid?
|
37
40
|
result.merge_result!(name, field_result)
|
@@ -44,7 +47,7 @@ class GraphQL::InputObjectType < GraphQL::BaseType
|
|
44
47
|
def coerce_non_null_input(value)
|
45
48
|
input_values = {}
|
46
49
|
|
47
|
-
|
50
|
+
arguments.each do |input_key, input_field_defn|
|
48
51
|
field_value = value[input_key]
|
49
52
|
field_value = input_field_defn.type.coerce_input(field_value)
|
50
53
|
|
data/lib/graphql/int_type.rb
CHANGED
@@ -12,8 +12,13 @@
|
|
12
12
|
#
|
13
13
|
class GraphQL::InterfaceType < GraphQL::BaseType
|
14
14
|
include GraphQL::BaseType::HasPossibleTypes
|
15
|
-
|
16
|
-
|
15
|
+
accepts_definitions :resolve_type, field: GraphQL::Define::AssignObjectField
|
16
|
+
|
17
|
+
attr_accessor :fields
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@fields = {}
|
21
|
+
end
|
17
22
|
|
18
23
|
def kind
|
19
24
|
GraphQL::TypeKinds::INTERFACE
|
@@ -4,5 +4,14 @@ GraphQL::Introspection::InputValueType = GraphQL::ObjectType.define do
|
|
4
4
|
field :name, !types.String, "The key for this value"
|
5
5
|
field :description, types.String, "What this value is used for"
|
6
6
|
field :type, -> { !GraphQL::Introspection::TypeType }, "The expected type for this value"
|
7
|
-
field :defaultValue, types.String, "The value applied if no other value is provided"
|
7
|
+
field :defaultValue, types.String, "The value applied if no other value is provided" do
|
8
|
+
resolve -> (obj, args, ctx) {
|
9
|
+
value = obj.default_value
|
10
|
+
if value.is_a?(String)
|
11
|
+
"\"#{value}\""
|
12
|
+
else
|
13
|
+
value
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
8
17
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module GraphQL
|
2
2
|
# Raised automatically when a field's resolve function returns `nil`
|
3
3
|
# for a non-null field.
|
4
|
-
class InvalidNullError <
|
4
|
+
class InvalidNullError < GraphQL::Error
|
5
5
|
def initialize(field_name, value)
|
6
6
|
@field_name = field_name
|
7
7
|
@value = value
|
data/lib/graphql/object_type.rb
CHANGED
@@ -1,37 +1,39 @@
|
|
1
1
|
# This type exposes fields on an object.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
3
|
+
# @example defining a type for your IMDB clone
|
4
|
+
# MovieType = GraphQL::ObjectType.define do
|
5
|
+
# name "Movie"
|
6
|
+
# description "A full-length film or a short film"
|
7
|
+
# interfaces [ProductionInterface, DurationInterface]
|
8
8
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
9
|
+
# field :runtimeMinutes, !types.Int, property: :runtime_minutes
|
10
|
+
# field :director, PersonType
|
11
|
+
# field :cast, CastType
|
12
|
+
# field :starring, types[PersonType] do
|
13
|
+
# arguments :limit, types.Int
|
14
|
+
# resolve -> (object, args, ctx) {
|
15
|
+
# stars = object.cast.stars
|
16
|
+
# args[:limit] && stars = stars.limit(args[:limit])
|
17
|
+
# stars
|
18
|
+
# }
|
19
|
+
# end
|
20
|
+
# end
|
21
21
|
#
|
22
22
|
class GraphQL::ObjectType < GraphQL::BaseType
|
23
|
-
|
24
|
-
attr_accessor :name, :description, :interfaces
|
23
|
+
accepts_definitions :interfaces, field: GraphQL::Define::AssignObjectField
|
24
|
+
attr_accessor :name, :description, :interfaces
|
25
25
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
# @return [Hash<String, GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
|
27
|
+
attr_accessor :fields
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@fields = {}
|
31
|
+
@interfaces = []
|
30
32
|
end
|
31
33
|
|
32
|
-
#
|
34
|
+
# Shovel this type into each interface's `possible_types` array.
|
33
35
|
#
|
34
|
-
#
|
36
|
+
# @param new_interfaces [Array<GraphQL::Interface>] interfaces that this type implements
|
35
37
|
def interfaces=(new_interfaces)
|
36
38
|
@interfaces ||= []
|
37
39
|
(@interfaces - new_interfaces).each { |i| i.possible_types.delete(self) }
|
data/lib/graphql/query.rb
CHANGED
@@ -1,16 +1,12 @@
|
|
1
1
|
class GraphQL::Query
|
2
|
-
class OperationNameMissingError <
|
2
|
+
class OperationNameMissingError < GraphQL::Error
|
3
3
|
def initialize(names)
|
4
4
|
msg = "You must provide an operation name from: #{names.join(", ")}"
|
5
5
|
super(msg)
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
# The executor will send the field's name to the target object
|
11
|
-
# and use the result.
|
12
|
-
DEFAULT_RESOLVE = :__default_resolve
|
13
|
-
attr_reader :schema, :document, :context, :fragments, :operations, :debug
|
9
|
+
attr_reader :schema, :document, :context, :fragments, :operations, :debug, :max_depth
|
14
10
|
|
15
11
|
# Prepare query `query_string` on `schema`
|
16
12
|
# @param schema [GraphQL::Schema]
|
@@ -20,9 +16,10 @@ class GraphQL::Query
|
|
20
16
|
# @param debug [Boolean] if true, errors are raised, if false, errors are put in the `errors` key
|
21
17
|
# @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
|
22
18
|
# @param operation_name [String] if the query string contains many operations, this is the one which should be executed
|
23
|
-
def initialize(schema, query_string, context: nil, variables: {}, debug: false, validate: true, operation_name: nil)
|
19
|
+
def initialize(schema, query_string, context: nil, variables: {}, debug: false, validate: true, operation_name: nil, max_depth: nil)
|
24
20
|
@schema = schema
|
25
21
|
@debug = debug
|
22
|
+
@max_depth = max_depth || schema.max_depth
|
26
23
|
@context = Context.new(query: self, values: context)
|
27
24
|
@validate = validate
|
28
25
|
@operation_name = operation_name
|
@@ -72,7 +69,7 @@ class GraphQL::Query
|
|
72
69
|
private
|
73
70
|
|
74
71
|
def validation_errors
|
75
|
-
@validation_errors ||= schema.static_validator.validate(
|
72
|
+
@validation_errors ||= schema.static_validator.validate(self)
|
76
73
|
end
|
77
74
|
|
78
75
|
|
@@ -62,22 +62,12 @@ module GraphQL
|
|
62
62
|
|
63
63
|
|
64
64
|
# Execute the field's resolve method
|
65
|
-
# then handle the DEFAULT_RESOLVE
|
66
65
|
# @return [Proc] suitable to be the last step in a middleware chain
|
67
66
|
def get_middleware_proc_from_field_resolve
|
68
67
|
-> (_parent_type, parent_object, field_definition, field_args, context, _next) {
|
69
68
|
context.ast_node = ast_node
|
70
69
|
value = field_definition.resolve(parent_object, field_args, context)
|
71
70
|
context.ast_node = nil
|
72
|
-
|
73
|
-
if value == GraphQL::Query::DEFAULT_RESOLVE
|
74
|
-
begin
|
75
|
-
value = target.public_send(ast_node.name)
|
76
|
-
rescue NoMethodError => err
|
77
|
-
raise("Couldn't resolve field '#{ast_node.name}' to #{parent_object.class} '#{parent_object}' (resulted in #{err})")
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
71
|
value
|
82
72
|
}
|
83
73
|
end
|
data/lib/graphql/scalar_type.rb
CHANGED
@@ -11,8 +11,7 @@ module GraphQL
|
|
11
11
|
# end
|
12
12
|
#
|
13
13
|
class ScalarType < GraphQL::BaseType
|
14
|
-
|
15
|
-
attr_accessor :name, :description
|
14
|
+
accepts_definitions :coerce, :coerce_input, :coerce_result
|
16
15
|
|
17
16
|
def coerce=(proc)
|
18
17
|
self.coerce_input = proc
|
@@ -21,7 +20,9 @@ module GraphQL
|
|
21
20
|
|
22
21
|
def validate_non_null_input(value)
|
23
22
|
result = Query::InputValidationResult.new
|
24
|
-
|
23
|
+
if coerce_non_null_input(value).nil?
|
24
|
+
result.add_problem("Could not coerce value #{JSON.dump(value)} to #{name}")
|
25
|
+
end
|
25
26
|
result
|
26
27
|
end
|
27
28
|
|
data/lib/graphql/schema.rb
CHANGED
@@ -69,7 +69,7 @@ class GraphQL::Schema
|
|
69
69
|
GraphQL::Schema::TypeExpression.new(self, ast_node).type
|
70
70
|
end
|
71
71
|
|
72
|
-
class InvalidTypeError <
|
72
|
+
class InvalidTypeError < GraphQL::Error
|
73
73
|
def initialize(type, errors)
|
74
74
|
super("Type #{type.respond_to?(:name) ? type.name : "Unnamed type" } is invalid: #{errors.join(", ")}")
|
75
75
|
end
|
@@ -11,10 +11,11 @@ module GraphQL
|
|
11
11
|
# It also provides limited access to the {TypeStack} instance,
|
12
12
|
# which tracks state as you climb in and out of different fields.
|
13
13
|
class ValidationContext
|
14
|
-
attr_reader :schema, :document, :errors, :visitor, :fragments, :operations
|
15
|
-
def initialize(
|
16
|
-
@
|
17
|
-
@
|
14
|
+
attr_reader :query, :schema, :document, :errors, :visitor, :fragments, :operations
|
15
|
+
def initialize(query)
|
16
|
+
@query = query
|
17
|
+
@schema = query.schema
|
18
|
+
@document = query.document
|
18
19
|
@fragments = {}
|
19
20
|
@operations = {}
|
20
21
|
|
@@ -18,12 +18,12 @@ class GraphQL::StaticValidation::Validator
|
|
18
18
|
# Validate `document` against the schema. Returns an array of message hashes.
|
19
19
|
# @param document [GraphQL::Language::Nodes::Document]
|
20
20
|
# @return [Array<Hash>]
|
21
|
-
def validate(
|
22
|
-
context = GraphQL::StaticValidation::ValidationContext.new(
|
21
|
+
def validate(query)
|
22
|
+
context = GraphQL::StaticValidation::ValidationContext.new(query)
|
23
23
|
@rules.each do |rules|
|
24
24
|
rules.new.validate(context)
|
25
25
|
end
|
26
|
-
context.visitor.visit(document)
|
26
|
+
context.visitor.visit(query.document)
|
27
27
|
context.errors.map(&:to_h)
|
28
28
|
end
|
29
29
|
end
|
data/lib/graphql/string_type.rb
CHANGED
data/lib/graphql/union_type.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
class GraphQL::UnionType < GraphQL::BaseType
|
12
12
|
include GraphQL::BaseType::HasPossibleTypes
|
13
13
|
attr_accessor :name, :description, :possible_types
|
14
|
-
|
14
|
+
accepts_definitions :possible_types, :resolve_type
|
15
15
|
|
16
16
|
def kind
|
17
17
|
GraphQL::TypeKinds::UNION
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -134,18 +134,12 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
|
|
134
134
|
|
135
135
|
## To Do
|
136
136
|
|
137
|
-
- Code clean-up
|
138
|
-
- Raise if you try to configure an attribute which doesn't suit the type (ie, if you try to define `resolve` on an ObjectType, it should somehow raise)
|
139
|
-
- make `DefinitionHelpers` more friendly for extension
|
140
137
|
- Interface's possible types should be a property of the schema, not the interface
|
141
138
|
- Type lookup should be by type name (to support reloaded constants in Rails code)
|
142
|
-
- Depth validator should be aware of fragments
|
143
139
|
- Add a complexity validator (reject queries if they're too big)
|
144
|
-
- Add a custom dump for Relay (it expects default value strings to be double-quoted)
|
145
140
|
- Add docs for shared behaviors & DRY code
|
146
141
|
- Optimize the pure-Ruby parser (hand-write, RACC?!)
|
147
|
-
-
|
148
|
-
|
149
|
-
-
|
150
|
-
|
151
|
-
- For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
|
142
|
+
- Revamp the fixture Schema to be more useful (better names, more extensible)
|
143
|
+
- __Subscriptions__
|
144
|
+
- This is a good chance to make an `Operation` abstraction of which `query`, `mutation` and `subscription` are members
|
145
|
+
- For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::BOOLEAN_TYPE do
|
4
|
+
describe "coerce_input" do
|
5
|
+
def coerce_input(input)
|
6
|
+
GraphQL::BOOLEAN_TYPE.coerce_input(input)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "accepts true and false" do
|
10
|
+
assert_equal true, coerce_input(true)
|
11
|
+
assert_equal false, coerce_input(false)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "rejects other types" do
|
15
|
+
assert_equal nil, coerce_input("true")
|
16
|
+
assert_equal nil, coerce_input(5.5)
|
17
|
+
assert_equal nil, coerce_input(nil)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "date"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
module Garden
|
5
|
+
module DefinePlantBetween
|
6
|
+
def self.call(plant, plant_range)
|
7
|
+
plant.start_planting_on = plant_range.begin
|
8
|
+
plant.end_planting_on = plant_range.end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Vegetable
|
13
|
+
attr_accessor :name, :start_planting_on, :end_planting_on
|
14
|
+
include GraphQL::Define::InstanceDefinable
|
15
|
+
accepts_definitions :name, plant_between: DefinePlantBetween
|
16
|
+
|
17
|
+
# definition added later:
|
18
|
+
attr_accessor :height
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe GraphQL::Define::InstanceDefinable do
|
23
|
+
describe "extending definitions" do
|
24
|
+
before do
|
25
|
+
Garden::Vegetable.accepts_definitions(:height)
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
Garden::Vegetable.own_dictionary.delete(:height)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "accepts after-the-fact definitions" do
|
33
|
+
corn = Garden::Vegetable.define do
|
34
|
+
name "Corn"
|
35
|
+
height 8
|
36
|
+
end
|
37
|
+
|
38
|
+
assert_equal "Corn", corn.name
|
39
|
+
assert_equal 8, corn.height
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "applying custom definitions" do
|
44
|
+
it "uses custom callables" do
|
45
|
+
tomato = Garden::Vegetable.define do
|
46
|
+
name "Tomato"
|
47
|
+
plant_between Date.new(2000, 4, 20)..Date.new(2000, 6, 1)
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_equal "Tomato", tomato.name
|
51
|
+
assert_equal Date.new(2000, 4, 20), tomato.start_planting_on
|
52
|
+
assert_equal Date.new(2000, 6, 1), tomato.end_planting_on
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|