graphql 0.18.4 → 0.18.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -2
  3. data/lib/graphql/argument.rb +2 -2
  4. data/lib/graphql/base_type.rb +17 -0
  5. data/lib/graphql/define.rb +1 -1
  6. data/lib/graphql/directive.rb +6 -0
  7. data/lib/graphql/enum_type.rb +48 -4
  8. data/lib/graphql/field.rb +81 -19
  9. data/lib/graphql/field/resolve.rb +1 -1
  10. data/lib/graphql/input_object_type.rb +17 -3
  11. data/lib/graphql/interface_type.rb +12 -2
  12. data/lib/graphql/list_type.rb +23 -4
  13. data/lib/graphql/non_null_type.rb +27 -2
  14. data/lib/graphql/query.rb +0 -1
  15. data/lib/graphql/query/arguments.rb +1 -1
  16. data/lib/graphql/query/serial_execution/value_resolution.rb +3 -1
  17. data/lib/graphql/relay/global_node_identification.rb +29 -16
  18. data/lib/graphql/scalar_type.rb +24 -1
  19. data/lib/graphql/schema.rb +109 -19
  20. data/lib/graphql/schema/printer.rb +3 -3
  21. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  22. data/lib/graphql/union_type.rb +19 -6
  23. data/lib/graphql/version.rb +1 -1
  24. data/readme.md +5 -6
  25. data/spec/graphql/analysis/query_complexity_spec.rb +1 -1
  26. data/spec/graphql/argument_spec.rb +1 -1
  27. data/spec/graphql/field_spec.rb +1 -1
  28. data/spec/graphql/interface_type_spec.rb +0 -19
  29. data/spec/graphql/query/context_spec.rb +1 -1
  30. data/spec/graphql/query/executor_spec.rb +1 -1
  31. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +8 -4
  32. data/spec/graphql/relay/array_connection_spec.rb +17 -17
  33. data/spec/graphql/relay/connection_type_spec.rb +1 -1
  34. data/spec/graphql/relay/global_node_identification_spec.rb +3 -21
  35. data/spec/graphql/relay/mutation_spec.rb +2 -2
  36. data/spec/graphql/relay/page_info_spec.rb +9 -9
  37. data/spec/graphql/relay/relation_connection_spec.rb +31 -31
  38. data/spec/graphql/schema/printer_spec.rb +1 -1
  39. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  40. data/spec/graphql/schema/timeout_middleware_spec.rb +1 -1
  41. data/spec/graphql/schema_spec.rb +18 -0
  42. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +6 -6
  43. data/spec/graphql/union_type_spec.rb +0 -4
  44. data/spec/spec_helper.rb +1 -1
  45. data/spec/support/dairy_app.rb +13 -8
  46. data/spec/support/star_wars_schema.rb +18 -16
  47. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45a2831bb2cf4c54f83f856f5e614a64b4e2a06e
4
- data.tar.gz: ca550cdf4e4e9772f0facd364295754bf2bc1440
3
+ metadata.gz: 9d69978a50350e058883fa1f9fdfc5c6e5dcc80b
4
+ data.tar.gz: 97cc2fdfc3778f6b75cf34a2b9d5dcf6354a1a28
5
5
  SHA512:
6
- metadata.gz: 3b52b0fd7948401ad258096f00611d63d7576d83981bd7efbda85a5d34fb7e3938ec661f357d92ab7d8bcf158a3e56ccc2be60a8402249746a9f14cda3edc601
7
- data.tar.gz: 06cab8b10aa6f6d1fd03049e24cdca3f97c014801d16fbc127d6adb6f81b55bad57797c35cfeacf4e0958834cd5e74e9e8925ca304b9e70439028ae31e1f9ba7
6
+ metadata.gz: c6177a5c17291fc6f95f0f2d8e14f3831ebc9af7f46addd15bc3283a63310151c8fc7fbfc1acc332a181ab808060afb399fd87ce83db6e0088c093adabd8b35b
7
+ data.tar.gz: b0464b86095e2aafa933d4dc5b6e5491ef2354df6025943967a78d36d63d7f7c2111e6bfaefe3b11abd5d8937c1ba14e160b5fe9d255cc7b5501d56b0f8e92a1
data/lib/graphql.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require "json"
2
2
  require "set"
3
3
  require "singleton"
4
- require "forwardable"
5
4
 
6
5
  module GraphQL
7
6
  class Error < StandardError
@@ -18,7 +17,7 @@ module GraphQL
18
17
  end
19
18
 
20
19
  # Turn a query string into an AST
21
- # @param [String] a GraphQL query string
20
+ # @param query_string [String] a GraphQL query string
22
21
  # @return [GraphQL::Language::Nodes::Document]
23
22
  def self.parse(query_string)
24
23
  parse_with_racc(query_string)
@@ -9,9 +9,9 @@ module GraphQL
9
9
  # argument :favoriteFood, types.String, "Favorite thing to eat", default_value: "pizza"
10
10
  # end
11
11
  #
12
- # @example defining an input field for an {InputObjectType}
12
+ # @example defining an argument for an {InputObjectType}
13
13
  # GraphQL::InputObjectType.define do
14
- # input_field :newName, !types.String
14
+ # argument :newName, !types.String
15
15
  # end
16
16
  #
17
17
  class Argument
@@ -49,6 +49,17 @@ module GraphQL
49
49
  end
50
50
 
51
51
  module HasPossibleTypes
52
+
53
+ # If a (deprecated) `resolve_type` function was provided, call that and warn.
54
+ # Otherwise call `schema.resolve_type` (the proper behavior)
55
+ def legacy_resolve_type(object, ctx)
56
+ if @resolve_type_proc
57
+ resolve_type(object, ctx)
58
+ else
59
+ ctx.schema.resolve_type(object, ctx)
60
+ end
61
+ end
62
+
52
63
  # Return the implementing type for `object`.
53
64
  # The default implementation assumes that there's a type with the same name as `object.class.name`.
54
65
  # Maybe you'll need to override this in your own interfaces!
@@ -58,6 +69,7 @@ module GraphQL
58
69
  # @return [GraphQL::ObjectType] the type which should expose `object`
59
70
  def resolve_type(object, ctx)
60
71
  ensure_defined
72
+ warn_resolve_type_deprecated
61
73
  instance_exec(object, ctx, &(@resolve_type_proc || DEFAULT_RESOLVE_TYPE))
62
74
  end
63
75
 
@@ -69,8 +81,13 @@ module GraphQL
69
81
  }
70
82
 
71
83
  def resolve_type=(new_proc)
84
+ warn_resolve_type_deprecated
72
85
  @resolve_type_proc = new_proc || DEFAULT_RESOLVE_TYPE
73
86
  end
87
+
88
+ def warn_resolve_type_deprecated
89
+ warn("#{self.name}.resolve_type is deprecated; define Schema.resolve_type instead")
90
+ end
74
91
  end
75
92
 
76
93
  # Print the human-readable name of this type using the query-string naming pattern
@@ -19,7 +19,7 @@ module GraphQL
19
19
  # # After definition, read the key from metadata
20
20
  # PostType.metadata[:resolves_to_class_names] # => [...]
21
21
  #
22
- # @param [Object] the key to assign in metadata
22
+ # @param key [Object] the key to assign in metadata
23
23
  # @return [#call(defn, value)] an assignment for `.accepts_definitions` which writes `key` to `#metadata`
24
24
  def self.assign_metadata_key(key)
25
25
  GraphQL::Define::InstanceDefinable::AssignMetadataKey.new(key)
@@ -1,4 +1,10 @@
1
1
  module GraphQL
2
+ # Directives are server-defined hooks for modifying execution.
3
+ #
4
+ # Two directives are included out-of-the-box:
5
+ # - `@skip(if: ...)` Skips the tagged field if the value of `if` is true
6
+ # - `@include(if: ...)` Includes the tagged field _only_ if `if` is true
7
+ #
2
8
  class Directive
3
9
  include GraphQL::Define::InstanceDefinable
4
10
  accepts_definitions :locations, :name, :description, :include_proc, argument: GraphQL::Define::AssignArgument
@@ -1,9 +1,15 @@
1
1
  module GraphQL
2
- # A finite set of possible values, represented in query strings with
3
- # SCREAMING_CASE_NAMES
2
+ # Represents a collection of related values.
3
+ # By convention, enum names are `SCREAMING_CASE_NAMES`,
4
+ # but other identifiers are supported too.
4
5
  #
5
- # @example An enum of programming languages
6
+ # You can use as return types _or_ as inputs.
7
+ #
8
+ # By default, enums are passed to `resolve` functions as
9
+ # the strings that identify them, but you can provide a
10
+ # custom Ruby value with the `value:` keyword.
6
11
  #
12
+ # @example An enum of programming languages
7
13
  # LanguageEnum = GraphQL::EnumType.define do
8
14
  # name "Languages"
9
15
  # description "Programming languages for Web projects"
@@ -11,6 +17,44 @@ module GraphQL
11
17
  # value("RUBY", "A very dynamic language aimed at programmer happiness")
12
18
  # value("JAVASCRIPT", "Accidental lingua franca of the web")
13
19
  # end
20
+ #
21
+ # @example Using an enum as a return type
22
+ # field :favoriteLanguage, LanguageEnum, "This person's favorite coding language"
23
+ # # ...
24
+ # # In a query:
25
+ # Schema.execute("{ coder(id: 1) { favoriteLanguage } }")
26
+ # # { "data" => { "coder" => { "favoriteLanguage" => "RUBY" } } }
27
+ #
28
+ # @example Defining an enum input
29
+ # field :coders, types[CoderType] do
30
+ # argument :knowing, types[LanguageType]
31
+ # resolve -> (obj, args, ctx) {
32
+ # Coder.where(language: args[:knowing])
33
+ # }
34
+ # end
35
+ #
36
+ # @example Using an enum as input
37
+ # {
38
+ # # find coders who know Python and Ruby
39
+ # coders(knowing: [PYTHON, RUBY]) {
40
+ # name
41
+ # hourlyRate
42
+ # }
43
+ # }
44
+ #
45
+ # @example Enum whose values are different in Ruby-land
46
+ # GraphQL::EnumType.define do
47
+ # # ...
48
+ # # use the `value:` keyword:
49
+ # value("RUBY", "Lisp? Smalltalk?", value: :rb)
50
+ # end
51
+ #
52
+ # # Now, resolve functions will receive `:rb` instead of `"RUBY"`
53
+ # field :favoriteLanguage, LanguageEnum
54
+ # resolve -> (obj, args, ctx) {
55
+ # args[:favoriteLanguage] # => :rb
56
+ # }
57
+ #
14
58
  class EnumType < GraphQL::BaseType
15
59
  accepts_definitions value: GraphQL::Define::AssignEnumValue
16
60
 
@@ -78,7 +122,7 @@ module GraphQL
78
122
 
79
123
  # A value within an {EnumType}
80
124
  #
81
- # Created with {EnumType#value}
125
+ # Created with the `value` helper
82
126
  class EnumValue
83
127
  attr_accessor :name, :description, :deprecation_reason, :value
84
128
  def initialize(name:, description:, deprecation_reason:, value:)
data/lib/graphql/field.rb CHANGED
@@ -5,40 +5,88 @@ module GraphQL
5
5
  #
6
6
  # They're usually created with the `field` helper. If you create it by hand, make sure {#name} is a String.
7
7
  #
8
- # @example creating a field
8
+ # A field must have a return type, but if you want to defer the return type calculation until later,
9
+ # you can pass a proc for the return type. That proc will be called when the schema is defined.
10
+ #
11
+ # @example Lazy type resolution
12
+ # # If the field's type isn't defined yet, you can pass a proc
13
+ # field :city, -> { TypeForModelName.find("City") }
14
+ #
15
+ # For complex field definition, you can pass a block to the `field` helper, eg `field :name do ... end`.
16
+ # This block is equivalent to calling `GraphQL::Field.define { ... }`.
17
+ #
18
+ # @example Defining a field with a block
19
+ # field :city, CityType do
20
+ # # field definition continues inside the block
21
+ # end
22
+ #
23
+ # ## Resolve
24
+ #
25
+ # Fields have `resolve` functions to determine their values at query-time.
26
+ # The default implementation is to call a method on the object based on the field name.
27
+ #
28
+ # @example Create a field which calls a method with the same name.
9
29
  # GraphQL::ObjectType.define do
10
30
  # field :name, types.String, "The name of this thing "
11
31
  # end
12
32
  #
13
- # @example handling a circular reference
14
- # # If the field's type isn't defined yet, you have two options:
33
+ # You can specify a custom proc with the `resolve` helper.
15
34
  #
35
+ # There are some shortcuts for common `resolve` implementations:
36
+ # - Provide `property:` to call a method with a different name than the field name
37
+ # - Provide `hash_key:` to resolve the field by doing a key lookup, eg `obj[:my_hash_key]`
38
+ #
39
+ # @example Create a field that calls a different method on the object
16
40
  # GraphQL::ObjectType.define do
17
- # # If you pass a Proc, it will be evaluated at schema build-time
18
- # field :city, -> { CityType }
19
- # # If you pass a String, it will be looked up in the global namespace at schema build-time
20
- # field :country, "CountryType"
41
+ # # use the `property` keyword:
42
+ # field :firstName, types.String, property: :first_name
21
43
  # end
22
44
  #
23
- # @example creating a field that accesses a different property on the object
45
+ # @example Create a field looks up with `[hash_key]`
24
46
  # GraphQL::ObjectType.define do
25
- # # use the `property` option:
26
- # field :firstName, types.String, property: :first_name
47
+ # # use the `hash_key` keyword:
48
+ # field :firstName, types.String, hash_key: :first_name
27
49
  # end
28
50
  #
29
- # @example defining a field, then attaching it to a type
30
- # name_field = GraphQL::Field.define do
31
- # name("Name")
32
- # type(!types.String)
33
- # description("The name of this thing")
34
- # resolve -> (object, arguments, context) { object.name }
51
+ # ## Arguments
52
+ #
53
+ # Fields can take inputs; they're called arguments. You can define them with the `argument` helper.
54
+ #
55
+ # @example Create a field with an argument
56
+ # field :students, types[StudentType] do
57
+ # argument :grade, types.Int
58
+ # resolve -> (obj, args, ctx) {
59
+ # Student.where(grade: args[:grade])
60
+ # }
35
61
  # end
36
62
  #
37
- # NamedType = GraphQL::ObjectType.define do
38
- # # use the `field` option:
39
- # field :name, field: name_field
63
+ # They can have default values which will be provided to `resolve` if the query doesn't include a value.
64
+ #
65
+ # @example Argument with a default value
66
+ # field :events, types[EventType] do
67
+ # # by default, don't include past events
68
+ # argument :includePast, types.Boolean, default_value: false
69
+ # resolve -> (obj, args, ctx) {
70
+ # args[:includePast] # => false if no value was provided in the query
71
+ # # ...
72
+ # }
40
73
  # end
41
74
  #
75
+ # Only certain types maybe used for inputs:
76
+ #
77
+ # - Scalars
78
+ # - Enums
79
+ # - Input Objects
80
+ # - Lists of those types
81
+ #
82
+ # Input types may also be non-null -- in that case, the query will fail
83
+ # if the input is not present.
84
+ #
85
+ # ## Complexity
86
+ #
87
+ # Fields can have _complexity_ values which describe the computation cost of resolving the field.
88
+ # You can provide the complexity as a constant with `complexity:` or as a proc, with the `complexity` helper.
89
+ #
42
90
  # @example Custom complexity values
43
91
  # # Complexity can be a number or a proc.
44
92
  #
@@ -57,12 +105,26 @@ module GraphQL
57
105
  # complexity -> (ctx, args, child_complexity) { child_complexity * args[:limit] }
58
106
  # end
59
107
  #
108
+ # @example Creating a field, then assigning it to a type
109
+ # name_field = GraphQL::Field.define do
110
+ # name("Name")
111
+ # type(!types.String)
112
+ # description("The name of this thing")
113
+ # resolve -> (object, arguments, context) { object.name }
114
+ # end
115
+ #
116
+ # NamedType = GraphQL::ObjectType.define do
117
+ # # The second argument may be a GraphQL::Field
118
+ # field :name, name_field
119
+ # end
120
+ #
60
121
  class Field
61
122
  include GraphQL::Define::InstanceDefinable
62
123
  accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, :complexity, :hash_key, argument: GraphQL::Define::AssignArgument
63
124
 
64
125
  lazy_defined_attr_accessor :deprecation_reason, :description, :property, :hash_key
65
126
 
127
+ # @return [<#call(obj, args,ctx)>] A proc-like object which can be called to return the field's value
66
128
  attr_reader :resolve_proc
67
129
 
68
130
  # @return [String] The name of this field on its {GraphQL::ObjectType} (or {GraphQL::InterfaceType})
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module Resolve
5
5
  module_function
6
6
 
7
- # @param [GraphQL::Field] A field that needs a resolve proc
7
+ # @param field [GraphQL::Field] A field that needs a resolve proc
8
8
  # @return [Proc] A resolver for this field, based on its config
9
9
  def create_proc(field)
10
10
  if field.property
@@ -1,13 +1,27 @@
1
1
  module GraphQL
2
- # A complex input type for a field argument.
2
+ # {InputObjectType}s are key-value inputs for fields.
3
+ #
4
+ # Input objects have _arguments_ which are identical to {GraphQL::Field} arguments.
5
+ # They map names to types and support default values.
6
+ # Their input types can be any input types, including {InputObjectType}s.
3
7
  #
4
8
  # @example An input type with name and number
5
9
  # PlayerInput = GraphQL::InputObjectType.define do
6
10
  # name("Player")
7
- # input_field :name, !types.String
8
- # input_field :number, !types.Int
11
+ # argument :name, !types.String
12
+ # argument :number, !types.Int
9
13
  # end
10
14
  #
15
+ # In a `resolve` function, you can access the values by making nested lookups on `args`.
16
+ #
17
+ # @example Accessing input values in a resolve function
18
+ # resolve -> (obj, args, ctx) {
19
+ # args[:player][:name] # => "Tony Gwynn"
20
+ # args[:player][:number] # => 19
21
+ # args[:player].to_h # { "name" => "Tony Gwynn", "number" => 19 }
22
+ # # ...
23
+ # }
24
+ #
11
25
  class InputObjectType < GraphQL::BaseType
12
26
  accepts_definitions(
13
27
  input_field: GraphQL::Define::AssignArgument,
@@ -1,7 +1,12 @@
1
1
  module GraphQL
2
- # A collection of types which implement the same fields
2
+ # An Interface contains a collection of types which implement some of the same fields.
3
3
  #
4
- # @example An interface with three required fields
4
+ # Interfaces can have fields, defined with `field`, just like an object type.
5
+ #
6
+ # Objects which implement this field _inherit_ field definitions from the interface.
7
+ # An object type can override the inherited definition by redefining that field.
8
+ #
9
+ # @example An interface with three fields
5
10
  # DeviceInterface = GraphQL::InterfaceType.define do
6
11
  # name("Device")
7
12
  # description("Hardware devices for computing")
@@ -11,6 +16,11 @@ module GraphQL
11
16
  # field :release_year, types.Int
12
17
  # end
13
18
  #
19
+ # @example Implementing an interface with an object type
20
+ # Laptoptype = GraphQL::ObjectType.define do
21
+ # interfaces [DeviceInterface]
22
+ # end
23
+ #
14
24
  class InterfaceType < GraphQL::BaseType
15
25
  include GraphQL::BaseType::HasPossibleTypes
16
26
  accepts_definitions :resolve_type, field: GraphQL::Define::AssignObjectField
@@ -1,7 +1,28 @@
1
1
  module GraphQL
2
- # A list type wraps another type.
2
+ # A list type modifies another type.
3
+ #
4
+ # List types can be created with the type helper (`types[InnerType]`)
5
+ # or {BaseType#to_list_type} (`InnerType.to_list_type`)
6
+ #
7
+ # For return types, it says that the returned value will be a list of the modified.
8
+ #
9
+ # @example A field which returns a list of items
10
+ # field :items, types[ItemType]
11
+ # # or
12
+ # field :items, ItemType.to_list_type
13
+ #
14
+ # For input types, it says that the incoming value will be a list of the modified type.
15
+ #
16
+ # @example A field which accepts a list of strings
17
+ # field :newNames do
18
+ # # ...
19
+ # argument :values, types[types.String]
20
+ # # or
21
+ # argument :values, types.String.to_list_type
22
+ # end
23
+ #
24
+ # Given a list type, you can always get the underlying type with {#unwrap}.
3
25
  #
4
- # Get the underlying type with {#unwrap}
5
26
  class ListType < GraphQL::BaseType
6
27
  include GraphQL::BaseType::ModifiesAnotherType
7
28
  attr_reader :of_type, :name
@@ -31,12 +52,10 @@ module GraphQL
31
52
  result
32
53
  end
33
54
 
34
-
35
55
  def coerce_non_null_input(value)
36
56
  ensure_array(value).map{ |item| of_type.coerce_input(item) }
37
57
  end
38
58
 
39
-
40
59
  private
41
60
 
42
61
  def ensure_array(value)
@@ -1,7 +1,32 @@
1
1
  module GraphQL
2
- # A non-null type wraps another type.
2
+ # A non-null type modifies another type.
3
+ #
4
+ # Non-null types can be created with `!` (`InnerType!`)
5
+ # or {BaseType#to_non_null_type} (`InnerType.to_non_null_type`)
6
+ #
7
+ # For return types, it says that the returned value will _always_ be present.
8
+ #
9
+ # @example A field which _always_ returns an error
10
+ # field :items, !ItemType
11
+ # # or
12
+ # field :items, ItemType.to_non_null_type
13
+ #
14
+ # (If the application fails to return a value, {InvalidNullError} will be raised.)
15
+ #
16
+ # For input types, it says that the incoming value _must_ be provided by the query.
17
+ #
18
+ # @example A field which _requires_ a string input
19
+ # field :newNames do
20
+ # # ...
21
+ # argument :values, !types.String
22
+ # # or
23
+ # argument :values, types.String.to_non_null_type
24
+ # end
25
+ #
26
+ # (If a value isn't provided, {Query::VariableValidationError} will be raised).
27
+ #
28
+ # Given a non-null type, you can always get the underlying type with {#unwrap}.
3
29
  #
4
- # Get the underlying type with {#unwrap}
5
30
  class NonNullType < GraphQL::BaseType
6
31
  include GraphQL::BaseType::ModifiesAnotherType
7
32