graphql 1.0.0 → 1.1.0

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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +10 -0
  3. data/lib/graphql/base_type.rb +8 -5
  4. data/lib/graphql/compatibility.rb +3 -0
  5. data/lib/graphql/compatibility/execution_specification.rb +414 -0
  6. data/lib/graphql/compatibility/query_parser_specification.rb +117 -0
  7. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +81 -0
  8. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +78 -0
  9. data/lib/graphql/compatibility/schema_parser_specification.rb +239 -0
  10. data/lib/graphql/define/instance_definable.rb +53 -21
  11. data/lib/graphql/directive.rb +1 -1
  12. data/lib/graphql/enum_type.rb +31 -8
  13. data/lib/graphql/execution/directive_checks.rb +0 -6
  14. data/lib/graphql/input_object_type.rb +6 -4
  15. data/lib/graphql/introspection/arguments_field.rb +3 -1
  16. data/lib/graphql/introspection/enum_values_field.rb +10 -5
  17. data/lib/graphql/introspection/fields_field.rb +1 -1
  18. data/lib/graphql/introspection/input_fields_field.rb +2 -2
  19. data/lib/graphql/introspection/interfaces_field.rb +7 -1
  20. data/lib/graphql/introspection/possible_types_field.rb +1 -1
  21. data/lib/graphql/introspection/schema_type.rb +1 -1
  22. data/lib/graphql/introspection/type_by_name_field.rb +4 -2
  23. data/lib/graphql/introspection/type_type.rb +7 -6
  24. data/lib/graphql/language/lexer.rl +0 -4
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language/parser.y +1 -1
  27. data/lib/graphql/list_type.rb +3 -4
  28. data/lib/graphql/non_null_type.rb +4 -8
  29. data/lib/graphql/object_type.rb +5 -3
  30. data/lib/graphql/query.rb +48 -12
  31. data/lib/graphql/query/context.rb +7 -1
  32. data/lib/graphql/query/serial_execution/execution_context.rb +8 -3
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +8 -5
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +2 -2
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +4 -21
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +59 -99
  37. data/lib/graphql/query/variables.rb +7 -2
  38. data/lib/graphql/scalar_type.rb +1 -1
  39. data/lib/graphql/schema.rb +49 -18
  40. data/lib/graphql/schema/build_from_definition.rb +248 -0
  41. data/lib/graphql/schema/instrumented_field_map.rb +23 -0
  42. data/lib/graphql/schema/loader.rb +4 -11
  43. data/lib/graphql/schema/possible_types.rb +4 -2
  44. data/lib/graphql/schema/printer.rb +1 -1
  45. data/lib/graphql/schema/type_expression.rb +4 -4
  46. data/lib/graphql/schema/type_map.rb +1 -1
  47. data/lib/graphql/schema/validation.rb +4 -0
  48. data/lib/graphql/schema/warden.rb +114 -0
  49. data/lib/graphql/static_validation/literal_validator.rb +10 -7
  50. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -3
  51. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  52. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  53. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -14
  54. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  55. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  56. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -4
  57. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +2 -1
  58. data/lib/graphql/static_validation/validation_context.rb +7 -1
  59. data/lib/graphql/union_type.rb +6 -3
  60. data/lib/graphql/unresolved_type_error.rb +1 -2
  61. data/lib/graphql/version.rb +1 -1
  62. data/readme.md +1 -5
  63. data/spec/graphql/compatibility/execution_specification_spec.rb +3 -0
  64. data/spec/graphql/compatibility/query_parser_specification_spec.rb +5 -0
  65. data/spec/graphql/compatibility/schema_parser_specification_spec.rb +5 -0
  66. data/spec/graphql/define/instance_definable_spec.rb +20 -0
  67. data/spec/graphql/directive_spec.rb +11 -0
  68. data/spec/graphql/enum_type_spec.rb +20 -1
  69. data/spec/graphql/input_object_type_spec.rb +9 -9
  70. data/spec/graphql/introspection/directive_type_spec.rb +4 -4
  71. data/spec/graphql/introspection/input_value_type_spec.rb +6 -6
  72. data/spec/graphql/introspection/type_type_spec.rb +28 -26
  73. data/spec/graphql/language/parser_spec.rb +27 -17
  74. data/spec/graphql/list_type_spec.rb +2 -2
  75. data/spec/graphql/query/variables_spec.rb +1 -0
  76. data/spec/graphql/scalar_type_spec.rb +3 -3
  77. data/spec/graphql/schema/build_from_definition_spec.rb +693 -0
  78. data/spec/graphql/schema/type_expression_spec.rb +3 -3
  79. data/spec/graphql/schema/validation_spec.rb +7 -3
  80. data/spec/graphql/schema/warden_spec.rb +510 -0
  81. data/spec/graphql/schema_spec.rb +129 -0
  82. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
  83. data/spec/graphql/static_validation/type_stack_spec.rb +3 -3
  84. data/spec/spec_helper.rb +27 -1
  85. data/spec/support/dairy_app.rb +8 -5
  86. metadata +21 -3
  87. data/lib/graphql/language/parser_tests.rb +0 -809
@@ -56,17 +56,23 @@ module GraphQL
56
56
  # # Access it from metadata
57
57
  # subaru_baja.metadata[:all_wheel_drive] # => true
58
58
  #
59
+ # @example Making a copy with an extended definition
60
+ # # Create an instance with `.define`:
61
+ # subaru_baja = Car.define do
62
+ # make "Subaru"
63
+ # model "Baja"
64
+ # doors 4
65
+ # end
66
+ #
67
+ # # Then extend it with `#redefine`
68
+ # two_door_baja = subaru_baja.redefine do
69
+ # doors 2
70
+ # end
59
71
  module InstanceDefinable
60
72
  def self.included(base)
61
73
  base.extend(ClassMethods)
62
74
  end
63
75
 
64
- # Set the definition block for this instance.
65
- # It can be run later with {#ensure_defined}
66
- def definition_proc=(defn_block)
67
- @definition_proc = defn_block
68
- end
69
-
70
76
  # `metadata` can store arbitrary key-values with an object.
71
77
  #
72
78
  # @return [Hash<Object, Object>] Hash for user-defined storage
@@ -84,19 +90,21 @@ module GraphQL
84
90
  def define(**kwargs, &block)
85
91
  # make sure the previous definition_proc was executed:
86
92
  ensure_defined
87
-
88
- @definition_proc = ->(obj) {
89
- kwargs.each do |keyword, value|
90
- public_send(keyword, value)
91
- end
92
-
93
- if block
94
- instance_eval(&block)
95
- end
96
- }
93
+ @pending_definition = Definition.new(kwargs, block)
97
94
  nil
98
95
  end
99
96
 
97
+ # Make a new instance of this class, then
98
+ # re-run any definitions on that object.
99
+ # @return [InstanceDefinable] A new instance, with any extended definitions
100
+ def redefine(**kwargs, &block)
101
+ ensure_defined
102
+ new_instance = self.class.new
103
+ applied_definitions.each { |defn| defn.apply(new_instance) }
104
+ new_instance.define(**kwargs, &block)
105
+ new_instance
106
+ end
107
+
100
108
  private
101
109
 
102
110
  # Run the definition block if it hasn't been run yet.
@@ -105,15 +113,39 @@ module GraphQL
105
113
  # come from the definition block.
106
114
  # @return [void]
107
115
  def ensure_defined
108
- if @definition_proc
109
- defn_proc = @definition_proc
110
- @definition_proc = nil
111
- proxy = DefinedObjectProxy.new(self)
112
- proxy.instance_eval(&defn_proc)
116
+ if @pending_definition
117
+ defn = @pending_definition
118
+ @pending_definition = nil
119
+ defn.apply(self)
120
+ applied_definitions << defn
113
121
  end
114
122
  nil
115
123
  end
116
124
 
125
+ def applied_definitions
126
+ @applied_definitions ||= []
127
+ end
128
+
129
+
130
+ class Definition
131
+ def initialize(define_keywords, define_proc)
132
+ @define_keywords = define_keywords
133
+ @define_proc = define_proc
134
+ end
135
+
136
+ def apply(instance)
137
+ defn_proxy = DefinedObjectProxy.new(instance)
138
+
139
+ @define_keywords.each do |keyword, value|
140
+ defn_proxy.public_send(keyword, value)
141
+ end
142
+
143
+ if @define_proc
144
+ defn_proxy.instance_eval(&@define_proc)
145
+ end
146
+ end
147
+ end
148
+
117
149
  module ClassMethods
118
150
  # Create a new instance
119
151
  # and prepare a definition using its {.definitions}.
@@ -7,7 +7,7 @@ module GraphQL
7
7
  #
8
8
  class Directive
9
9
  include GraphQL::Define::InstanceDefinable
10
- accepts_definitions :locations, :name, :description, argument: GraphQL::Define::AssignArgument
10
+ accepts_definitions :locations, :name, :description, :arguments, argument: GraphQL::Define::AssignArgument
11
11
 
12
12
  lazy_defined_attr_accessor :locations, :arguments, :name, :description
13
13
 
@@ -55,25 +55,37 @@ module GraphQL
55
55
  # args[:favoriteLanguage] # => :rb
56
56
  # }
57
57
  #
58
+ # @example Enum whose values are different in ActiveRecord-land
59
+ # class Language < ActiveRecord::BaseType
60
+ # enum language: {
61
+ # rb: 0
62
+ # }
63
+ # end
64
+ #
65
+ # # Now enum type should be defined as
66
+ # GraphQL::EnumType.define do
67
+ # # ...
68
+ # # use the `value:` keyword:
69
+ # value("RUBY", "Lisp? Smalltalk?", value: 'rb')
70
+ # end
71
+ #
72
+
58
73
  class EnumType < GraphQL::BaseType
59
74
  accepts_definitions :values, value: GraphQL::Define::AssignEnumValue
60
75
 
61
76
  def initialize
62
77
  @values_by_name = {}
63
- @values_by_value = {}
64
78
  end
65
79
 
66
80
  # @param new_values [Array<EnumValue>] The set of values contained in this type
67
81
  def values=(new_values)
68
82
  @values_by_name = {}
69
- @values_by_value = {}
70
83
  new_values.each { |enum_value| add_value(enum_value) }
71
84
  end
72
85
 
73
86
  # @param enum_value [EnumValue] A value to add to this type's set of values
74
87
  def add_value(enum_value)
75
88
  @values_by_name[enum_value.name] = enum_value
76
- @values_by_value[enum_value.value] = enum_value
77
89
  end
78
90
 
79
91
  # @return [Hash<String => EnumValue>] `{name => value}` pairs contained in this type
@@ -86,12 +98,14 @@ module GraphQL
86
98
  GraphQL::TypeKinds::ENUM
87
99
  end
88
100
 
89
- def validate_non_null_input(value_name)
101
+ def validate_non_null_input(value_name, warden)
90
102
  ensure_defined
91
103
  result = GraphQL::Query::InputValidationResult.new
104
+ allowed_values = warden.enum_values(self)
105
+ matching_value = allowed_values.find { |v| v.name == value_name }
92
106
 
93
- if !@values_by_name.key?(value_name)
94
- result.add_problem("Expected #{JSON.generate(value_name, quirks_mode: true)} to be one of: #{@values_by_name.keys.join(', ')}")
107
+ if matching_value.nil?
108
+ result.add_problem("Expected #{JSON.generate(value_name, quirks_mode: true)} to be one of: #{allowed_values.join(', ')}")
95
109
  end
96
110
 
97
111
  result
@@ -114,9 +128,15 @@ module GraphQL
114
128
  end
115
129
  end
116
130
 
117
- def coerce_result(value)
131
+ def coerce_result(value, warden = nil)
118
132
  ensure_defined
119
- @values_by_value.fetch(value).name
133
+ all_values = warden ? warden.enum_values(self) : @values_by_name.each_value
134
+ enum_value = all_values.find { |val| val.value == value }
135
+ if enum_value
136
+ enum_value.name
137
+ else
138
+ raise(UnresolvedValueError, "Can't resolve enum #{name} for #{value}")
139
+ end
120
140
  end
121
141
 
122
142
  def to_s
@@ -132,5 +152,8 @@ module GraphQL
132
152
 
133
153
  lazy_defined_attr_accessor :name, :description, :deprecation_reason, :value
134
154
  end
155
+
156
+ class UnresolvedValueError < GraphQL::Error
157
+ end
135
158
  end
136
159
  end
@@ -8,12 +8,6 @@ module GraphQL
8
8
 
9
9
  module_function
10
10
 
11
- # This covers `@include(if:)` & `@skip(if:)`
12
- # @return [Boolean] Should this node be skipped altogether?
13
- def skip?(ast_node, query)
14
- !include?(ast_node, query)
15
- end
16
-
17
11
  # @return [Boolean] Should this node be included in the query?
18
12
  def include?(directive_irep_nodes, query)
19
13
  directive_irep_nodes.each do |directive_irep_node|
@@ -47,19 +47,21 @@ module GraphQL
47
47
  GraphQL::TypeKinds::INPUT_OBJECT
48
48
  end
49
49
 
50
- def validate_non_null_input(input)
50
+ def validate_non_null_input(input, warden)
51
51
  result = GraphQL::Query::InputValidationResult.new
52
52
 
53
+ visible_arguments_map = warden.input_fields(self).reduce({}) { |m, f| m[f.name] = f; m}
54
+
53
55
  # Items in the input that are unexpected
54
56
  input.each do |name, value|
55
- if arguments[name].nil?
57
+ if visible_arguments_map[name].nil?
56
58
  result.add_problem("Field is not defined on #{self.name}", [name])
57
59
  end
58
60
  end
59
61
 
60
62
  # Items in the input that are expected, but have invalid values
61
- invalid_fields = arguments.map do |name, field|
62
- field_result = field.type.validate_input(input[name])
63
+ invalid_fields = visible_arguments_map.map do |name, field|
64
+ field_result = field.type.validate_input(input[name], warden)
63
65
  if !field_result.valid?
64
66
  result.merge_result!(name, field_result)
65
67
  end
@@ -1,4 +1,6 @@
1
1
  GraphQL::Introspection::ArgumentsField = GraphQL::Field.define do
2
2
  type !GraphQL::ListType.new(of_type: !GraphQL::Introspection::InputValueType)
3
- resolve ->(target, a, c) { target.arguments.values }
3
+ resolve ->(obj, args, ctx) {
4
+ ctx.warden.arguments(obj)
5
+ }
4
6
  end
@@ -2,11 +2,16 @@ GraphQL::Introspection::EnumValuesField = GraphQL::Field.define do
2
2
  type types[!GraphQL::Introspection::EnumValueType]
3
3
  argument :includeDeprecated, types.Boolean, default_value: false
4
4
  resolve ->(object, arguments, context) do
5
- return nil if !object.kind.enum?
6
- fields = object.values.values
7
- if !arguments["includeDeprecated"]
8
- fields = fields.select {|f| !f.deprecation_reason }
5
+ if !object.kind.enum?
6
+ nil
7
+ else
8
+ enum_values = context.warden.enum_values(object)
9
+
10
+ if !arguments["includeDeprecated"]
11
+ enum_values = enum_values.select {|f| !f.deprecation_reason }
12
+ end
13
+
14
+ enum_values
9
15
  end
10
- fields
11
16
  end
12
17
  end
@@ -3,7 +3,7 @@ GraphQL::Introspection::FieldsField = GraphQL::Field.define do
3
3
  argument :includeDeprecated, GraphQL::BOOLEAN_TYPE, default_value: false
4
4
  resolve ->(object, arguments, context) {
5
5
  return nil if !object.kind.fields?
6
- fields = object.all_fields
6
+ fields = context.warden.fields(object)
7
7
  if !arguments["includeDeprecated"]
8
8
  fields = fields.select {|f| !f.deprecation_reason }
9
9
  end
@@ -1,9 +1,9 @@
1
1
  GraphQL::Introspection::InputFieldsField = GraphQL::Field.define do
2
2
  name "inputFields"
3
3
  type types[!GraphQL::Introspection::InputValueType]
4
- resolve ->(target, a, c) {
4
+ resolve ->(target, a, ctx) {
5
5
  if target.kind.input_object?
6
- target.input_fields.values
6
+ ctx.warden.input_fields(target)
7
7
  else
8
8
  nil
9
9
  end
@@ -1,4 +1,10 @@
1
1
  GraphQL::Introspection::InterfacesField = GraphQL::Field.define do
2
2
  type -> { types[!GraphQL::Introspection::TypeType] }
3
- resolve ->(target, a, c) { target.kind.object? ? target.interfaces : nil }
3
+ resolve ->(target, a, ctx) {
4
+ if target.kind == GraphQL::TypeKinds::OBJECT
5
+ ctx.warden.interfaces(target)
6
+ else
7
+ nil
8
+ end
9
+ }
4
10
  end
@@ -2,7 +2,7 @@ GraphQL::Introspection::PossibleTypesField = GraphQL::Field.define do
2
2
  type -> { types[!GraphQL::Introspection::TypeType] }
3
3
  resolve ->(target, args, ctx) {
4
4
  if target.kind.resolves?
5
- ctx.schema.possible_types(target)
5
+ ctx.warden.possible_types(target)
6
6
  else
7
7
  nil
8
8
  end
@@ -5,7 +5,7 @@ GraphQL::Introspection::SchemaType = GraphQL::ObjectType.define do
5
5
  "query, mutation, and subscription operations."
6
6
 
7
7
  field :types, !types[!GraphQL::Introspection::TypeType], "A list of all types supported by this server." do
8
- resolve ->(obj, arg, ctx) { obj.types.values }
8
+ resolve -> (obj, arg, ctx) { ctx.warden.types }
9
9
  end
10
10
 
11
11
  field :queryType, !GraphQL::Introspection::TypeType, "The type that query operations will be rooted at." do
@@ -2,13 +2,15 @@ module GraphQL
2
2
  module Introspection
3
3
  # A wrapper to create `__type(name: )` dynamically.
4
4
  class TypeByNameField
5
- def self.create(type_hash)
5
+ def self.create(schema)
6
6
  GraphQL::Field.define do
7
7
  name("__type")
8
8
  description("A type in the GraphQL system")
9
9
  type(GraphQL::Introspection::TypeType)
10
10
  argument :name, !types.String
11
- resolve ->(o, args, c) { type_hash.fetch(args["name"], nil) }
11
+ resolve ->(o, args, ctx) {
12
+ ctx.warden.get_type(args["name"])
13
+ }
12
14
  end
13
15
  end
14
16
  end
@@ -12,12 +12,13 @@ GraphQL::Introspection::TypeType = GraphQL::ObjectType.define do
12
12
  type !GraphQL::Introspection::TypeKindEnum
13
13
  resolve ->(target, a, c) { target.kind.name }
14
14
  end
15
+
15
16
  field :name, types.String
16
17
  field :description, types.String
17
- field :fields, field: GraphQL::Introspection::FieldsField
18
- field :interfaces, field: GraphQL::Introspection::InterfacesField
19
- field :possibleTypes, field: GraphQL::Introspection::PossibleTypesField
20
- field :enumValues, field: GraphQL::Introspection::EnumValuesField
21
- field :inputFields, field: GraphQL::Introspection::InputFieldsField
22
- field :ofType, field: GraphQL::Introspection::OfTypeField
18
+ field :fields, GraphQL::Introspection::FieldsField
19
+ field :interfaces, GraphQL::Introspection::InterfacesField
20
+ field :possibleTypes, GraphQL::Introspection::PossibleTypesField
21
+ field :enumValues, GraphQL::Introspection::EnumValuesField
22
+ field :inputFields, GraphQL::Introspection::InputFieldsField
23
+ field :ofType, GraphQL::Introspection::OfTypeField
23
24
  end
@@ -109,7 +109,6 @@ module GraphQL
109
109
  # To avoid allocating more strings, this modifies the string passed into it
110
110
  def self.replace_escaped_characters_in_place(raw_string)
111
111
  raw_string.gsub!(ESCAPES, ESCAPES_REPLACE)
112
- raw_string.gsub!(UTF_8, &UTF_8_REPLACE)
113
112
  nil
114
113
  end
115
114
 
@@ -178,9 +177,6 @@ module GraphQL
178
177
  "\\t" => "\t",
179
178
  }
180
179
 
181
- UTF_8 = /\\u[\dAa-f]{4}/i
182
- UTF_8_REPLACE = ->(m) { [m[-4..-1].to_i(16)].pack('U'.freeze) }
183
-
184
180
  def self.emit_string(ts, te, meta)
185
181
  value = meta[:data][ts...te].pack("c*").force_encoding("UTF-8")
186
182
  if value =~ /\\u|\\./ && value !~ ESCAPES
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  def parse_document
22
22
  @document ||= begin
23
- @tokens ||= GraphQL::Language::Lexer.tokenize(@query_string)
23
+ @tokens ||= GraphQL.scan(@query_string)
24
24
  if @tokens.none?
25
25
  make_node(:Document, definitions: [])
26
26
  else
@@ -348,7 +348,7 @@ end
348
348
 
349
349
  def parse_document
350
350
  @document ||= begin
351
- @tokens ||= GraphQL::Language::Lexer.tokenize(@query_string)
351
+ @tokens ||= GraphQL.scan(@query_string)
352
352
  if @tokens.none?
353
353
  make_node(:Document, definitions: [])
354
354
  else
@@ -25,9 +25,8 @@ module GraphQL
25
25
  #
26
26
  class ListType < GraphQL::BaseType
27
27
  include GraphQL::BaseType::ModifiesAnotherType
28
- attr_reader :of_type, :name
28
+ attr_reader :of_type
29
29
  def initialize(of_type:)
30
- @name = "List"
31
30
  @of_type = of_type
32
31
  end
33
32
 
@@ -39,11 +38,11 @@ module GraphQL
39
38
  "[#{of_type.to_s}]"
40
39
  end
41
40
 
42
- def validate_non_null_input(value)
41
+ def validate_non_null_input(value, warden)
43
42
  result = GraphQL::Query::InputValidationResult.new
44
43
 
45
44
  ensure_array(value).each_with_index do |item, index|
46
- item_result = of_type.validate_input(item)
45
+ item_result = of_type.validate_input(item, warden)
47
46
  if !item_result.valid?
48
47
  result.merge_result!(index, item_result)
49
48
  end
@@ -35,21 +35,17 @@ module GraphQL
35
35
  @of_type = of_type
36
36
  end
37
37
 
38
- def name
39
- "Non-Null"
38
+ def valid_input?(value, warden)
39
+ validate_input(value, warden).valid?
40
40
  end
41
41
 
42
- def valid_input?(value)
43
- validate_input(value).valid?
44
- end
45
-
46
- def validate_input(value)
42
+ def validate_input(value, warden)
47
43
  if value.nil?
48
44
  result = GraphQL::Query::InputValidationResult.new
49
45
  result.add_problem("Expected value to not be null")
50
46
  result
51
47
  else
52
- of_type.validate_input(value)
48
+ of_type.validate_input(value, warden)
53
49
  end
54
50
  end
55
51