graphql 0.11.1 → 0.12.0

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +5 -2
  3. data/lib/graphql/argument.rb +8 -3
  4. data/lib/graphql/base_type.rb +5 -3
  5. data/lib/graphql/boolean_type.rb +6 -1
  6. data/lib/graphql/define.rb +8 -0
  7. data/lib/graphql/define/assign_argument.rb +19 -0
  8. data/lib/graphql/define/assign_enum_value.rb +16 -0
  9. data/lib/graphql/define/assign_object_field.rb +19 -0
  10. data/lib/graphql/define/assignment_dictionary.rb +26 -0
  11. data/lib/graphql/define/defined_object_proxy.rb +32 -0
  12. data/lib/graphql/define/instance_definable.rb +79 -0
  13. data/lib/graphql/{definition_helpers → define}/non_null_with_bang.rb +1 -1
  14. data/lib/graphql/{definition_helpers → define}/type_definer.rb +1 -1
  15. data/lib/graphql/directive.rb +7 -2
  16. data/lib/graphql/enum_type.rb +12 -6
  17. data/lib/graphql/execution_error.rb +1 -1
  18. data/lib/graphql/field.rb +26 -23
  19. data/lib/graphql/float_type.rb +2 -3
  20. data/lib/graphql/id_type.rb +9 -1
  21. data/lib/graphql/input_object_type.rb +11 -8
  22. data/lib/graphql/int_type.rb +2 -1
  23. data/lib/graphql/interface_type.rb +7 -2
  24. data/lib/graphql/introspection/input_value_type.rb +10 -1
  25. data/lib/graphql/invalid_null_error.rb +1 -1
  26. data/lib/graphql/object_type.rb +27 -25
  27. data/lib/graphql/query.rb +5 -8
  28. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -10
  29. data/lib/graphql/scalar_type.rb +4 -3
  30. data/lib/graphql/schema.rb +1 -1
  31. data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +1 -1
  32. data/lib/graphql/static_validation/validation_context.rb +5 -4
  33. data/lib/graphql/static_validation/validator.rb +3 -3
  34. data/lib/graphql/string_type.rb +2 -1
  35. data/lib/graphql/union_type.rb +1 -1
  36. data/lib/graphql/version.rb +1 -1
  37. data/readme.md +4 -10
  38. data/spec/graphql/boolean_type_spec.rb +20 -0
  39. data/spec/graphql/define/instance_definable_spec.rb +55 -0
  40. data/spec/graphql/field_spec.rb +14 -2
  41. data/spec/graphql/float_type_spec.rb +15 -0
  42. data/spec/graphql/id_type_spec.rb +9 -0
  43. data/spec/graphql/int_type_spec.rb +15 -0
  44. data/spec/graphql/introspection/input_value_type_spec.rb +36 -0
  45. data/spec/graphql/introspection/type_type_spec.rb +0 -23
  46. data/spec/graphql/query_spec.rb +16 -0
  47. data/spec/graphql/static_validation/complexity_validator_spec.rb +1 -1
  48. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +4 -3
  49. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +4 -3
  50. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -3
  51. data/spec/graphql/static_validation/rules/document_does_not_exceed_max_depth_spec.rb +9 -1
  52. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +3 -2
  53. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -3
  54. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +4 -3
  55. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +4 -3
  56. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -3
  57. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -3
  58. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +4 -3
  59. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +4 -3
  60. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +4 -3
  61. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +5 -4
  62. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +4 -3
  63. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +4 -3
  64. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +4 -3
  65. data/spec/graphql/static_validation/type_stack_spec.rb +3 -2
  66. data/spec/graphql/static_validation/validator_spec.rb +3 -25
  67. data/spec/graphql/string_type_spec.rb +15 -0
  68. data/spec/support/dairy_app.rb +2 -0
  69. metadata +25 -9
  70. data/lib/graphql/definition_helpers.rb +0 -4
  71. data/lib/graphql/definition_helpers/defined_by_config.rb +0 -123
  72. data/lib/graphql/definition_helpers/string_named_hash.rb +0 -22
@@ -1,6 +1,5 @@
1
1
  GraphQL::FLOAT_TYPE = GraphQL::ScalarType.define do
2
2
  name "Float"
3
- coerce -> (value) do
4
- value.is_a?(Numeric) ? value.to_f : nil
5
- end
3
+ coerce_input -> (value) { value.is_a?(Numeric) ? value.to_f : nil }
4
+ coerce_result -> (value) { value.to_f }
6
5
  end
@@ -1,4 +1,12 @@
1
1
  GraphQL::ID_TYPE = GraphQL::ScalarType.define do
2
2
  name "ID"
3
- coerce -> (value) { value.to_s }
3
+ coerce_result -> (value) { value.to_s }
4
+ coerce_input -> (value) {
5
+ case value
6
+ when String, Fixnum, Bignum
7
+ value.to_s
8
+ else
9
+ nil
10
+ end
11
+ }
4
12
  end
@@ -8,12 +8,15 @@
8
8
  # end
9
9
  #
10
10
  class GraphQL::InputObjectType < GraphQL::BaseType
11
- attr_accessor :name, :description, :input_fields
12
- defined_by_config :name, :description, :input_fields
13
- alias :arguments :input_fields
11
+ accepts_definitions input_field: GraphQL::Define::AssignArgument
14
12
 
15
- def input_fields=(new_fields)
16
- @input_fields = GraphQL::DefinitionHelpers::StringNamedHash.new(new_fields).to_h
13
+ # @return [Hash<String, GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations
14
+ attr_accessor :arguments
15
+
16
+ alias :input_fields :arguments
17
+
18
+ def initialize
19
+ @arguments = {}
17
20
  end
18
21
 
19
22
  def kind
@@ -25,13 +28,13 @@ class GraphQL::InputObjectType < GraphQL::BaseType
25
28
 
26
29
  # Items in the input that are unexpected
27
30
  input.each do |name, value|
28
- if input_fields[name].nil?
31
+ if arguments[name].nil?
29
32
  result.add_problem("Field is not defined on #{self.name}", [name])
30
33
  end
31
34
  end
32
35
 
33
36
  # Items in the input that are expected, but have invalid values
34
- invalid_fields = input_fields.map do |name, field|
37
+ invalid_fields = arguments.map do |name, field|
35
38
  field_result = field.type.validate_input(input[name])
36
39
  if !field_result.valid?
37
40
  result.merge_result!(name, field_result)
@@ -44,7 +47,7 @@ class GraphQL::InputObjectType < GraphQL::BaseType
44
47
  def coerce_non_null_input(value)
45
48
  input_values = {}
46
49
 
47
- input_fields.each do |input_key, input_field_defn|
50
+ arguments.each do |input_key, input_field_defn|
48
51
  field_value = value[input_key]
49
52
  field_value = input_field_defn.type.coerce_input(field_value)
50
53
 
@@ -1,4 +1,5 @@
1
1
  GraphQL::INT_TYPE = GraphQL::ScalarType.define do
2
2
  name "Int"
3
- coerce -> (value) { value.is_a?(Numeric) ? value.to_i : nil }
3
+ coerce_input -> (value) { value.is_a?(Numeric) ? value.to_i : nil }
4
+ coerce_result -> (value) { value.to_i }
4
5
  end
@@ -12,8 +12,13 @@
12
12
  #
13
13
  class GraphQL::InterfaceType < GraphQL::BaseType
14
14
  include GraphQL::BaseType::HasPossibleTypes
15
- defined_by_config :name, :description, :fields, :resolve_type
16
- attr_accessor :name, :description, :fields
15
+ accepts_definitions :resolve_type, field: GraphQL::Define::AssignObjectField
16
+
17
+ attr_accessor :fields
18
+
19
+ def initialize
20
+ @fields = {}
21
+ end
17
22
 
18
23
  def kind
19
24
  GraphQL::TypeKinds::INTERFACE
@@ -4,5 +4,14 @@ GraphQL::Introspection::InputValueType = GraphQL::ObjectType.define do
4
4
  field :name, !types.String, "The key for this value"
5
5
  field :description, types.String, "What this value is used for"
6
6
  field :type, -> { !GraphQL::Introspection::TypeType }, "The expected type for this value"
7
- field :defaultValue, types.String, "The value applied if no other value is provided", property: :default_value
7
+ field :defaultValue, types.String, "The value applied if no other value is provided" do
8
+ resolve -> (obj, args, ctx) {
9
+ value = obj.default_value
10
+ if value.is_a?(String)
11
+ "\"#{value}\""
12
+ else
13
+ value
14
+ end
15
+ }
16
+ end
8
17
  end
@@ -1,7 +1,7 @@
1
1
  module GraphQL
2
2
  # Raised automatically when a field's resolve function returns `nil`
3
3
  # for a non-null field.
4
- class InvalidNullError < RuntimeError
4
+ class InvalidNullError < GraphQL::Error
5
5
  def initialize(field_name, value)
6
6
  @field_name = field_name
7
7
  @value = value
@@ -1,37 +1,39 @@
1
1
  # This type exposes fields on an object.
2
2
  #
3
- # @example defining a type for your IMDB clone
4
- # MovieType = GraphQL::ObjectType.define do
5
- # name "Movie"
6
- # description "A full-length film or a short film"
7
- # interfaces [ProductionInterface, DurationInterface]
3
+ # @example defining a type for your IMDB clone
4
+ # MovieType = GraphQL::ObjectType.define do
5
+ # name "Movie"
6
+ # description "A full-length film or a short film"
7
+ # interfaces [ProductionInterface, DurationInterface]
8
8
  #
9
- # field :runtimeMinutes, !types.Int, property: :runtime_minutes
10
- # field :director, PersonType
11
- # field :cast, CastType
12
- # field :starring, types[PersonType] do
13
- # arguments :limit, types.Int
14
- # resolve -> (object, args, ctx) {
15
- # stars = object.cast.stars
16
- # args[:limit] && stars = stars.limit(args[:limit])
17
- # stars
18
- # }
19
- # end
20
- # end
9
+ # field :runtimeMinutes, !types.Int, property: :runtime_minutes
10
+ # field :director, PersonType
11
+ # field :cast, CastType
12
+ # field :starring, types[PersonType] do
13
+ # arguments :limit, types.Int
14
+ # resolve -> (object, args, ctx) {
15
+ # stars = object.cast.stars
16
+ # args[:limit] && stars = stars.limit(args[:limit])
17
+ # stars
18
+ # }
19
+ # end
20
+ # end
21
21
  #
22
22
  class GraphQL::ObjectType < GraphQL::BaseType
23
- defined_by_config :name, :description, :interfaces, :fields
24
- attr_accessor :name, :description, :interfaces, :fields
23
+ accepts_definitions :interfaces, field: GraphQL::Define::AssignObjectField
24
+ attr_accessor :name, :description, :interfaces
25
25
 
26
- # Define fields to be `new_fields`, normalize with {StringNamedHash}
27
- # @param new_fields [Hash] The fields exposed by this type
28
- def fields=(new_fields)
29
- @fields = GraphQL::DefinitionHelpers::StringNamedHash.new(new_fields).to_h
26
+ # @return [Hash<String, GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
27
+ attr_accessor :fields
28
+
29
+ def initialize
30
+ @fields = {}
31
+ @interfaces = []
30
32
  end
31
33
 
32
- # Shovel this type into each interface's `possible_types` array.
34
+ # Shovel this type into each interface's `possible_types` array.
33
35
  #
34
- # @param new_interfaces [Array<GraphQL::Interface>] interfaces that this type implements
36
+ # @param new_interfaces [Array<GraphQL::Interface>] interfaces that this type implements
35
37
  def interfaces=(new_interfaces)
36
38
  @interfaces ||= []
37
39
  (@interfaces - new_interfaces).each { |i| i.possible_types.delete(self) }
@@ -1,16 +1,12 @@
1
1
  class GraphQL::Query
2
- class OperationNameMissingError < StandardError
2
+ class OperationNameMissingError < GraphQL::Error
3
3
  def initialize(names)
4
4
  msg = "You must provide an operation name from: #{names.join(", ")}"
5
5
  super(msg)
6
6
  end
7
7
  end
8
8
 
9
- # If a resolve function returns `GraphQL::Query::DEFAULT_RESOLVE`,
10
- # The executor will send the field's name to the target object
11
- # and use the result.
12
- DEFAULT_RESOLVE = :__default_resolve
13
- attr_reader :schema, :document, :context, :fragments, :operations, :debug
9
+ attr_reader :schema, :document, :context, :fragments, :operations, :debug, :max_depth
14
10
 
15
11
  # Prepare query `query_string` on `schema`
16
12
  # @param schema [GraphQL::Schema]
@@ -20,9 +16,10 @@ class GraphQL::Query
20
16
  # @param debug [Boolean] if true, errors are raised, if false, errors are put in the `errors` key
21
17
  # @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
22
18
  # @param operation_name [String] if the query string contains many operations, this is the one which should be executed
23
- def initialize(schema, query_string, context: nil, variables: {}, debug: false, validate: true, operation_name: nil)
19
+ def initialize(schema, query_string, context: nil, variables: {}, debug: false, validate: true, operation_name: nil, max_depth: nil)
24
20
  @schema = schema
25
21
  @debug = debug
22
+ @max_depth = max_depth || schema.max_depth
26
23
  @context = Context.new(query: self, values: context)
27
24
  @validate = validate
28
25
  @operation_name = operation_name
@@ -72,7 +69,7 @@ class GraphQL::Query
72
69
  private
73
70
 
74
71
  def validation_errors
75
- @validation_errors ||= schema.static_validator.validate(document)
72
+ @validation_errors ||= schema.static_validator.validate(self)
76
73
  end
77
74
 
78
75
 
@@ -62,22 +62,12 @@ module GraphQL
62
62
 
63
63
 
64
64
  # Execute the field's resolve method
65
- # then handle the DEFAULT_RESOLVE
66
65
  # @return [Proc] suitable to be the last step in a middleware chain
67
66
  def get_middleware_proc_from_field_resolve
68
67
  -> (_parent_type, parent_object, field_definition, field_args, context, _next) {
69
68
  context.ast_node = ast_node
70
69
  value = field_definition.resolve(parent_object, field_args, context)
71
70
  context.ast_node = nil
72
-
73
- if value == GraphQL::Query::DEFAULT_RESOLVE
74
- begin
75
- value = target.public_send(ast_node.name)
76
- rescue NoMethodError => err
77
- raise("Couldn't resolve field '#{ast_node.name}' to #{parent_object.class} '#{parent_object}' (resulted in #{err})")
78
- end
79
- end
80
-
81
71
  value
82
72
  }
83
73
  end
@@ -11,8 +11,7 @@ module GraphQL
11
11
  # end
12
12
  #
13
13
  class ScalarType < GraphQL::BaseType
14
- defined_by_config :name, :coerce, :coerce_input, :coerce_result, :description
15
- attr_accessor :name, :description
14
+ accepts_definitions :coerce, :coerce_input, :coerce_result
16
15
 
17
16
  def coerce=(proc)
18
17
  self.coerce_input = proc
@@ -21,7 +20,9 @@ module GraphQL
21
20
 
22
21
  def validate_non_null_input(value)
23
22
  result = Query::InputValidationResult.new
24
- result.add_problem("Could not coerce value #{JSON.dump(value)} to #{name}") if coerce_non_null_input(value).nil?
23
+ if coerce_non_null_input(value).nil?
24
+ result.add_problem("Could not coerce value #{JSON.dump(value)} to #{name}")
25
+ end
25
26
  result
26
27
  end
27
28
 
@@ -69,7 +69,7 @@ class GraphQL::Schema
69
69
  GraphQL::Schema::TypeExpression.new(self, ast_node).type
70
70
  end
71
71
 
72
- class InvalidTypeError < StandardError
72
+ class InvalidTypeError < GraphQL::Error
73
73
  def initialize(type, errors)
74
74
  super("Type #{type.respond_to?(:name) ? type.name : "Unnamed type" } is invalid: #{errors.join(", ")}")
75
75
  end
@@ -4,7 +4,7 @@ module GraphQL
4
4
  include GraphQL::StaticValidation::Message::MessageHelper
5
5
 
6
6
  def validate(context)
7
- max_allowed_depth = context.schema.max_depth
7
+ max_allowed_depth = context.query.max_depth
8
8
  return if max_allowed_depth.nil?
9
9
 
10
10
  visitor = context.visitor
@@ -11,10 +11,11 @@ module GraphQL
11
11
  # It also provides limited access to the {TypeStack} instance,
12
12
  # which tracks state as you climb in and out of different fields.
13
13
  class ValidationContext
14
- attr_reader :schema, :document, :errors, :visitor, :fragments, :operations
15
- def initialize(schema, document)
16
- @schema = schema
17
- @document = document
14
+ attr_reader :query, :schema, :document, :errors, :visitor, :fragments, :operations
15
+ def initialize(query)
16
+ @query = query
17
+ @schema = query.schema
18
+ @document = query.document
18
19
  @fragments = {}
19
20
  @operations = {}
20
21
 
@@ -18,12 +18,12 @@ class GraphQL::StaticValidation::Validator
18
18
  # Validate `document` against the schema. Returns an array of message hashes.
19
19
  # @param document [GraphQL::Language::Nodes::Document]
20
20
  # @return [Array<Hash>]
21
- def validate(document)
22
- context = GraphQL::StaticValidation::ValidationContext.new(@schema, document)
21
+ def validate(query)
22
+ context = GraphQL::StaticValidation::ValidationContext.new(query)
23
23
  @rules.each do |rules|
24
24
  rules.new.validate(context)
25
25
  end
26
- context.visitor.visit(document)
26
+ context.visitor.visit(query.document)
27
27
  context.errors.map(&:to_h)
28
28
  end
29
29
  end
@@ -1,4 +1,5 @@
1
1
  GraphQL::STRING_TYPE = GraphQL::ScalarType.define do
2
2
  name "String"
3
- coerce -> (value) { value.to_s }
3
+ coerce_result -> (value) { value.to_s }
4
+ coerce_input -> (value) { value.is_a?(String) ? value : nil }
4
5
  end
@@ -11,7 +11,7 @@
11
11
  class GraphQL::UnionType < GraphQL::BaseType
12
12
  include GraphQL::BaseType::HasPossibleTypes
13
13
  attr_accessor :name, :description, :possible_types
14
- defined_by_config :name, :description, :possible_types, :resolve_type
14
+ accepts_definitions :possible_types, :resolve_type
15
15
 
16
16
  def kind
17
17
  GraphQL::TypeKinds::UNION
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.11.1"
2
+ VERSION = "0.12.0"
3
3
  end
data/readme.md CHANGED
@@ -134,18 +134,12 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
134
134
 
135
135
  ## To Do
136
136
 
137
- - Code clean-up
138
- - Raise if you try to configure an attribute which doesn't suit the type (ie, if you try to define `resolve` on an ObjectType, it should somehow raise)
139
- - make `DefinitionHelpers` more friendly for extension
140
137
  - Interface's possible types should be a property of the schema, not the interface
141
138
  - Type lookup should be by type name (to support reloaded constants in Rails code)
142
- - Depth validator should be aware of fragments
143
139
  - Add a complexity validator (reject queries if they're too big)
144
- - Add a custom dump for Relay (it expects default value strings to be double-quoted)
145
140
  - Add docs for shared behaviors & DRY code
146
141
  - Optimize the pure-Ruby parser (hand-write, RACC?!)
147
- - Big ideas:
148
- - Revamp the fixture Schema to be more useful (better names, more extensible)
149
- - __Subscriptions__
150
- - This is a good chance to make an `Operation` abstraction of which `query`, `mutation` and `subscription` are members
151
- - For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
142
+ - Revamp the fixture Schema to be more useful (better names, more extensible)
143
+ - __Subscriptions__
144
+ - This is a good chance to make an `Operation` abstraction of which `query`, `mutation` and `subscription` are members
145
+ - For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::BOOLEAN_TYPE do
4
+ describe "coerce_input" do
5
+ def coerce_input(input)
6
+ GraphQL::BOOLEAN_TYPE.coerce_input(input)
7
+ end
8
+
9
+ it "accepts true and false" do
10
+ assert_equal true, coerce_input(true)
11
+ assert_equal false, coerce_input(false)
12
+ end
13
+
14
+ it "rejects other types" do
15
+ assert_equal nil, coerce_input("true")
16
+ assert_equal nil, coerce_input(5.5)
17
+ assert_equal nil, coerce_input(nil)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ require "date"
2
+ require "spec_helper"
3
+
4
+ module Garden
5
+ module DefinePlantBetween
6
+ def self.call(plant, plant_range)
7
+ plant.start_planting_on = plant_range.begin
8
+ plant.end_planting_on = plant_range.end
9
+ end
10
+ end
11
+
12
+ class Vegetable
13
+ attr_accessor :name, :start_planting_on, :end_planting_on
14
+ include GraphQL::Define::InstanceDefinable
15
+ accepts_definitions :name, plant_between: DefinePlantBetween
16
+
17
+ # definition added later:
18
+ attr_accessor :height
19
+ end
20
+ end
21
+
22
+ describe GraphQL::Define::InstanceDefinable do
23
+ describe "extending definitions" do
24
+ before do
25
+ Garden::Vegetable.accepts_definitions(:height)
26
+ end
27
+
28
+ after do
29
+ Garden::Vegetable.own_dictionary.delete(:height)
30
+ end
31
+
32
+ it "accepts after-the-fact definitions" do
33
+ corn = Garden::Vegetable.define do
34
+ name "Corn"
35
+ height 8
36
+ end
37
+
38
+ assert_equal "Corn", corn.name
39
+ assert_equal 8, corn.height
40
+ end
41
+ end
42
+
43
+ describe "applying custom definitions" do
44
+ it "uses custom callables" do
45
+ tomato = Garden::Vegetable.define do
46
+ name "Tomato"
47
+ plant_between Date.new(2000, 4, 20)..Date.new(2000, 6, 1)
48
+ end
49
+
50
+ assert_equal "Tomato", tomato.name
51
+ assert_equal Date.new(2000, 4, 20), tomato.start_planting_on
52
+ assert_equal Date.new(2000, 6, 1), tomato.end_planting_on
53
+ end
54
+ end
55
+ end