graphql 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/directive.rb +36 -0
  3. data/lib/graph_ql/directives/directive_chain.rb +33 -0
  4. data/lib/graph_ql/directives/include_directive.rb +15 -0
  5. data/lib/graph_ql/directives/skip_directive.rb +15 -0
  6. data/lib/graph_ql/enum.rb +34 -0
  7. data/lib/graph_ql/fields/abstract_field.rb +37 -0
  8. data/lib/graph_ql/fields/access_field.rb +24 -0
  9. data/lib/graph_ql/fields/field.rb +34 -0
  10. data/lib/graph_ql/interface.rb +14 -0
  11. data/lib/graph_ql/introspection/arguments_field.rb +5 -0
  12. data/lib/graph_ql/introspection/directive_type.rb +12 -0
  13. data/lib/graph_ql/introspection/enum_value_type.rb +10 -0
  14. data/lib/graph_ql/introspection/enum_values_field.rb +15 -0
  15. data/lib/graph_ql/introspection/field_type.rb +11 -0
  16. data/lib/graph_ql/introspection/fields_field.rb +14 -0
  17. data/lib/graph_ql/introspection/input_fields_field.rb +12 -0
  18. data/lib/graph_ql/introspection/input_value_type.rb +10 -0
  19. data/lib/graph_ql/introspection/of_type_field.rb +12 -0
  20. data/lib/graph_ql/introspection/possible_types_field.rb +12 -0
  21. data/lib/graph_ql/introspection/schema_type.rb +32 -0
  22. data/lib/graph_ql/introspection/type_kind_enum.rb +7 -0
  23. data/lib/graph_ql/introspection/type_type.rb +22 -0
  24. data/lib/graph_ql/parser/nodes.rb +72 -0
  25. data/lib/graph_ql/parser/parser.rb +108 -0
  26. data/lib/graph_ql/parser/transform.rb +86 -0
  27. data/lib/graph_ql/parser/visitor.rb +47 -0
  28. data/lib/graph_ql/query.rb +50 -0
  29. data/lib/graph_ql/query/arguments.rb +25 -0
  30. data/lib/graph_ql/query/field_resolution_strategy.rb +83 -0
  31. data/lib/graph_ql/query/fragment_spread_resolution_strategy.rb +16 -0
  32. data/lib/graph_ql/query/inline_fragment_resolution_strategy.rb +14 -0
  33. data/lib/graph_ql/query/operation_resolver.rb +28 -0
  34. data/lib/graph_ql/query/selection_resolver.rb +20 -0
  35. data/lib/graph_ql/query/type_resolver.rb +19 -0
  36. data/lib/graph_ql/repl.rb +27 -0
  37. data/lib/graph_ql/schema.rb +30 -0
  38. data/lib/graph_ql/schema/type_reducer.rb +44 -0
  39. data/lib/graph_ql/type_kinds.rb +15 -0
  40. data/lib/graph_ql/types/abstract_type.rb +14 -0
  41. data/lib/graph_ql/types/boolean_type.rb +6 -0
  42. data/lib/graph_ql/types/float_type.rb +6 -0
  43. data/lib/graph_ql/types/input_object_type.rb +17 -0
  44. data/lib/graph_ql/types/input_value.rb +10 -0
  45. data/lib/graph_ql/types/int_type.rb +6 -0
  46. data/lib/graph_ql/types/list_type.rb +10 -0
  47. data/lib/graph_ql/types/non_null_type.rb +18 -0
  48. data/lib/graph_ql/types/non_null_with_bang.rb +5 -0
  49. data/lib/graph_ql/types/object_type.rb +62 -0
  50. data/lib/graph_ql/types/scalar_type.rb +5 -0
  51. data/lib/graph_ql/types/string_type.rb +6 -0
  52. data/lib/graph_ql/types/type_definer.rb +16 -0
  53. data/lib/graph_ql/union.rb +35 -0
  54. data/lib/graph_ql/validations/fields_are_defined_on_type.rb +44 -0
  55. data/lib/graph_ql/validations/fields_will_merge.rb +80 -0
  56. data/lib/graph_ql/validations/fragments_are_used.rb +24 -0
  57. data/lib/graph_ql/validator.rb +29 -0
  58. data/lib/graph_ql/version.rb +3 -0
  59. data/lib/graphql.rb +92 -99
  60. data/readme.md +17 -177
  61. data/spec/graph_ql/directive_spec.rb +81 -0
  62. data/spec/graph_ql/enum_spec.rb +5 -0
  63. data/spec/graph_ql/fields/field_spec.rb +10 -0
  64. data/spec/graph_ql/interface_spec.rb +13 -0
  65. data/spec/graph_ql/introspection/directive_type_spec.rb +40 -0
  66. data/spec/graph_ql/introspection/schema_type_spec.rb +39 -0
  67. data/spec/graph_ql/introspection/type_type_spec.rb +104 -0
  68. data/spec/graph_ql/parser/parser_spec.rb +120 -0
  69. data/spec/graph_ql/parser/transform_spec.rb +109 -0
  70. data/spec/graph_ql/parser/visitor_spec.rb +31 -0
  71. data/spec/graph_ql/query/operation_resolver_spec.rb +14 -0
  72. data/spec/graph_ql/query_spec.rb +82 -0
  73. data/spec/graph_ql/schema/type_reducer_spec.rb +24 -0
  74. data/spec/graph_ql/types/input_object_type_spec.rb +12 -0
  75. data/spec/graph_ql/types/object_type_spec.rb +35 -0
  76. data/spec/graph_ql/union_spec.rb +27 -0
  77. data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +28 -0
  78. data/spec/graph_ql/validations/fields_will_merge_spec.rb +40 -0
  79. data/spec/graph_ql/validations/fragments_are_used_spec.rb +28 -0
  80. data/spec/graph_ql/validator_spec.rb +24 -0
  81. data/spec/spec_helper.rb +2 -2
  82. data/spec/support/dummy_app.rb +123 -63
  83. data/spec/support/dummy_data.rb +11 -0
  84. metadata +107 -59
  85. data/lib/graphql/call.rb +0 -8
  86. data/lib/graphql/connection.rb +0 -65
  87. data/lib/graphql/field.rb +0 -12
  88. data/lib/graphql/field_definer.rb +0 -25
  89. data/lib/graphql/introspection/call_type.rb +0 -13
  90. data/lib/graphql/introspection/connection.rb +0 -9
  91. data/lib/graphql/introspection/field_type.rb +0 -10
  92. data/lib/graphql/introspection/root_call_argument_node.rb +0 -5
  93. data/lib/graphql/introspection/root_call_type.rb +0 -20
  94. data/lib/graphql/introspection/schema_call.rb +0 -8
  95. data/lib/graphql/introspection/schema_type.rb +0 -17
  96. data/lib/graphql/introspection/type_call.rb +0 -8
  97. data/lib/graphql/introspection/type_type.rb +0 -18
  98. data/lib/graphql/node.rb +0 -244
  99. data/lib/graphql/parser/parser.rb +0 -39
  100. data/lib/graphql/parser/transform.rb +0 -22
  101. data/lib/graphql/query.rb +0 -109
  102. data/lib/graphql/root_call.rb +0 -202
  103. data/lib/graphql/root_call_argument.rb +0 -11
  104. data/lib/graphql/root_call_argument_definer.rb +0 -17
  105. data/lib/graphql/schema/all.rb +0 -46
  106. data/lib/graphql/schema/schema.rb +0 -87
  107. data/lib/graphql/schema/schema_validation.rb +0 -32
  108. data/lib/graphql/syntax/call.rb +0 -8
  109. data/lib/graphql/syntax/field.rb +0 -9
  110. data/lib/graphql/syntax/fragment.rb +0 -7
  111. data/lib/graphql/syntax/node.rb +0 -8
  112. data/lib/graphql/syntax/query.rb +0 -8
  113. data/lib/graphql/syntax/variable.rb +0 -7
  114. data/lib/graphql/types/boolean_type.rb +0 -3
  115. data/lib/graphql/types/number_type.rb +0 -3
  116. data/lib/graphql/types/object_type.rb +0 -6
  117. data/lib/graphql/types/string_type.rb +0 -3
  118. data/lib/graphql/version.rb +0 -3
  119. data/spec/graphql/node_spec.rb +0 -69
  120. data/spec/graphql/parser/parser_spec.rb +0 -168
  121. data/spec/graphql/parser/transform_spec.rb +0 -157
  122. data/spec/graphql/query_spec.rb +0 -274
  123. data/spec/graphql/root_call_spec.rb +0 -69
  124. data/spec/graphql/schema/schema_spec.rb +0 -93
  125. data/spec/graphql/schema/schema_validation_spec.rb +0 -48
  126. data/spec/support/nodes.rb +0 -175
@@ -0,0 +1,44 @@
1
+ class GraphQL::Validations::FieldsAreDefinedOnType
2
+ TYPE_INFERRENCE_ROOTS = [GraphQL::Nodes::OperationDefinition, GraphQL::Nodes::FragmentDefinition]
3
+ FIELD_MODIFIERS = [GraphQL::TypeKinds::LIST]
4
+
5
+ def validate(context)
6
+ visitor = context.visitor
7
+ TYPE_INFERRENCE_ROOTS.each do |node_class|
8
+ visitor[node_class] << -> (node){ validate_document_part(node, context) }
9
+ end
10
+ end
11
+
12
+ private
13
+ def validate_document_part(part, context)
14
+ if part.is_a?(GraphQL::Nodes::FragmentDefinition)
15
+ type = context.schema.types[part.type]
16
+ validate_selections(type, part.selections, context)
17
+ elsif part.is_a?(GraphQL::Nodes::OperationDefinition)
18
+ type = context.schema.public_send(part.operation_type) # mutation root or query root
19
+ validate_selections(type, part.selections, context)
20
+ end
21
+ end
22
+
23
+ def validate_selections(type, selections, context)
24
+ selections
25
+ .select {|f| f.is_a?(GraphQL::Nodes::Field) } # don't worry about fragments
26
+ .each do |ast_field|
27
+ field = type.fields[ast_field.name]
28
+ if field.nil?
29
+ context.errors << "Field '#{ast_field.name}' doesn't exist on type '#{type.name}'"
30
+ else
31
+ field_type = get_field_type(field)
32
+ validate_selections(field_type, ast_field.selections, context)
33
+ end
34
+ end
35
+ end
36
+
37
+ def get_field_type(field)
38
+ if FIELD_MODIFIERS.include?(field.type.kind)
39
+ field.type.of_type
40
+ else
41
+ field.type
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,80 @@
1
+ class GraphQL::Validations::FieldsWillMerge
2
+ HAS_SELECTIONS = [GraphQL::Nodes::OperationDefinition, GraphQL::Nodes::InlineFragment]
3
+
4
+ def validate(context)
5
+ fragments = {}
6
+ has_selections = []
7
+ visitor = context.visitor
8
+ HAS_SELECTIONS.each do |node_class|
9
+ visitor[node_class] << -> (node) { has_selections << node }
10
+ end
11
+ visitor[GraphQL::Nodes::FragmentDefinition] << -> (node) { fragments[node.name] = node }
12
+ visitor[GraphQL::Nodes::Document].leave << -> (node) {
13
+ has_selections.each { |node| validate_selections(node.selections, {}, fragments, context.errors)}
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ def validate_selections(selections, name_to_field, fragments, errors)
20
+ selections.each do |field|
21
+ if field.is_a?(GraphQL::Nodes::InlineFragment)
22
+ validate_selections(field.selections, name_to_field, fragments, errors)
23
+ elsif field.is_a?(GraphQL::Nodes::FragmentSpread)
24
+ fragment = fragments[field.name]
25
+ validate_selections(fragment.selections, name_to_field, fragments, errors)
26
+ else
27
+ field_errors = validate_field(field, name_to_field)
28
+ errors.push(*field_errors)
29
+ validate_selections(field.selections, name_to_field, fragments, errors)
30
+ end
31
+ end
32
+ end
33
+
34
+ def validate_field(field, name_to_field)
35
+ name_in_selection = field.alias || field.name
36
+ name_to_field[name_in_selection] ||= field
37
+ first_field_def = name_to_field[name_in_selection]
38
+ comparison = FieldDefinitionComparison.new(name_in_selection, first_field_def, field)
39
+ comparison.errors
40
+ end
41
+
42
+ # Compare two field definitions, add errors to the list if there are any
43
+ class FieldDefinitionComparison
44
+ NAMED_VALUES = [GraphQL::Nodes::Enum, GraphQL::Nodes::VariableIdentifier]
45
+ attr_reader :errors
46
+ def initialize(name, prev_def, next_def)
47
+ errors = []
48
+ if prev_def.name != next_def.name
49
+ errors << "Field '#{name}' has a field conflict: #{prev_def.name} or #{next_def.name}?"
50
+ end
51
+ prev_arguments = reduce_list(prev_def.arguments)
52
+ next_arguments = reduce_list(next_def.arguments)
53
+ if prev_arguments != next_arguments
54
+ errors << "Field '#{name}' has an argument conflict: #{JSON.dump(prev_arguments)} or #{JSON.dump(next_arguments)}?"
55
+ end
56
+ prev_directive_names = prev_def.directives.map(&:name)
57
+ next_directive_names = next_def.directives.map(&:name)
58
+ if prev_directive_names != next_directive_names
59
+ errors << "Field '#{name}' has a directive conflict: [#{prev_directive_names.join(", ")}] or [#{next_directive_names.join(", ")}]?"
60
+ end
61
+ prev_directive_args = prev_def.directives.map {|d| reduce_list(d.arguments) }
62
+ next_directive_args = next_def.directives.map {|d| reduce_list(d.arguments) }
63
+ if prev_directive_args != next_directive_args
64
+ errors << "Field '#{name}' has a directive argument conflict: #{JSON.dump(prev_directive_args)} or #{JSON.dump(next_directive_args)}?"
65
+ end
66
+ @errors = errors
67
+ end
68
+
69
+ private
70
+
71
+ # Turn AST tree into a hash
72
+ # can't look up args, the names just have to match
73
+ def reduce_list(args)
74
+ args.reduce({}) do |memo, a|
75
+ memo[a.name] = NAMED_VALUES.include?(a.value.class) ? a.value.name : a.value
76
+ memo
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,24 @@
1
+ class GraphQL::Validations::FragmentsAreUsed
2
+ def validate(context)
3
+ v = context.visitor
4
+ used_fragment_names = []
5
+ defined_fragment_names = []
6
+ v[GraphQL::Nodes::FragmentSpread] << -> (node) { used_fragment_names << node.name }
7
+ v[GraphQL::Nodes::FragmentDefinition] << -> (node) { defined_fragment_names << node.name}
8
+ v[GraphQL::Nodes::Document].leave << -> (node) { add_errors(context.errors, used_fragment_names, defined_fragment_names) }
9
+ end
10
+
11
+ private
12
+
13
+ def add_errors(errors, used_fragment_names, defined_fragment_names)
14
+ undefined_fragment_names = used_fragment_names - defined_fragment_names
15
+ if undefined_fragment_names.any?
16
+ errors << "Some fragments were used but not defined: #{undefined_fragment_names.join(", ")}"
17
+ end
18
+
19
+ unused_fragment_names = defined_fragment_names - used_fragment_names
20
+ if unused_fragment_names.any?
21
+ errors << "Some fragments were defined but not used: #{unused_fragment_names.join(", ")}"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ class GraphQL::Validator
2
+ VALIDATORS = [
3
+ GraphQL::Validations::FragmentsAreUsed,
4
+ ]
5
+
6
+ def initialize(schema:, validators: VALIDATORS)
7
+ @schema = schema
8
+ @validators = validators
9
+ end
10
+
11
+ def validate(document)
12
+ context = Context.new(@schema, document)
13
+ @validators.each do |validator|
14
+ validator.new.validate(context)
15
+ end
16
+ context.visitor.visit(document)
17
+ context.errors
18
+ end
19
+
20
+ class Context
21
+ attr_reader :schema, :document, :errors, :visitor
22
+ def initialize(schema, document)
23
+ @schema = schema
24
+ @document = document
25
+ @visitor = GraphQL::Visitor.new
26
+ @errors = []
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module GraphQL
2
+ VERSION = "0.1.0"
3
+ end
@@ -1,121 +1,114 @@
1
1
  require "active_support/core_ext/object/blank"
2
2
  require "active_support/core_ext/string/inflections"
3
+ require "active_support/dependencies/autoload"
3
4
  require "json"
4
5
  require "parslet"
5
6
 
6
7
  module GraphQL
7
- autoload(:Call, "graphql/call")
8
- autoload(:Connection, "graphql/connection")
9
- autoload(:FieldDefiner, "graphql/field_definer")
10
- autoload(:Field, "graphql/field")
11
- autoload(:Node, "graphql/node")
12
- autoload(:Query, "graphql/query")
13
- autoload(:RootCall, "graphql/root_call")
14
- autoload(:RootCallArgument, "graphql/root_call_argument")
15
- autoload(:RootCallArgumentDefiner, "graphql/root_call_argument_definer")
16
- autoload(:VERSION, "graphql/version")
8
+ extend ActiveSupport::Autoload
9
+ autoload(:Directive)
10
+ autoload(:Enum)
11
+ autoload(:Interface)
12
+ autoload(:Parser)
13
+ autoload(:Query)
14
+ autoload(:Repl)
15
+ autoload(:Schema)
16
+ autoload(:TypeKinds)
17
+ autoload(:Union)
18
+ autoload(:Validator)
19
+ autoload(:VERSION)
17
20
 
18
- # These objects are used for introspections (eg, responding to `schema()` calls).
19
- module Introspection
20
- autoload(:CallType, "graphql/introspection/call_type")
21
- autoload(:Connection, "graphql/introspection/connection")
22
- autoload(:FieldType, "graphql/introspection/field_type")
23
- autoload(:RootCallArgumentNode, "graphql/introspection/root_call_argument_node")
24
- autoload(:RootCallType, "graphql/introspection/root_call_type")
25
- autoload(:SchemaCall, "graphql/introspection/schema_call")
26
- autoload(:SchemaType, "graphql/introspection/schema_type")
27
- autoload(:TypeCall, "graphql/introspection/type_call")
28
- autoload(:TypeType, "graphql/introspection/type_type")
21
+ autoload_under "directives" do
22
+ autoload(:DirectiveChain)
23
+ autoload(:IncludeDirective)
24
+ autoload(:SkipDirective)
29
25
  end
30
26
 
31
- # These objects are singletons used to parse queries
32
- module Parser
33
- autoload(:Parser, "graphql/parser/parser")
34
- autoload(:Transform, "graphql/parser/transform")
27
+ autoload_under "fields" do
28
+ autoload(:AbstractField)
29
+ autoload(:AccessField)
30
+ autoload(:Field)
35
31
  end
36
32
 
37
- # These objects are used to track the schema of the graph
38
- module Schema
39
- autoload(:ALL, "graphql/schema/all")
40
- autoload(:Schema, "graphql/schema/schema")
41
- autoload(:SchemaValidation, "graphql/schema/schema_validation")
33
+ autoload_under "introspection" do
34
+ autoload(:ArgumentsField)
35
+ autoload(:DirectiveType)
36
+ autoload(:EnumValuesField)
37
+ autoload(:EnumValueType)
38
+ autoload(:FieldType)
39
+ autoload(:FieldsField)
40
+ autoload(:InputValueType)
41
+ autoload(:InputFieldsField)
42
+ autoload(:OfTypeField)
43
+ autoload(:PossibleTypesField)
44
+ autoload(:SchemaType)
45
+ autoload(:TypeKindEnum)
46
+ autoload(:TypeType)
42
47
  end
43
48
 
44
- # These objects are skinny wrappers for going from the AST to actual {Node} and {Field} instances.
45
- module Syntax
46
- autoload(:Call, "graphql/syntax/call")
47
- autoload(:Field, "graphql/syntax/field")
48
- autoload(:Query, "graphql/syntax/query")
49
- autoload(:Fragment, "graphql/syntax/fragment")
50
- autoload(:Node, "graphql/syntax/node")
51
- autoload(:Variable, "graphql/syntax/variable")
49
+ autoload_under "parser" do
50
+ autoload(:Nodes)
51
+ autoload(:Parser)
52
+ autoload(:Transform)
53
+ autoload(:Visitor)
52
54
  end
53
55
 
54
- # These objects expose values
55
- module Types
56
- autoload(:BooleanType, "graphql/types/boolean_type")
57
- autoload(:ObjectType, "graphql/types/object_type")
58
- autoload(:StringType, "graphql/types/string_type")
59
- autoload(:NumberType, "graphql/types/number_type")
56
+ autoload_under "types" do
57
+ autoload(:AbstractType)
58
+ autoload(:BOOLEAN_TYPE)
59
+ autoload(:ScalarType)
60
+ autoload(:FLOAT_TYPE)
61
+ autoload(:InputObjectType)
62
+ autoload(:InputValue)
63
+ autoload(:INT_TYPE)
64
+ autoload(:ListType)
65
+ autoload(:NonNullType)
66
+ autoload(:NonNullWithBang)
67
+ autoload(:ObjectType)
68
+ autoload(:STRING_TYPE)
69
+ autoload(:TypeDefiner)
60
70
  end
61
- # @abstract
62
- # Base class for all errors, so you can rescue from all graphql errors at once.
63
- class Error < RuntimeError; end
64
- # This node doesn't have a field with that name.
65
- class FieldNotDefinedError < Error
66
- def initialize(node_class, field_name)
67
- class_name = node_class.name
68
- defined_field_names = node_class.all_fields.keys
69
- super("#{class_name}##{field_name} was requested, but it isn't defined. Defined fields are: #{defined_field_names}")
70
- end
71
- end
72
- # The class that this node is supposed to expose isn't defined
73
- class ExposesClassMissingError < Error
74
- def initialize(node_class)
75
- super("#{node_class.name} exposes #{node_class.exposes_class_names.join(", ")}, but that class wasn't found.")
76
- end
77
- end
78
- # There's no Node defined for that kind of object.
79
- class NodeNotDefinedError < Error
80
- def initialize(node_name)
81
- super("#{node_name} was requested but was not found. Defined nodes are: #{SCHEMA.type_names}")
82
- end
83
- end
84
- # This node doesn't have a connection with that name.
85
- class ConnectionNotDefinedError < Error
86
- def initialize(node_name)
87
- super("#{node_name} was requested but was not found. Defined connections are: #{SCHEMA.connection_names}")
88
- end
89
- end
90
- # The root call of this query isn't in the schema.
91
- class RootCallNotDefinedError < Error
92
- def initialize(name)
93
- super("Call '#{name}' was requested but was not found. Defined calls are: #{SCHEMA.call_names}")
94
- end
71
+
72
+ module Validations
73
+ extend ActiveSupport::Autoload
74
+ autoload(:FieldsWillMerge)
75
+ autoload(:FragmentsAreUsed)
76
+ autoload(:FieldsAreDefinedOnType)
95
77
  end
96
- # The query couldn't be parsed.
97
- class SyntaxError < Error
98
- def initialize(line, col, string)
99
- lines = string.split("\n")
100
- super("Syntax Error at (#{line}, #{col}), check usage: #{string}")
101
- end
78
+
79
+ PARSER = Parser.new
80
+ TRANSFORM = Transform.new
81
+
82
+ def self.parse(string, as: nil)
83
+ parser = as ? GraphQL::PARSER.send(as) : GraphQL::PARSER
84
+ tree = parser.parse(string)
85
+ GraphQL::TRANSFORM.apply(tree)
86
+ rescue Parslet::ParseFailed => error
87
+ line, col = error.cause.source.line_and_column
88
+ raise [line, col, string].join(", ")
102
89
  end
103
- # This root call takes different arguments.
104
- class RootCallArgumentError < Error
105
- def initialize(declaration, actual)
106
- super("Wrong type for #{declaration.name}: expected a #{declaration.type} but got #{actual}")
90
+
91
+
92
+ module Definable
93
+ def attr_definable(*names)
94
+ attr_accessor(*names)
95
+ names.each do |name|
96
+ ivar_name = "@#{name}".to_sym
97
+ define_method(name) do |new_value=nil|
98
+ new_value && self.instance_variable_set(ivar_name, new_value)
99
+ instance_variable_get(ivar_name)
100
+ end
101
+ end
107
102
  end
108
103
  end
109
104
 
110
- # Singleton {Parser::Parser} instance
111
- PARSER = Parser::Parser.new
112
- # This singleton contains all defined nodes and fields.
113
- SCHEMA = Schema::Schema.instance
114
- # Singleton {Parser::Transform} instance
115
- TRANSFORM = Parser::Transform.new
116
- # preload these so they're in SCHEMA
117
- ["introspection", "types"].each do |preload_dir|
118
- Dir["#{File.dirname(__FILE__)}/graphql/#{preload_dir}/*.rb"].each { |f| require f }
105
+ module Forwardable
106
+ def delegate(*methods, to:)
107
+ methods.each do |method_name|
108
+ define_method(method_name) do |*args|
109
+ self.public_send(to).public_send(method_name, *args)
110
+ end
111
+ end
112
+ end
119
113
  end
120
- Node.field.__type__(:__type__)
121
- end
114
+ end
data/readme.md CHANGED
@@ -2,190 +2,30 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/rmosolgo/graphql-ruby.svg?branch=master)](https://travis-ci.org/rmosolgo/graphql-ruby)
4
4
  [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
5
- [![Dependency Status](https://gemnasium.com/rmosolgo/graphql-ruby.svg)](https://gemnasium.com/rmosolgo/graphql-ruby)
6
5
  [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
7
6
  [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
8
7
  [![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](http://rmosolgo.github.io/react-badges/)
9
8
 
9
+ __Current status__: rewriting according to spec, see also the previous [prototype implementation](https://github.com/rmosolgo/graphql-ruby/tree/74ad3c30a6d8db010ec3856f5871f8a02fcfba42)!
10
10
 
11
- Create a GraphQL interface by implementing [__nodes__](#nodes) and [__calls__](#calls), then running [__queries__](#queries).
11
+ ## Overview
12
12
 
13
- API Docs: <http://rubydoc.info/gems/graphql>
13
+ - See the __[test schema](https://github.com/rmosolgo/graphql-ruby/blob/master/spec/support/dummy_app.rb)__ for an example GraphQL schema in Ruby.
14
+ - See __[query_spec.rb](https://github.com/rmosolgo/graphql-ruby/blob/master/spec/graph_ql/query_spec.rb)__ for an example of query execution.
14
15
 
15
- ## To do:
16
+ ## To Do:
16
17
 
17
- - How do you express failure? HTTP response? `errors` key?
18
- - Handle blank objects in nested calls (how? wait for spec)
19
- - Implement calls as arguments
20
- - double-check how to handle `pals.first(3) { count }`
21
- - Implement call argument introspection (wait for spec)
18
+ - Validations:
19
+ - implement lots of validators
20
+ - build error object with position
21
+ - Express failure with `errors` key (http://facebook.github.io/graphql/#sec-Errors)
22
+ - directives:
23
+ - `@skip` has precedence over `@include`
24
+ - directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
25
+ - field merging (https://github.com/graphql/graphql-js/issues/19#issuecomment-118515077)
22
26
 
23
- ## Example Implementation
27
+ ## Goals:
24
28
 
25
- - See test implementation in [`/spec/support/dummy_app/nodes.rb`](https://github.com/rmosolgo/graphql/blob/master/spec/support/nodes.rb)
26
- - See `graphql-ruby-demo` with Rails on [github](https://github.com/rmosolgo/graphql-ruby-demo) or [heroku](http://graphql-ruby-demo.herokuapp.com/)
27
-
28
- <a href="http://graphql-ruby-demo.herokuapp.com/" target="_blank"><img src="https://cloud.githubusercontent.com/assets/2231765/6217972/5d24edda-b5ce-11e4-9e07-3548304af862.png" style="max-width: 800px;"/></a>
29
-
30
-
31
- ## Usage
32
-
33
- - Implement _nodes_ that wrap objects in your application
34
- - Implement _calls_ that return those objects (and may mutate the application state)
35
- - Execute _queries_ and return the result.
36
-
37
- ### Nodes
38
-
39
- Nodes are delegators that wrap objects in your app. You must whitelist fields by declaring them in the class definition.
40
-
41
-
42
- ```ruby
43
- class FishNode < GraphQL::Node
44
- exposes "Fish"
45
- cursor(:id)
46
- field.number(:id)
47
- field.string(:name)
48
- field.string(:species)
49
- # specify an `AquariumNode`:
50
- field.aquarium(:aquarium)
51
- end
52
- ```
53
-
54
- You can also declare connections between objects:
55
-
56
- ```ruby
57
- class AquariumNode < GraphQL::Node
58
- exposes "Aquarium"
59
- cursor(:id)
60
- field.number(:id)
61
- field.number(:occupancy)
62
- field.connection(:fishes)
63
- end
64
- ```
65
-
66
- You can make custom connections:
67
-
68
- ```ruby
69
- class FishSchoolConnection < GraphQL::Connection
70
- type :fish_school # now it is a field type
71
- call :largest, -> (prev_value, number) { fishes.sort_by(&:weight).first(number.to_i) }
72
-
73
- field.number(:count) # delegated to `target`
74
- field.boolean(:has_more)
75
-
76
- def has_more
77
- # the `largest()` call may have removed some items:
78
- target.count < original_target.count
79
- end
80
- end
81
- ```
82
-
83
- Then use them:
84
-
85
- ```ruby
86
- class AquariumNode < GraphQL::Node
87
- field.fish_school(:fishes)
88
- end
89
- ```
90
-
91
- And in queries:
92
-
93
- ```
94
- aquarium(1) {
95
- name,
96
- occupancy,
97
- fishes.largest(3) {
98
- edges {
99
- node { name, species }
100
- },
101
- count,
102
- has_more
103
- }
104
- }
105
- }
106
- ```
107
-
108
- ### Calls
109
-
110
- Calls selectively expose your application to the world. They always return values and they may perform mutations.
111
-
112
- Calls declare returns, declare arguments, and implement `#execute!`.
113
-
114
- This call just finds values:
115
-
116
- ```ruby
117
- class FindFishCall < GraphQL::RootCall
118
- returns :fish
119
- argument.number(:id)
120
- def execute!(id)
121
- Fish.find(id)
122
- end
123
- end
124
- ```
125
-
126
- This call performs a mutation:
127
-
128
- ```ruby
129
- class RelocateFishCall < GraphQL::RootCall
130
- returns :fish, :previous_aquarium, :new_aquarium
131
- argument.number(:fish_id)
132
- argument.number(:new_aquarium_id)
133
-
134
- def execute!(fish_id, new_aquarium_id)
135
- fish = Fish.find(fish_id)
136
-
137
- # context is defined by the query, see below
138
- if !context[:user].can_move?(fish)
139
- raise RelocateNotAllowedError
140
- end
141
-
142
- previous_aquarium = fish.aquarium
143
- new_aquarium = Aquarium.find(new_aquarium_id)
144
- fish.update_attributes(aquarium: new_aquarium)
145
- {
146
- fish: fish,
147
- previous_aquarium: previous_aquarium,
148
- new_aquarium: new_aquarium,
149
- }
150
- end
151
- end
152
- ```
153
-
154
- ### Queries
155
-
156
- When your system is setup, you can perform queries from a string.
157
-
158
- ```ruby
159
- query_str = "find_fish(1) { name, species } "
160
- query = GraphQL::Query.new(query_str)
161
- result = query.as_result
162
-
163
- result
164
- # {
165
- # "1" => {
166
- # "name" => "Sharky",
167
- # "species" => "Goldfish",
168
- # }
169
- # }
170
- ```
171
-
172
- Each query may also define a `context` object which will be accessible at every point in execution.
173
-
174
- ```ruby
175
- query_str = "move_fish(1, 3) { fish { name }, new_aquarium { occupancy } }"
176
- query_ctx = {user: current_user, request: request}
177
- query = GraphQL::Query.new(query_str, context: query_ctx)
178
- result = query.as_result
179
-
180
- result
181
- # {
182
- # "fish" => {
183
- # "name" => "Sharky"
184
- # },
185
- # "new_aquarium" => {
186
- # "occupancy" => 12
187
- # }
188
- # }
189
- ```
190
-
191
- You could do something like this [inside a Rails controller](https://github.com/rmosolgo/graphql-ruby-demo/blob/master/app/controllers/queries_controller.rb#L5).
29
+ - Implement the GraphQL spec & support a Relay front end
30
+ - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible
31
+ - Support `graphql-rails`