graphql 1.8.0.pre6 → 1.8.0.pre7

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