graphql 1.0.0 → 1.1.0

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