graphql 1.8.3 → 1.8.4

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +4 -1
  3. data/lib/graphql/argument.rb +1 -0
  4. data/lib/graphql/authorization.rb +81 -0
  5. data/lib/graphql/boolean_type.rb +0 -1
  6. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +2 -1
  7. data/lib/graphql/execution/execute.rb +34 -10
  8. data/lib/graphql/execution/lazy.rb +5 -1
  9. data/lib/graphql/field.rb +7 -1
  10. data/lib/graphql/float_type.rb +0 -1
  11. data/lib/graphql/id_type.rb +0 -1
  12. data/lib/graphql/int_type.rb +0 -1
  13. data/lib/graphql/introspection/entry_points.rb +2 -2
  14. data/lib/graphql/object_type.rb +3 -3
  15. data/lib/graphql/query.rb +6 -0
  16. data/lib/graphql/query/arguments.rb +2 -0
  17. data/lib/graphql/query/context.rb +6 -0
  18. data/lib/graphql/query/variables.rb +7 -1
  19. data/lib/graphql/relay/connection_instrumentation.rb +2 -2
  20. data/lib/graphql/relay/connection_resolve.rb +7 -27
  21. data/lib/graphql/relay/connection_type.rb +1 -0
  22. data/lib/graphql/relay/edge_type.rb +1 -0
  23. data/lib/graphql/relay/edges_instrumentation.rb +9 -25
  24. data/lib/graphql/relay/mutation/instrumentation.rb +1 -2
  25. data/lib/graphql/relay/mutation/resolve.rb +2 -4
  26. data/lib/graphql/relay/node.rb +1 -6
  27. data/lib/graphql/relay/page_info.rb +1 -9
  28. data/lib/graphql/schema.rb +84 -11
  29. data/lib/graphql/schema/argument.rb +13 -0
  30. data/lib/graphql/schema/enum.rb +1 -1
  31. data/lib/graphql/schema/enum_value.rb +4 -0
  32. data/lib/graphql/schema/field.rb +44 -11
  33. data/lib/graphql/schema/interface.rb +20 -0
  34. data/lib/graphql/schema/introspection_system.rb +1 -1
  35. data/lib/graphql/schema/member/base_dsl_methods.rb +25 -3
  36. data/lib/graphql/schema/member/instrumentation.rb +15 -17
  37. data/lib/graphql/schema/mutation.rb +4 -0
  38. data/lib/graphql/schema/object.rb +33 -0
  39. data/lib/graphql/schema/possible_types.rb +2 -0
  40. data/lib/graphql/schema/resolver.rb +10 -0
  41. data/lib/graphql/schema/traversal.rb +9 -2
  42. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +11 -2
  43. data/lib/graphql/string_type.rb +0 -1
  44. data/lib/graphql/types.rb +7 -0
  45. data/lib/graphql/types/relay.rb +31 -0
  46. data/lib/graphql/types/relay/base_connection.rb +87 -0
  47. data/lib/graphql/types/relay/base_edge.rb +51 -0
  48. data/lib/graphql/types/relay/base_field.rb +22 -0
  49. data/lib/graphql/types/relay/base_interface.rb +29 -0
  50. data/lib/graphql/types/relay/base_object.rb +26 -0
  51. data/lib/graphql/types/relay/node.rb +18 -0
  52. data/lib/graphql/types/relay/page_info.rb +23 -0
  53. data/lib/graphql/unauthorized_error.rb +20 -0
  54. data/lib/graphql/version.rb +1 -1
  55. data/spec/graphql/authorization_spec.rb +684 -0
  56. data/spec/graphql/query/variables_spec.rb +20 -0
  57. data/spec/graphql/relay/connection_instrumentation_spec.rb +1 -1
  58. data/spec/graphql/schema/resolver_spec.rb +31 -0
  59. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +52 -0
  60. data/spec/support/dummy/schema.rb +16 -0
  61. data/spec/support/star_wars/schema.rb +28 -17
  62. metadata +15 -2
@@ -114,6 +114,10 @@ module GraphQL
114
114
  @object_class || (superclass.respond_to?(:object_class) ? superclass.object_class : GraphQL::Schema::Object)
115
115
  end
116
116
 
117
+ def visible?(context)
118
+ true
119
+ end
120
+
117
121
  private
118
122
 
119
123
  # Build a subclass of {.object_class} based on `self`.
@@ -12,6 +12,39 @@ module GraphQL
12
12
  # @return [GraphQL::Query::Context] the context instance for this query
13
13
  attr_reader :context
14
14
 
15
+ class << self
16
+ # This is protected so that we can be sure callers use the public method, {.authorized_new}
17
+ # @see authorized_new to make instances
18
+ protected :new
19
+
20
+ # Make a new instance of this type _if_ the auth check passes,
21
+ # otherwise, raise an error.
22
+ #
23
+ # Probably only the framework should call this method.
24
+ #
25
+ # This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?`
26
+ # hook returns some lazy value (like a Promise).
27
+ #
28
+ # The reason that the auth check is in this wrapper method instead of {.new} is because
29
+ # of how it might return a Promise. It would be weird if `.new` returned a promise;
30
+ # It would be a headache to try to maintain Promise-y state inside a {Schema::Object}
31
+ # instance. So, hopefully this wrapper method will do the job.
32
+ #
33
+ # @param object [Object] The thing wrapped by this object
34
+ # @param context [GraphQL::Query::Context]
35
+ # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
36
+ # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
37
+ def authorized_new(object, context)
38
+ context.schema.after_lazy(authorized?(object, context)) do |is_authorized|
39
+ if is_authorized
40
+ self.new(object, context)
41
+ else
42
+ raise GraphQL::UnauthorizedError.new(object: object, type: self, context: context)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
15
48
  def initialize(object, context)
16
49
  @object = object
17
50
  @context = context
@@ -22,6 +22,8 @@ module GraphQL
22
22
 
23
23
  def possible_types(type_defn)
24
24
  case type_defn
25
+ when Module
26
+ possible_types(type_defn.graphql_definition)
25
27
  when GraphQL::UnionType
26
28
  type_defn.possible_types
27
29
  when GraphQL::InterfaceType
@@ -99,6 +99,15 @@ module GraphQL
99
99
  end
100
100
  end
101
101
 
102
+ # Specifies the complexity of the field. Defaults to `1`
103
+ # @return [Integer, Proc]
104
+ def complexity(new_complexity = nil)
105
+ if new_complexity
106
+ @complexity = new_complexity
107
+ end
108
+ @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
109
+ end
110
+
102
111
  def field_options
103
112
  {
104
113
  type: type_expr,
@@ -108,6 +117,7 @@ module GraphQL
108
117
  resolver_class: self,
109
118
  arguments: arguments,
110
119
  null: null,
120
+ complexity: complexity,
111
121
  }
112
122
  end
113
123
 
@@ -16,13 +16,20 @@ module GraphQL
16
16
  # @return [Hash<String => Array<GraphQL::BaseType>]
17
17
  attr_reader :union_memberships
18
18
 
19
+
19
20
  # @param schema [GraphQL::Schema]
20
21
  def initialize(schema, introspection: true)
21
22
  @schema = schema
22
23
  @introspection = introspection
23
24
  @field_instrumenters =
24
25
  schema.instrumenters[:field] +
25
- Schema::BUILT_IN_INSTRUMENTERS +
26
+ # Wrap Relay-related objects in wrappers
27
+ [
28
+ GraphQL::Relay::ConnectionInstrumentation,
29
+ GraphQL::Relay::EdgesInstrumentation,
30
+ GraphQL::Relay::Mutation::Instrumentation,
31
+ GraphQL::Schema::Member::Instrumentation,
32
+ ] +
26
33
  schema.instrumenters[:field_after_built_ins]
27
34
 
28
35
  # These fields have types specified by _name_,
@@ -158,7 +165,7 @@ Some late-bound types couldn't be resolved:
158
165
  end
159
166
  elsif !prev_type.equal?(type_defn)
160
167
  # If the previous entry in the map isn't the same object we just found, raise.
161
- raise("Duplicate type definition found for name '#{type_defn.name}' (#{prev_type.metadata[:type_class]}, #{type_defn.metadata[:type_class]}})")
168
+ raise("Duplicate type definition found for name '#{type_defn.name}' at '#{context_description}' (#{prev_type.metadata[:type_class] || prev_type}, #{type_defn.metadata[:type_class] || type_defn})")
162
169
  end
163
170
  when Class
164
171
  if member.respond_to?(:graphql_definition)
@@ -20,8 +20,17 @@ module GraphQL
20
20
  type = context.schema.type_from_ast(node.type)
21
21
  if type.nil?
22
22
  # This is handled by another validator
23
- elsif !context.valid_literal?(value, type)
24
- context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node, context: context)
23
+ else
24
+ begin
25
+ valid = context.valid_literal?(value, type)
26
+ rescue GraphQL::CoercionError => err
27
+ error_message = err.message
28
+ end
29
+
30
+ if !valid
31
+ error_message ||= "Default value for $#{node.name} doesn't match type #{type}"
32
+ context.errors << message(error_message, node, context: context)
33
+ end
25
34
  end
26
35
  end
27
36
  end
@@ -1,3 +1,2 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/types/string"
3
2
  GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/types/boolean"
3
+ require "graphql/types/float"
4
+ require "graphql/types/id"
5
+ require "graphql/types/int"
6
+ require "graphql/types/string"
7
+ require "graphql/types/relay"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/types/relay/base_field"
3
+ require "graphql/types/relay/base_object"
4
+ require "graphql/types/relay/base_interface"
5
+ require "graphql/types/relay/page_info"
6
+ require "graphql/types/relay/base_connection"
7
+ require "graphql/types/relay/base_edge"
8
+ require "graphql/types/relay/node"
9
+
10
+ module GraphQL
11
+ module Types
12
+ # This module contains some types and fields that could support Relay conventions in GraphQL.
13
+ #
14
+ # You can use these classes out of the box if you want, but if you want to use your _own_
15
+ # GraphQL extensions along with the features in this code, you could also
16
+ # open up the source files and copy the relevant methods and configuration into
17
+ # your own classes.
18
+ #
19
+ # For example, the provided object types extend {Types::Relay::BaseObject},
20
+ # but you might want to:
21
+ #
22
+ # 1. Migrate the extensions from {Types::Relay::BaseObject} into _your app's_ base object
23
+ # 2. Copy {Relay::BaseConnection}, {Relay::BaseEdge}, etc into _your app_, and
24
+ # change them to extend _your_ base object.
25
+ #
26
+ # Similarly, `BaseField`'s extensions could be migrated to your app
27
+ # and `Node` could be implemented to mix in your base interface module.
28
+ module Relay
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ module Relay
6
+ # Use this to implement Relay connections, or take it as inspiration
7
+ # for Relay classes in your own app.
8
+ #
9
+ # You may wish to copy this code into your own base class,
10
+ # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`.
11
+ #
12
+ # @example Implementation a connection and edge
13
+ # # Given some object in your app ...
14
+ # class Types::Post < BaseObject
15
+ # end
16
+ #
17
+ # # Make a couple of base classes:
18
+ # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end
19
+ # class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end
20
+ #
21
+ # # Then extend them for the object in your app
22
+ # class Types::PostEdge < Types::BaseEdge
23
+ # node_type(Types::Post)
24
+ # end
25
+ # class Types::PostConnection < Types::BaseConnection
26
+ # edge_type(Types::PostEdge)
27
+ # end
28
+ #
29
+ # @see Relay::BaseEdge for edge types
30
+ class BaseConnection < Types::Relay::BaseObject
31
+ extend Forwardable
32
+ def_delegators :@object, :cursor_from_node, :parent
33
+
34
+ class << self
35
+ # @return [Class]
36
+ attr_reader :node_type
37
+
38
+ # @return [Class]
39
+ attr_reader :edge_type
40
+
41
+ # Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
42
+ #
43
+ # This method will use the inputs to create:
44
+ # - `edges` field
45
+ # - `nodes` field
46
+ # - description
47
+ #
48
+ # It's called when you subclass this base connection, trying to use the
49
+ # class name to set defaults. You can call it again in the class definition
50
+ # to override the default (or provide a value, if the default lookup failed).
51
+ def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type)
52
+ # Set this connection's graphql name
53
+ node_type_name = node_type.graphql_name
54
+
55
+ @node_type = node_type
56
+ @edge_type = edge_type_class
57
+
58
+ field :edges, [edge_type_class, null: true],
59
+ null: true,
60
+ description: "A list of edges.",
61
+ method: :edge_nodes,
62
+ edge_class: edge_class
63
+
64
+ field :nodes, [node_type, null: true],
65
+ null: true,
66
+ description: "A list of nodes."
67
+
68
+ description("The connection type for #{node_type_name}.")
69
+ end
70
+
71
+ # Add the shortcut `nodes` field to this connection and its subclasses
72
+ def nodes_field
73
+ field :nodes, [@node_type, null: true], null: true
74
+ end
75
+ end
76
+
77
+ field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
78
+
79
+ # By default this calls through to the ConnectionWrapper's edge nodes method,
80
+ # but sometimes you need to override it to support the `nodes` field
81
+ def nodes
82
+ @object.edge_nodes
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Types
4
+ module Relay
5
+ # A class-based definition for Relay edges.
6
+ #
7
+ # Use this as a parent class in your app, or use it as inspiration for your
8
+ # own base `Edge` class.
9
+ #
10
+ # For example, you may want to extend your own `BaseObject` instead of the
11
+ # built-in `GraphQL::Schema::Object`.
12
+ #
13
+ # @example Making a UserEdge type
14
+ # # Make a base class for your app
15
+ # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge
16
+ # end
17
+ #
18
+ # # Then extend your own base class
19
+ # class Types::UserEdge < Types::BaseEdge
20
+ # node_type(Types::User)
21
+ # end
22
+ #
23
+ # @see {Relay::BaseConnection} for connection types
24
+ class BaseEdge < Types::Relay::BaseObject
25
+ description "An edge in a connection."
26
+
27
+ class << self
28
+ # Get or set the Object type that this edge wraps.
29
+ #
30
+ # @param node_type [Class] A `Schema::Object` subclass
31
+ def node_type(node_type = nil)
32
+ if node_type
33
+ @node_type = node_type
34
+ wrapped_type_name = node_type.graphql_name
35
+ # Set this to be named like the node type, but suffixed with `Edge`
36
+ graphql_name("#{wrapped_type_name}Edge")
37
+ # Add a default `node` field
38
+ field :node, node_type, null: true, description: "The item at the end of the edge."
39
+ end
40
+ @node_type
41
+ end
42
+ end
43
+
44
+
45
+ field :cursor, String,
46
+ null: false,
47
+ description: "A cursor for use in pagination."
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ module Relay
6
+ class BaseField < GraphQL::Schema::Field
7
+ def initialize(edge_class: nil, **rest, &block)
8
+ @edge_class = edge_class
9
+ super(**rest, &block)
10
+ end
11
+
12
+ def to_graphql
13
+ field = super
14
+ if @edge_class
15
+ field.edge_class = @edge_class
16
+ end
17
+ field
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ module Relay
6
+ module BaseInterface
7
+ include GraphQL::Schema::Interface
8
+
9
+ field_class(Types::Relay::BaseField)
10
+
11
+ definition_methods do
12
+ def default_relay(new_value)
13
+ @default_relay = new_value
14
+ end
15
+
16
+ def default_relay?
17
+ !!@default_relay
18
+ end
19
+
20
+ def to_graphql
21
+ type_defn = super
22
+ type_defn.default_relay = default_relay?
23
+ type_defn
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ module Relay
6
+ class BaseObject < GraphQL::Schema::Object
7
+ field_class(Types::Relay::BaseField)
8
+ class << self
9
+ def default_relay(new_value)
10
+ @default_relay = new_value
11
+ end
12
+
13
+ def default_relay?
14
+ !!@default_relay
15
+ end
16
+
17
+ def to_graphql
18
+ type_defn = super
19
+ type_defn.default_relay = default_relay?
20
+ type_defn
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ module Relay
6
+ # This can be used for Relay's `Node` interface,
7
+ # or you can take it as inspiration for your own implementation
8
+ # of the `Node` interface.
9
+ module Node
10
+ include Types::Relay::BaseInterface
11
+ default_relay(true)
12
+ description "An object with an ID."
13
+ field(:id, ID, null: false, description: "ID of the object.")
14
+ # TODO Should I implement `id` here to call the schema's hook?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Types
4
+ module Relay
5
+ # The return type of a connection's `pageInfo` field
6
+ class PageInfo < Types::Relay::BaseObject
7
+ default_relay true
8
+ description "Information about pagination in a connection."
9
+ field :has_next_page, Boolean, null: false,
10
+ description: "When paginating forwards, are there more items?"
11
+
12
+ field :has_previous_page, Boolean, null: false,
13
+ description: "When paginating backwards, are there more items?"
14
+
15
+ field :start_cursor, String, null: true,
16
+ description: "When paginating backwards, the cursor to continue."
17
+
18
+ field :end_cursor, String, null: true,
19
+ description: "When paginating forwards, the cursor to continue."
20
+ end
21
+ end
22
+ end
23
+ end