graphql 0.3.0 → 0.4.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 (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