graphql 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/{types/input_value.rb → argument.rb} +4 -1
  3. data/lib/graph_ql/{scalars/boolean_type.rb → boolean_type.rb} +0 -0
  4. data/lib/graph_ql/definition_helpers.rb +10 -0
  5. data/lib/graph_ql/definition_helpers/argument_definer.rb +5 -2
  6. data/lib/graph_ql/definition_helpers/definable.rb +1 -1
  7. data/lib/graph_ql/definition_helpers/field_definer.rb +4 -1
  8. data/lib/graph_ql/definition_helpers/non_null_with_bang.rb +3 -1
  9. data/lib/graph_ql/definition_helpers/string_named_hash.rb +7 -1
  10. data/lib/graph_ql/definition_helpers/type_definer.rb +13 -1
  11. data/lib/graph_ql/directive.rb +14 -6
  12. data/lib/graph_ql/{directives → directive}/include_directive.rb +1 -1
  13. data/lib/graph_ql/{directives → directive}/skip_directive.rb +1 -1
  14. data/lib/graph_ql/enum_type.rb +66 -0
  15. data/lib/graph_ql/field.rb +49 -13
  16. data/lib/graph_ql/{scalars/float_type.rb → float_type.rb} +0 -0
  17. data/lib/graph_ql/{scalars/id_type.rb → id_type.rb} +0 -0
  18. data/lib/graph_ql/input_object_type.rb +33 -0
  19. data/lib/graph_ql/{scalars/int_type.rb → int_type.rb} +0 -0
  20. data/lib/graph_ql/interface_type.rb +33 -0
  21. data/lib/graph_ql/introspection/field_type.rb +1 -0
  22. data/lib/graph_ql/introspection/fields_field.rb +1 -0
  23. data/lib/graph_ql/introspection/input_value_type.rb +1 -1
  24. data/lib/graph_ql/introspection/introspection_query.rb +77 -0
  25. data/lib/graph_ql/introspection/schema_field.rb +13 -0
  26. data/lib/graph_ql/introspection/type_by_name_field.rb +14 -0
  27. data/lib/graph_ql/introspection/type_kind_enum.rb +1 -1
  28. data/lib/graph_ql/{types/list_type.rb → list_type.rb} +3 -0
  29. data/lib/graph_ql/{parser/nodes.rb → nodes.rb} +8 -1
  30. data/lib/graph_ql/{types/non_null_type.rb → non_null_type.rb} +3 -0
  31. data/lib/graph_ql/object_type.rb +92 -0
  32. data/lib/graph_ql/parser.rb +107 -6
  33. data/lib/graph_ql/query.rb +8 -0
  34. data/lib/graph_ql/query/arguments.rb +13 -4
  35. data/lib/graph_ql/{directives → query}/directive_chain.rb +2 -2
  36. data/lib/graph_ql/query/field_resolution_strategy.rb +2 -2
  37. data/lib/graph_ql/query/operation_resolver.rb +3 -3
  38. data/lib/graph_ql/query/selection_resolver.rb +1 -1
  39. data/lib/graph_ql/scalar_type.rb +9 -0
  40. data/lib/graph_ql/schema.rb +26 -17
  41. data/lib/graph_ql/schema/type_reducer.rb +7 -0
  42. data/lib/graph_ql/static_validation/arguments_validator.rb +1 -0
  43. data/lib/graph_ql/static_validation/message.rb +4 -1
  44. data/lib/graph_ql/static_validation/rules/fields_are_defined_on_type.rb +1 -0
  45. data/lib/graph_ql/static_validation/rules/fields_have_appropriate_selections.rb +1 -0
  46. data/lib/graph_ql/static_validation/type_stack.rb +24 -4
  47. data/lib/graph_ql/static_validation/validator.rb +27 -0
  48. data/lib/graph_ql/{scalars/string_type.rb → string_type.rb} +0 -0
  49. data/lib/graph_ql/transform.rb +87 -0
  50. data/lib/graph_ql/type_kinds.rb +9 -0
  51. data/lib/graph_ql/{types/union.rb → union_type.rb} +8 -4
  52. data/lib/graph_ql/version.rb +1 -1
  53. data/lib/graph_ql/{parser/visitor.rb → visitor.rb} +29 -4
  54. data/lib/graphql.rb +28 -11
  55. data/readme.md +11 -1
  56. data/spec/graph_ql/{types/enum_spec.rb → enum_type_spec.rb} +1 -1
  57. data/spec/graph_ql/{fields/field_spec.rb → field_spec.rb} +0 -0
  58. data/spec/graph_ql/{scalars/id_type_spec.rb → id_type_spec.rb} +0 -0
  59. data/spec/graph_ql/{types/input_object_type_spec.rb → input_object_type_spec.rb} +0 -0
  60. data/spec/graph_ql/{types/interface_spec.rb → interface_type_spec.rb} +1 -1
  61. data/spec/graph_ql/introspection/introspection_query_spec.rb +10 -0
  62. data/spec/graph_ql/introspection/schema_type_spec.rb +0 -4
  63. data/spec/graph_ql/introspection/type_type_spec.rb +1 -5
  64. data/spec/graph_ql/{types/object_type_spec.rb → object_type_spec.rb} +0 -0
  65. data/spec/graph_ql/{parser/parser_spec.rb → parser_spec.rb} +0 -0
  66. data/spec/graph_ql/query/operation_resolver_spec.rb +1 -1
  67. data/spec/graph_ql/query_spec.rb +6 -2
  68. data/spec/graph_ql/schema/type_validator_spec.rb +1 -1
  69. data/spec/graph_ql/{parser/transform_spec.rb → transform_spec.rb} +0 -0
  70. data/spec/graph_ql/{types/union_spec.rb → union_type_spec.rb} +2 -2
  71. data/spec/graph_ql/{parser/visitor_spec.rb → visitor_spec.rb} +0 -0
  72. data/spec/support/{dummy_app.rb → dairy_app.rb} +8 -8
  73. data/spec/support/{dummy_data.rb → dairy_data.rb} +0 -0
  74. data/spec/support/star_wars_data.rb +71 -0
  75. data/spec/support/star_wars_schema.rb +87 -0
  76. metadata +59 -50
  77. data/lib/graph_ql/definition_helpers/forwardable.rb +0 -10
  78. data/lib/graph_ql/parser/parser.rb +0 -108
  79. data/lib/graph_ql/parser/transform.rb +0 -87
  80. data/lib/graph_ql/scalars/scalar_type.rb +0 -5
  81. data/lib/graph_ql/types/enum.rb +0 -32
  82. data/lib/graph_ql/types/input_object_type.rb +0 -14
  83. data/lib/graph_ql/types/interface.rb +0 -14
  84. data/lib/graph_ql/types/object_type.rb +0 -66
@@ -0,0 +1,13 @@
1
+ # A wrapper to implement `__schema`
2
+ class GraphQL::Introspection::SchemaField
3
+ DEFINITION = Proc.new { |f, wrapped_type|
4
+ f.name("__schema")
5
+ f.description("This GraphQL schema")
6
+ f.type(!GraphQL::Introspection::SchemaType)
7
+ f.resolve -> (o, a, c) { wrapped_type }
8
+ }
9
+
10
+ def self.create(wrapped_type)
11
+ GraphQL::Field.new { |f| DEFINITION.call(f, wrapped_type) }
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # A wrapper to create `__type(name: )` dynamically.
2
+ class GraphQL::Introspection::TypeByNameField
3
+ DEFINITION = Proc.new { |f, type, field, arg, type_hash|
4
+ f.name("__type")
5
+ f.description("A type in the GraphQL system")
6
+ f.arguments({name: arg.build(type: !type.String)})
7
+ f.type(!GraphQL::Introspection::TypeType)
8
+ f.resolve -> (o, args, c) { type_hash[args["name"]] }
9
+ }
10
+
11
+ def self.create(type_hash)
12
+ GraphQL::Field.new { |f, type, field, arg| DEFINITION.call(f, type, field, arg, type_hash) }
13
+ end
14
+ end
@@ -1,4 +1,4 @@
1
- GraphQL::Introspection::TypeKindEnum = GraphQL::Enum.new do |e|
1
+ GraphQL::Introspection::TypeKindEnum = GraphQL::EnumType.new do |e|
2
2
  e.name "__TypeKind"
3
3
  e.description "The kinds of types in this GraphQL system"
4
4
  GraphQL::TypeKinds::KIND_NAMES.each do |kind_name|
@@ -1,3 +1,6 @@
1
+ # A list type wraps another type.
2
+ #
3
+ # See {TypeKind#unwrap} for accessing the modified type
1
4
  class GraphQL::ListType < GraphQL::ObjectType
2
5
  attr_reader :of_type
3
6
  def initialize(of_type:)
@@ -4,6 +4,8 @@ module GraphQL::Nodes
4
4
  # - expose accessors for keyword arguments
5
5
  class AbstractNode
6
6
  attr_accessor :line, :col
7
+
8
+ # @param options [Hash] Must contain all attributes defined by {required_attrs}, may also include `position_source`
7
9
  def initialize(options)
8
10
  required_keys = self.class.required_attrs
9
11
  allowed_keys = required_keys + [:line, :col]
@@ -31,15 +33,17 @@ module GraphQL::Nodes
31
33
  end
32
34
  end
33
35
 
36
+ # Test all attributes, checking for any other nodes below this one
34
37
  def children
35
38
  self.class.required_attrs
36
39
  .map { |attr| send(attr) }
37
- .flatten # eg #fields is a list of children
40
+ .flatten
38
41
  .select { |val| val.is_a?(GraphQL::Nodes::AbstractNode) }
39
42
  end
40
43
 
41
44
  class << self
42
45
  attr_reader :required_attrs
46
+ # Defines attributes which are required at initialization.
43
47
  def attr_required(*attr_names)
44
48
  @required_attrs ||= []
45
49
  @required_attrs += attr_names
@@ -48,6 +52,9 @@ module GraphQL::Nodes
48
52
 
49
53
  # Create a new AbstractNode child which
50
54
  # requires and exposes {attr_names}.
55
+ # @param attr_names [Array<Symbol>] Attributes this node class will have
56
+ # @param block [Block] Block passed to `Class.new`
57
+ # @return [Class] A new node class
51
58
  def create(*attr_names, &block)
52
59
  cls = Class.new(self, &block)
53
60
  cls.attr_required(*attr_names)
@@ -1,3 +1,6 @@
1
+ # A non-null type wraps another type.
2
+ #
3
+ # See {TypeKind#unwrap} for accessing the modified type
1
4
  class GraphQL::NonNullType < GraphQL::ObjectType
2
5
  attr_reader :of_type
3
6
  def initialize(of_type:)
@@ -0,0 +1,92 @@
1
+ # This type exposes fields on an object.
2
+ #
3
+ #
4
+ class GraphQL::ObjectType
5
+ include GraphQL::DefinitionHelpers::NonNullWithBang
6
+ extend GraphQL::DefinitionHelpers::Definable
7
+ attr_definable :name, :description, :interfaces, :fields
8
+
9
+ def initialize(&block)
10
+ self.fields = []
11
+ self.interfaces = []
12
+ yield(
13
+ self,
14
+ GraphQL::DefinitionHelpers::TypeDefiner.instance,
15
+ GraphQL::DefinitionHelpers::FieldDefiner.instance,
16
+ GraphQL::DefinitionHelpers::ArgumentDefiner.instance
17
+ )
18
+ end
19
+
20
+ # @overload fields(new_fields)
21
+ # Define `new_fields` as the fields this type exposes, uses {#fields=}
22
+ #
23
+ # @overload fields()
24
+ # @return [Hash] fields this type exposes
25
+ def fields(new_fields=nil)
26
+ if !new_fields.nil?
27
+ self.fields = new_fields
28
+ end
29
+ @fields
30
+ end
31
+
32
+
33
+ # Define fields to be `new_fields`, normalize with {StringNamedHash}
34
+ # @param new_fields [Hash] The fields exposed by this type
35
+ def fields=(new_fields)
36
+ @fields = GraphQL::DefinitionHelpers::StringNamedHash.new(new_fields).to_h
37
+ end
38
+
39
+ # @overload interfaces(new_interfaces)
40
+ # Declare that this type implements `new_interfaces`.
41
+ # Shovel this type into each interface's `possible_types` array.
42
+ #
43
+ # (There's a bug here: if you define interfaces twice, it won't remove previous definitions.)
44
+ #
45
+ # @param new_interfaces [Array<GraphQL::Interface>] interfaces that this type implements
46
+ #
47
+ # @overload interfaces
48
+ # @return [Array<GraphQL::Interface>] interfaces that this type implements
49
+ #
50
+ def interfaces(new_interfaces=nil)
51
+ if !new_interfaces.nil?
52
+ @interfaces = new_interfaces
53
+ new_interfaces.each {|i| i.possible_types << self }
54
+ end
55
+ @interfaces
56
+ end
57
+
58
+ def kind
59
+ GraphQL::TypeKinds::OBJECT
60
+ end
61
+
62
+ # Print the human-readable name of this type
63
+ def to_s
64
+ Printer.instance.print(self)
65
+ end
66
+
67
+ alias :inspect :to_s
68
+
69
+ # @param other [GraphQL::ObjectType] compare to this object
70
+ # @return [Boolean] are these types equivalent? (incl. non-null, list)
71
+ def ==(other)
72
+ if other.is_a?(GraphQL::ObjectType)
73
+ self.to_s == other.to_s
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # Print a type, using the query-style naming pattern
80
+ class Printer
81
+ include Singleton
82
+ def print(type)
83
+ if type.kind.non_null?
84
+ "#{print(type.of_type)}!"
85
+ elsif type.kind.list?
86
+ "[#{print(type.of_type)}]"
87
+ else
88
+ type.name
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,9 +1,110 @@
1
- require 'graph_ql/parser/nodes'
2
- require 'graph_ql/parser/parser'
3
- require 'graph_ql/parser/transform'
4
- require 'graph_ql/parser/visitor'
5
-
6
1
  module GraphQL
2
+ # Parser is a [parslet](http://kschiess.github.io/parslet/) parser for parsing queries.
3
+ #
4
+ class Parser < Parslet::Parser
5
+ root(:document)
6
+ rule(:document) { (
7
+ space |
8
+ operation_definition |
9
+ fragment_definition
10
+ ).repeat(1).as(:document_parts)
11
+ }
12
+
13
+ # TODO: whitespace sensitive regarding `on`, eg `onFood`, see lookahead note in spec
14
+ rule(:fragment_definition) {
15
+ str("fragment").as(:fragment_keyword) >>
16
+ space? >> name.as(:fragment_name) >>
17
+ space? >> str("on") >> space? >> name.as(:type_condition) >>
18
+ space? >> directives.maybe.as(:optional_directives).as(:directives) >>
19
+ space? >> selections.as(:selections)
20
+ }
21
+
22
+ rule(:fragment_spread) {
23
+ spread.as(:fragment_spread_keyword) >> space? >>
24
+ name.as(:fragment_spread_name) >> space? >>
25
+ directives.maybe.as(:optional_directives).as(:directives)
26
+ }
27
+ rule(:spread) { str("...") }
28
+ # TODO: `on` bug, see spec
29
+ rule(:inline_fragment) {
30
+ spread.as(:fragment_spread_keyword) >> space? >>
31
+ str("on ") >> name.as(:inline_fragment_type) >> space? >>
32
+ directives.maybe.as(:optional_directives).as(:directives) >> space? >>
33
+ selections.as(:selections)
34
+ }
35
+
36
+ rule(:operation_definition) { (unnamed_selections | named_operation_definition) }
37
+ rule(:unnamed_selections) { selections.as(:unnamed_selections)}
38
+ rule(:named_operation_definition) {
39
+ operation_type.as(:operation_type) >> space? >>
40
+ name.as(:name) >> space? >>
41
+ operation_variable_definitions.maybe.as(:optional_variables).as(:variables) >> space? >>
42
+ directives.maybe.as(:optional_directives).as(:directives) >> space? >>
43
+ selections.as(:selections)
44
+ }
45
+ rule(:operation_type) { (str("query") | str("mutation")) }
46
+ rule(:operation_variable_definitions) { str("(") >> space? >> (operation_variable_definition >> separator?).repeat(1) >> space? >> str(")") }
47
+ rule(:operation_variable_definition) {
48
+ value_variable.as(:variable_name) >> space? >>
49
+ str(":") >> space? >>
50
+ type.as(:variable_type) >> space? >>
51
+ (str("=") >> space? >> value.as(:variable_default_value)).maybe.as(:variable_optional_default_value)}
52
+
53
+ rule(:selection) { (inline_fragment | fragment_spread | field) >> space? >> separator? }
54
+ rule(:selections) { str("{") >> space? >> selection.repeat(1) >> space? >> str("}")}
55
+
56
+ rule(:field) {
57
+ field_alias.maybe.as(:alias) >>
58
+ name.as(:field_name) >>
59
+ field_arguments.maybe.as(:optional_field_arguments).as(:field_arguments) >> space? >>
60
+ directives.maybe.as(:optional_directives).as(:directives) >> space? >>
61
+ selections.maybe.as(:optional_selections).as(:selections)
62
+ }
63
+
64
+ rule(:field_alias) { name.as(:alias_name) >> space? >> str(":") >> space? }
65
+ rule(:field_arguments) { str("(") >> field_argument.repeat(1) >> str(")") }
66
+ rule(:field_argument) { name.as(:field_argument_name) >> str(":") >> space? >> value.as(:field_argument_value) >> separator? }
67
+
68
+ rule(:directives) { (directive >> separator?).repeat(1) }
69
+ rule(:directive) {
70
+ str("@") >> name.as(:directive_name) >>
71
+ directive_arguments.maybe.as(:optional_directive_arguments).as(:directive_arguments)
72
+ }
73
+ rule(:directive_arguments) { str("(") >> directive_argument.repeat(1) >> str(")") }
74
+ rule(:directive_argument) { name.as(:directive_argument_name) >> str(":") >> space? >> value.as(:directive_argument_value) >> separator? }
75
+
76
+ rule(:type) { (non_null_type | list_type | type_name)}
77
+ rule(:list_type) { str("[") >> type.as(:list_type) >> str("]")}
78
+ rule(:non_null_type) { (list_type | type_name).as(:non_null_type) >> str("!") }
79
+ rule(:type_name) { name.as(:type_name) }
80
+
81
+ rule(:value) {(
82
+ value_input_object |
83
+ value_float |
84
+ value_int |
85
+ value_string |
86
+ value_boolean |
87
+ value_array |
88
+ value_variable |
89
+ value_enum
90
+ )}
91
+ rule(:value_sign?) { match('[\-\+]').maybe }
92
+ rule(:value_array) { (str("[") >> (value >> separator?).repeat(0) >> str("]")).as(:array) }
93
+ rule(:value_boolean) { (str("true") | str("false")).as(:boolean) }
94
+ rule(:value_float) { (value_sign? >> match('\d').repeat(1) >> str(".") >> match('\d').repeat(1) >> (match("[eE]") >> value_sign? >> match('\d').repeat(1)).maybe).as(:float) }
95
+ rule(:value_input_object) { str("{") >> value_input_object_pair.repeat(1).as(:input_object) >> str("}") }
96
+ rule(:value_input_object_pair) { space? >> name.as(:input_object_name) >> space? >> str(":") >> space? >> value.as(:input_object_value) >> separator? }
97
+ rule(:value_int) { (value_sign? >> match('\d').repeat(1)).as(:int) }
98
+ # TODO: support unicode, escaped chars (match the spec)
99
+ rule(:value_string) { str('"') >> match('[^\"]').repeat(1).as(:string) >> str('"')}
100
+ rule(:value_enum) { name.as(:enum) }
101
+ rule(:value_variable) { str("$") >> name.as(:variable) }
102
+
103
+ rule(:separator?) { (space? >> str(",") >> space?).maybe }
104
+ rule(:name) { match('[_A-Za-z]') >> match('[_0-9A-Za-z]').repeat(0) }
105
+ rule(:comment) { str("#") >> match('[^\r\n]').repeat(0) }
106
+ rule(:space) { (match('[\s\n]+') | comment).repeat(1) }
107
+ rule(:space?) { space.maybe }
108
+ end
7
109
  PARSER = GraphQL::Parser.new
8
- TRANSFORM = GraphQL::Transform.new
9
110
  end
@@ -5,6 +5,13 @@ class GraphQL::Query
5
5
  DEFAULT_RESOLVE = :__default_resolve
6
6
  attr_reader :schema, :document, :context, :fragments, :params
7
7
 
8
+ # Prepare query `query_string` on {GraphQL::Schema} `schema`
9
+ # @param schema [GraphQL::Schema]
10
+ # @param query_string [String]
11
+ # @param context [#[]] (default: `nil`) an arbitrary hash of values which you can access in {GraphQL::Field#resolve}
12
+ # @param params [Hash] (default: `{}`) values for `$variables` in the query
13
+ # @param debug [Boolean] (default: `true`) if true, errors are raised, if false, errors are put in the `errors` key
14
+ # @param validate [Boolean] (default: `true`) if true, `query_string` will be validated with {StaticValidation::Validator}
8
15
  def initialize(schema, query_string, context: nil, params: {}, debug: true, validate: true)
9
16
  @schema = schema
10
17
  @debug = debug
@@ -82,3 +89,4 @@ require 'graph_ql/query/inline_fragment_resolution_strategy'
82
89
  require 'graph_ql/query/operation_resolver'
83
90
  require 'graph_ql/query/selection_resolver'
84
91
  require 'graph_ql/query/type_resolver'
92
+ require 'graph_ql/query/directive_chain'
@@ -1,8 +1,11 @@
1
- # Creates a plain hash out of arguments, looking up variables if necessary
1
+ require 'forwardable'
2
+
3
+ # Provide read-only access to arguments by string or symbol names.
2
4
  class GraphQL::Query::Arguments
3
- attr_reader :to_h
5
+ extend Forwardable
6
+
4
7
  def initialize(ast_arguments, argument_hash, variables)
5
- @to_h = ast_arguments.reduce({}) do |memo, arg|
8
+ @hash = ast_arguments.reduce({}) do |memo, arg|
6
9
  arg_defn = argument_hash[arg.name]
7
10
  value = reduce_value(arg.value, arg_defn, variables)
8
11
  memo[arg.name] = value
@@ -10,6 +13,12 @@ class GraphQL::Query::Arguments
10
13
  end
11
14
  end
12
15
 
16
+ def_delegators :@hash, :keys, :values
17
+
18
+ def [](key)
19
+ @hash[key.to_s]
20
+ end
21
+
13
22
  private
14
23
 
15
24
  def reduce_value(value, arg_defn, variables)
@@ -18,7 +27,7 @@ class GraphQL::Query::Arguments
18
27
  elsif value.is_a?(GraphQL::Nodes::Enum)
19
28
  value = arg_defn.type.coerce(value.name)
20
29
  elsif value.is_a?(GraphQL::Nodes::InputObject)
21
- value = self.class.new(value.pairs, arg_defn.type.input_fields, variables).to_h
30
+ value = self.class.new(value.pairs, arg_defn.type.input_fields, variables)
22
31
  else
23
32
  value
24
33
  end
@@ -1,4 +1,4 @@
1
- class GraphQL::DirectiveChain
1
+ class GraphQL::Query::DirectiveChain
2
2
  DIRECTIVE_ON = {
3
3
  GraphQL::Nodes::Field => GraphQL::Directive::ON_FIELD,
4
4
  GraphQL::Nodes::InlineFragment => GraphQL::Directive::ON_FRAGMENT,
@@ -24,7 +24,7 @@ class GraphQL::DirectiveChain
24
24
  @result = block.call
25
25
  else
26
26
  applicable_directives.map do |(ast_directive, directive)|
27
- args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, operation_resolver.variables).to_h
27
+ args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, operation_resolver.variables)
28
28
  @result = directive.resolve(args, block)
29
29
  end
30
30
  @result ||= {}
@@ -3,8 +3,8 @@ class GraphQL::Query::FieldResolutionStrategy
3
3
 
4
4
  def initialize(ast_field, parent_type, target, operation_resolver)
5
5
  field_name = ast_field.name
6
- field = parent_type.fields[field_name] || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
7
- arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables).to_h
6
+ field = operation_resolver.query.schema.get_field(parent_type, field_name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
7
+ arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables)
8
8
  value = field.resolve(target, arguments, operation_resolver.context)
9
9
  if value.nil?
10
10
  @result_value = value
@@ -1,14 +1,14 @@
1
1
  class GraphQL::Query::OperationResolver
2
- extend GraphQL::Forwardable
3
- attr_reader :variables, :query
2
+ attr_reader :variables, :query, :context
4
3
 
5
4
  def initialize(operation_definition, query)
6
5
  @operation_definition = operation_definition
7
6
  @variables = query.params
8
7
  @query = query
8
+ @context = query.context
9
9
  end
10
10
 
11
- delegate :context, to: :query
11
+
12
12
 
13
13
  def result
14
14
  @result ||= execute(@operation_definition, query)
@@ -9,7 +9,7 @@ class GraphQL::Query::SelectionResolver
9
9
 
10
10
  def initialize(target, type, selections, operation_resolver)
11
11
  @result = selections.reduce({}) do |memo, ast_field|
12
- chain = GraphQL::DirectiveChain.new(ast_field, operation_resolver) {
12
+ chain = GraphQL::Query::DirectiveChain.new(ast_field, operation_resolver) {
13
13
  strategy_class = RESOLUTION_STRATEGIES[ast_field.class]
14
14
  strategy = strategy_class.new(ast_field, type, target, operation_resolver)
15
15
  strategy.result
@@ -0,0 +1,9 @@
1
+ module GraphQL
2
+ # The parent type for scalars, eg {GraphQL::STRING_TYPE}, {GraphQL::INT_TYPE}
3
+ #
4
+ class ScalarType < GraphQL::ObjectType
5
+ def kind
6
+ GraphQL::TypeKinds::SCALAR
7
+ end
8
+ end
9
+ end
@@ -1,23 +1,13 @@
1
+ # A GraphQL schema which may be queried with {GraphQL::Query}.
1
2
  class GraphQL::Schema
2
- DIRECTIVES = [GraphQL::SkipDirective, GraphQL::IncludeDirective]
3
+ DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective]
4
+ DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
3
5
 
4
6
  attr_reader :query, :mutation, :directives, :static_validator
5
- def initialize(query:, mutation:)
6
- # Add fields to this query root for introspection:
7
- query.fields = query.fields.merge({
8
- "__type" => GraphQL::Field.new do |f, type, field, arg|
9
- f.description("A type in the GraphQL system")
10
- f.arguments({name: arg.build(type: !type.String)})
11
- f.type(!GraphQL::Introspection::TypeType)
12
- f.resolve -> (o, a, c) { self.types[a["name"]] }
13
- end,
14
- "__schema" => GraphQL::Field.new do |f|
15
- f.description("This GraphQL schema")
16
- f.type(!GraphQL::Introspection::SchemaType)
17
- f.resolve -> (o, a, c) { self }
18
- end
19
- })
20
7
 
8
+ # @param query [GraphQL::ObjectType] the query root for the schema
9
+ # @param mutation [GraphQL::ObjectType, nil] the mutation root for the schema
10
+ def initialize(query:, mutation:)
21
11
  @query = query
22
12
  @mutation = mutation
23
13
  @directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m }
@@ -29,8 +19,27 @@ class GraphQL::Schema
29
19
  end
30
20
  end
31
21
 
22
+ # A `{ name => type }` hash of types in this schema
23
+ # @returns Hash
32
24
  def types
33
- @types ||= TypeReducer.new(query, {}).result
25
+ @types ||= TypeReducer.find_all([query, mutation, GraphQL::Introspection::SchemaType].compact)
26
+ end
27
+
28
+ # Resolve field named `field_name` for type `parent_type`.
29
+ # Handles dynamic fields `__typename`, `__type` and `__schema`, too
30
+ def get_field(parent_type, field_name)
31
+ defined_field = parent_type.fields[field_name]
32
+ if defined_field
33
+ defined_field
34
+ elsif field_name == "__typename"
35
+ GraphQL::Introspection::TypenameField.create(parent_type)
36
+ elsif field_name == "__schema" && parent_type == query
37
+ GraphQL::Introspection::SchemaField.create(self)
38
+ elsif field_name == "__type" && parent_type == query
39
+ GraphQL::Introspection::TypeByNameField.create(self.types)
40
+ else
41
+ nil
42
+ end
34
43
  end
35
44
  end
36
45