graphql 1.8.3 → 1.8.4

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