graphql 0.0.4 → 0.1.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 (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`