graphql 0.11.1 → 0.12.0

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