graphql 1.8.0.pre6 → 1.8.0.pre7

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 (36) hide show
  1. checksums.yaml +5 -5
  2. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +14 -0
  3. data/lib/graphql/internal_representation/node.rb +3 -20
  4. data/lib/graphql/language/parser.rb +590 -598
  5. data/lib/graphql/language/parser.y +1 -2
  6. data/lib/graphql/query/context.rb +26 -0
  7. data/lib/graphql/schema.rb +77 -22
  8. data/lib/graphql/schema/argument.rb +11 -3
  9. data/lib/graphql/schema/field.rb +50 -3
  10. data/lib/graphql/schema/input_object.rb +6 -0
  11. data/lib/graphql/schema/interface.rb +0 -1
  12. data/lib/graphql/schema/member.rb +2 -16
  13. data/lib/graphql/schema/member/has_fields.rb +4 -0
  14. data/lib/graphql/schema/object.rb +5 -4
  15. data/lib/graphql/schema/union.rb +0 -12
  16. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -1
  17. data/lib/graphql/upgrader/member.rb +97 -10
  18. data/lib/graphql/version.rb +1 -1
  19. data/spec/fixtures/upgrader/starrable.original.rb +46 -0
  20. data/spec/fixtures/upgrader/starrable.transformed.rb +43 -0
  21. data/spec/fixtures/upgrader/subscribable.transformed.rb +32 -30
  22. data/spec/fixtures/upgrader/type_x.original.rb +14 -2
  23. data/spec/fixtures/upgrader/type_x.transformed.rb +12 -0
  24. data/spec/graphql/query/executor_spec.rb +2 -1
  25. data/spec/graphql/rake_task_spec.rb +3 -1
  26. data/spec/graphql/schema/field_spec.rb +94 -1
  27. data/spec/graphql/schema/input_object_spec.rb +6 -0
  28. data/spec/graphql/schema/interface_spec.rb +15 -0
  29. data/spec/graphql/schema/object_spec.rb +5 -3
  30. data/spec/graphql/schema/printer_spec.rb +19 -0
  31. data/spec/graphql/schema_spec.rb +10 -3
  32. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -2
  33. data/spec/graphql/upgrader/member_spec.rb +2 -2
  34. data/spec/support/jazz.rb +21 -14
  35. data/spec/support/star_wars/schema.rb +1 -1
  36. metadata +7 -3
@@ -82,8 +82,7 @@ rule
82
82
  | EQUALS literal_value { return val[1] }
83
83
 
84
84
  selection_set:
85
- LCURLY RCURLY { return [] }
86
- | LCURLY selection_list RCURLY { return val[1] }
85
+ LCURLY selection_list RCURLY { return val[1] }
87
86
 
88
87
  selection_set_opt:
89
88
  /* none */ { return [] }
@@ -70,6 +70,32 @@ module GraphQL
70
70
  def backtrace
71
71
  GraphQL::Backtrace.new(self)
72
72
  end
73
+
74
+ def execution_errors
75
+ @execution_errors ||= ExecutionErrors.new(self)
76
+ end
77
+ end
78
+
79
+ class ExecutionErrors
80
+ def initialize(ctx)
81
+ @context = ctx
82
+ end
83
+
84
+ def add(err_or_msg)
85
+ err = case err_or_msg
86
+ when String
87
+ GraphQL::ExecutionError.new(err_or_msg)
88
+ when GraphQL::ExecutionError
89
+ err_or_msg
90
+ else
91
+ raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})"
92
+ end
93
+ # This will assign ast_node and path
94
+ @context.add_error(err)
95
+ end
96
+
97
+ alias :>> :add
98
+ alias :push :add
73
99
  end
74
100
 
75
101
  include SharedMethods
@@ -613,7 +613,7 @@ module GraphQL
613
613
  # @param except [<#call(member, ctx)>]
614
614
  # @return [Hash] GraphQL result
615
615
  def as_json(only: nil, except: nil, context: {})
616
- execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context)
616
+ execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h
617
617
  end
618
618
 
619
619
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
@@ -625,19 +625,32 @@ module GraphQL
625
625
 
626
626
  class << self
627
627
  extend GraphQL::Delegate
628
- def_delegators :graphql_definition, :as_json, :to_json
629
-
630
- def method_missing(method_name, *args, &block)
631
- if graphql_definition.respond_to?(method_name)
632
- graphql_definition.public_send(method_name, *args, &block)
633
- else
634
- super
635
- end
636
- end
637
-
638
- def respond_to_missing?(method_name, incl_private = false)
639
- graphql_definition.respond_to?(method_name, incl_private) || super
640
- end
628
+ # For compatibility, these methods all:
629
+ # - Cause the Schema instance to be created, if it hasn't been created yet
630
+ # - Delegate to that instance
631
+ # Eventually, the methods will be moved into this class, removing the need for the singleton.
632
+ def_delegators :graphql_definition,
633
+ # Schema structure
634
+ :as_json, :to_json, :to_document, :to_definition,
635
+ # Execution
636
+ :execute, :multiplex,
637
+ :static_validator, :introspection_system,
638
+ :query_analyzers, :middleware, :tracers, :instrumenters,
639
+ :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
640
+ :validate, :multiplex_analyzers, :lazy?, :lazy_method_name,
641
+ # Configuration
642
+ :max_complexity=, :max_depth=,
643
+ :metadata,
644
+ :default_filter, :redefine,
645
+ :id_from_object_proc, :object_from_id_proc,
646
+ :id_from_object=, :object_from_id=, :type_error,
647
+ :remove_handler,
648
+ # Members
649
+ :types, :get_fields, :find,
650
+ :root_type_for_operation,
651
+ :union_memberships,
652
+ :get_field, :root_types, :references_to, :type_from_ast,
653
+ :possible_types, :get_field
641
654
 
642
655
  def graphql_definition
643
656
  @graphql_definition ||= to_graphql
@@ -653,6 +666,7 @@ module GraphQL
653
666
 
654
667
  def to_graphql
655
668
  schema_defn = self.new
669
+ schema_defn.raise_definition_error = true
656
670
  schema_defn.query = query
657
671
  schema_defn.mutation = mutation
658
672
  schema_defn.subscription = subscription
@@ -660,16 +674,17 @@ module GraphQL
660
674
  schema_defn.max_depth = max_depth
661
675
  schema_defn.default_max_page_size = default_max_page_size
662
676
  schema_defn.orphan_types = orphan_types
663
- if !directives
664
- directives(DIRECTIVES)
665
- end
666
677
  schema_defn.directives = directives
667
678
  schema_defn.introspection_namespace = introspection
668
679
  schema_defn.resolve_type = method(:resolve_type)
669
680
  schema_defn.object_from_id = method(:object_from_id)
670
681
  schema_defn.id_from_object = method(:id_from_object)
682
+ schema_defn.type_error = method(:type_error)
671
683
  schema_defn.context_class = context_class
672
- instrumenters.each do |step, insts|
684
+ schema_defn.tracers.concat(defined_tracers)
685
+ schema_defn.query_analyzers.concat(defined_query_analyzers)
686
+ schema_defn.multiplex_analyzers.concat(defined_multiplex_analyzers)
687
+ defined_instrumenters.each do |step, insts|
673
688
  insts.each do |inst|
674
689
  schema_defn.instrumenters[step] << inst
675
690
  end
@@ -678,6 +693,12 @@ module GraphQL
678
693
  lazy_classes.each do |lazy_class, value_method|
679
694
  schema_defn.lazy_methods.set(lazy_class, value_method)
680
695
  end
696
+ if @rescues
697
+ @rescues.each do |err_class, handler|
698
+ schema_defn.rescue_from(err_class, &handler)
699
+ end
700
+ end
701
+
681
702
  if plugins.any?
682
703
  schema_plugins = plugins
683
704
  # TODO don't depend on .define
@@ -776,6 +797,11 @@ module GraphQL
776
797
  end
777
798
  end
778
799
 
800
+ def rescue_from(err_class, &handler_block)
801
+ @rescues ||= {}
802
+ @rescues[err_class] = handler_block
803
+ end
804
+
779
805
  def resolve_type(type, obj, ctx)
780
806
  raise NotImplementedError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
781
807
  end
@@ -788,6 +814,10 @@ module GraphQL
788
814
  raise NotImplementedError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)"
789
815
  end
790
816
 
817
+ def type_error(type_err, ctx)
818
+ DefaultTypeError.call(type_err, ctx)
819
+ end
820
+
791
821
  def lazy_resolve(lazy_class, value_method)
792
822
  lazy_classes[lazy_class] = value_method
793
823
  end
@@ -798,14 +828,27 @@ module GraphQL
798
828
  else
799
829
  instrument_step
800
830
  end
801
- instrumenters[step] << instrumenter
831
+ defined_instrumenters[step] << instrumenter
802
832
  end
803
833
 
804
834
  def directives(new_directives = nil)
805
835
  if new_directives
806
836
  @directives = new_directives.reduce({}) { |m, d| m[d.name] = d; m }
807
837
  end
808
- @directives
838
+
839
+ @directives ||= directives(DIRECTIVES)
840
+ end
841
+
842
+ def tracer(new_tracer)
843
+ defined_tracers << new_tracer
844
+ end
845
+
846
+ def query_analyzer(new_analyzer)
847
+ defined_query_analyzers << new_analyzer
848
+ end
849
+
850
+ def multiplex_analyzer(new_analyzer)
851
+ defined_multiplex_analyzers << new_analyzer
809
852
  end
810
853
 
811
854
  private
@@ -814,8 +857,20 @@ module GraphQL
814
857
  @lazy_classes ||= {}
815
858
  end
816
859
 
817
- def instrumenters
818
- @instrumenters ||= Hash.new { |h,k| h[k] = [] }
860
+ def defined_instrumenters
861
+ @defined_instrumenters ||= Hash.new { |h,k| h[k] = [] }
862
+ end
863
+
864
+ def defined_tracers
865
+ @defined_tracers ||= []
866
+ end
867
+
868
+ def defined_query_analyzers
869
+ @defined_query_analyzers ||= []
870
+ end
871
+
872
+ def defined_multiplex_analyzers
873
+ @defined_multiplex_analyzers ||= []
819
874
  end
820
875
  end
821
876
 
@@ -8,17 +8,25 @@ module GraphQL
8
8
 
9
9
  attr_reader :name
10
10
 
11
- def initialize(arg_name, type_expr, desc = nil, required:, default_value: NO_DEFAULT)
11
+ # @param arg_name [Symbol]
12
+ # @param type_expr
13
+ # @param desc [String]
14
+ # @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable
15
+ # @param description [String]
16
+ # @param default_value [Object]
17
+ # @param camelize [Boolean] if true, the name will be camelized when building the schema
18
+ def initialize(arg_name, type_expr, desc = nil, required:, description: nil, default_value: NO_DEFAULT, camelize: true)
12
19
  @name = arg_name.to_s
13
20
  @type_expr = type_expr
14
- @description = desc
21
+ @description = desc || description
15
22
  @null = !required
16
23
  @default_value = default_value
24
+ @camelize = camelize
17
25
  end
18
26
 
19
27
  def to_graphql
20
28
  argument = GraphQL::Argument.new
21
- argument.name = Member::BuildType.camelize(@name)
29
+ argument.name = @camelize ? Member::BuildType.camelize(@name) : @name
22
30
  argument.type = -> {
23
31
  Member::BuildType.parse_type(@type_expr, null: @null)
24
32
  }
@@ -19,7 +19,28 @@ module GraphQL
19
19
  # @return [Symbol]
20
20
  attr_reader :method
21
21
 
22
- def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, extras: [], &definition_block)
22
+ # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
23
+ # @param return_type_expr [Class, GraphQL::BaseType, Array] The return type of this field
24
+ # @param desc [String] Field description
25
+ # @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null`
26
+ # @param description [String] Field description
27
+ # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
28
+ # @param method [Symbol] The method to call to resolve this field (defaults to `name`)
29
+ # @param hash_key [Object] The hash key to lookup to resolve this field (defaults to `name` or `name.to_s`)
30
+ # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
31
+ # @param max_page_size [Integer] For connections, the maximum number of items to return from this field
32
+ # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
33
+ # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
34
+ # @param field [GraphQL::Field] **deprecated** for compatibility with <1.8.0
35
+ # @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0
36
+ # @param camelize [Boolean] If true, the field name will be camelized when building the schema
37
+ # @param complexity [Numeric] When provided, set the complexity for this field
38
+ def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], &definition_block)
39
+ if (field || function) && desc.nil? && return_type_expr.is_a?(String)
40
+ # The return type should be copied from `field` or `function`, and the second positional argument is the description
41
+ desc = return_type_expr
42
+ return_type_expr = nil
43
+ end
23
44
  if !(field || function)
24
45
  if return_type_expr.nil?
25
46
  raise ArgumentError, "missing positional argument `type`"
@@ -40,7 +61,12 @@ module GraphQL
40
61
  @function = function
41
62
  @resolve = resolve
42
63
  @deprecation_reason = deprecation_reason
64
+ if method && hash_key
65
+ raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
66
+ end
43
67
  @method = method
68
+ @hash_key = hash_key
69
+ @complexity = complexity
44
70
  @return_type_expr = return_type_expr
45
71
  @return_type_null = null
46
72
  @connection = connection
@@ -48,6 +74,7 @@ module GraphQL
48
74
  @introspection = introspection
49
75
  @extras = extras
50
76
  @arguments = {}
77
+ @camelize = camelize
51
78
 
52
79
  if definition_block
53
80
  instance_eval(&definition_block)
@@ -68,9 +95,28 @@ module GraphQL
68
95
  end
69
96
  end
70
97
 
98
+ def complexity(new_complexity)
99
+ case new_complexity
100
+ when Proc
101
+ if new_complexity.parameters.size != 3
102
+ fail(
103
+ "A complexity proc should always accept 3 parameters: ctx, args, child_complexity. "\
104
+ "E.g.: complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }"
105
+ )
106
+ else
107
+ @complexity = new_complexity
108
+ end
109
+ when Numeric
110
+ @complexity = new_complexity
111
+ else
112
+ raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
113
+ end
114
+
115
+ end
116
+
71
117
  # @return [GraphQL::Field]
72
118
  def to_graphql
73
- method_name = @method || Member::BuildType.underscore(@name)
119
+ method_name = @method || @hash_key || Member::BuildType.underscore(@name)
74
120
 
75
121
  field_defn = if @field
76
122
  @field.dup
@@ -80,7 +126,7 @@ module GraphQL
80
126
  GraphQL::Field.new
81
127
  end
82
128
 
83
- field_defn.name = Member::BuildType.camelize(name)
129
+ field_defn.name = @camelize ? Member::BuildType.camelize(name) : name
84
130
  if @return_type_expr
85
131
  return_type_name = Member::BuildType.to_type_name(@return_type_expr)
86
132
  connection = @connection.nil? ? return_type_name.end_with?("Connection") : @connection
@@ -116,6 +162,7 @@ module GraphQL
116
162
  field_defn.connection = connection
117
163
  field_defn.connection_max_page_size = @max_page_size
118
164
  field_defn.introspection = @introspection
165
+ field_defn.complexity = @complexity
119
166
 
120
167
  # apply this first, so it can be overriden below
121
168
  if connection
@@ -9,6 +9,12 @@ module GraphQL
9
9
  @context = context
10
10
  end
11
11
 
12
+ # @return [GraphQL::Query::Context] The context for this query
13
+ attr_reader :context
14
+
15
+ # @return [GraphQL::Query::Arguments] The underlying arguments instance
16
+ attr_reader :arguments
17
+
12
18
  # A lot of methods work just like GraphQL::Arguments
13
19
  def_delegators :@arguments, :[], :key?, :to_h
14
20
  def_delegators :to_h, :keys, :values, :each, :any?
@@ -6,7 +6,6 @@ module GraphQL
6
6
  field_class GraphQL::Schema::Field
7
7
 
8
8
  class << self
9
-
10
9
  # When this interface is added to a `GraphQL::Schema::Object`,
11
10
  # it calls this method. We add methods to the object by convention,
12
11
  # a nested module named `Implementation`
@@ -29,31 +29,17 @@ module GraphQL
29
29
  # @example
30
30
  # field :isDraft, Boolean, null: false
31
31
  # field :id, ID, null: false
32
+ # field :score, Int, null: false
32
33
  module GraphQLTypeNames
33
34
  Boolean = "Boolean"
34
35
  ID = "ID"
36
+ Int = "Int"
35
37
  end
36
38
 
37
39
  include GraphQLTypeNames
38
40
  class << self
39
41
  include CachedGraphQLDefinition
40
42
  include GraphQL::Relay::TypeExtensions
41
- # Delegate to the derived type definition if possible.
42
- # This is tricky because missing methods cause the definition to be built & cached.
43
- def method_missing(method_name, *args, &block)
44
- if graphql_definition.respond_to?(method_name)
45
- graphql_definition.public_send(method_name, *args, &block)
46
- else
47
- super
48
- end
49
- end
50
-
51
- # Check if the derived type definition responds to the method
52
- # @return [Boolean]
53
- def respond_to_missing?(method_name, incl_private = false)
54
- graphql_definition.respond_to?(method_name, incl_private) || super
55
- end
56
-
57
43
  # Call this with a new name to override the default name for this schema member; OR
58
44
  # call it without an argument to get the name of this schema member
59
45
  #
@@ -37,6 +37,10 @@ module GraphQL
37
37
  end
38
38
  end
39
39
 
40
+ def global_id_field(field_name)
41
+ field field_name, "ID", null: false, resolve: GraphQL::Relay::GlobalIdResolve.new(type: self)
42
+ end
43
+
40
44
  private
41
45
 
42
46
  # @return [Array<GraphQL::Schema::Field>] Fields defined on this class _specifically_, not parent classes
@@ -3,12 +3,17 @@
3
3
  module GraphQL
4
4
  class Schema
5
5
  class Object < GraphQL::Schema::Member
6
+ # @return [Object] the application object this type is wrapping
6
7
  attr_reader :object
7
8
 
9
+ # @return [GraphQL::Query::Context] the context instance for this query
10
+ attr_reader :context
11
+
8
12
  def initialize(object, context)
9
13
  @object = object
10
14
  @context = context
11
15
  end
16
+
12
17
  extend GraphQL::Schema::Member::HasFields
13
18
  field_class GraphQL::Schema::Field
14
19
 
@@ -56,10 +61,6 @@ module GraphQL
56
61
 
57
62
  obj_type
58
63
  end
59
-
60
- def global_id_field(field_name)
61
- field field_name, "ID", null: false, resolve: GraphQL::Relay::GlobalIdResolve.new(type: self)
62
- end
63
64
  end
64
65
  end
65
66
  end
@@ -2,11 +2,6 @@
2
2
  module GraphQL
3
3
  class Schema
4
4
  class Union < GraphQL::Schema::Member
5
- def initialize(obj, ctx)
6
- @object = obj
7
- @context = ctx
8
- end
9
-
10
5
  class << self
11
6
  def possible_types(*types)
12
7
  if types.any?
@@ -23,13 +18,6 @@ module GraphQL
23
18
  @own_possible_types ||= []
24
19
  end
25
20
 
26
- # The class resolves type by:
27
- # - make an instance
28
- # - call the instance method
29
- def resolve_type(value, ctx)
30
- self.new(value, ctx).resolve_type
31
- end
32
-
33
21
  def to_graphql
34
22
  type_defn = GraphQL::UnionType.new
35
23
  type_defn.name = graphql_name