graphql 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +78 -9
  3. data/lib/graphql/call.rb +7 -0
  4. data/lib/graphql/connection.rb +44 -0
  5. data/lib/graphql/field.rb +117 -0
  6. data/lib/graphql/field_definer.rb +29 -0
  7. data/lib/graphql/introspection/call_node.rb +13 -0
  8. data/lib/graphql/introspection/connection.rb +9 -0
  9. data/lib/graphql/introspection/field_node.rb +19 -0
  10. data/lib/graphql/introspection/root_call_argument_node.rb +5 -0
  11. data/lib/graphql/introspection/root_call_node.rb +16 -0
  12. data/lib/graphql/introspection/schema_call.rb +8 -0
  13. data/lib/graphql/introspection/schema_node.rb +17 -0
  14. data/lib/graphql/introspection/type_call.rb +8 -0
  15. data/lib/graphql/introspection/type_node.rb +16 -0
  16. data/lib/graphql/node.rb +141 -34
  17. data/lib/graphql/parser.rb +19 -8
  18. data/lib/graphql/query.rb +64 -21
  19. data/lib/graphql/root_call.rb +176 -0
  20. data/lib/graphql/root_call_argument.rb +8 -0
  21. data/lib/graphql/root_call_argument_definer.rb +20 -0
  22. data/lib/graphql/schema.rb +99 -0
  23. data/lib/graphql/syntax/call.rb +3 -12
  24. data/lib/graphql/syntax/field.rb +4 -2
  25. data/lib/graphql/syntax/node.rb +3 -10
  26. data/lib/graphql/syntax/query.rb +7 -0
  27. data/lib/graphql/syntax/variable.rb +7 -0
  28. data/lib/graphql/transform.rb +14 -5
  29. data/lib/graphql/types/boolean_field.rb +3 -0
  30. data/lib/graphql/types/connection_field.rb +30 -0
  31. data/lib/graphql/types/cursor_field.rb +9 -0
  32. data/lib/graphql/types/number_field.rb +3 -0
  33. data/lib/graphql/types/object_field.rb +8 -0
  34. data/lib/graphql/types/string_field.rb +3 -0
  35. data/lib/graphql/types/type_field.rb +6 -0
  36. data/lib/graphql/version.rb +3 -0
  37. data/readme.md +142 -10
  38. data/spec/graphql/field_spec.rb +66 -0
  39. data/spec/graphql/node_spec.rb +68 -0
  40. data/spec/graphql/parser_spec.rb +75 -25
  41. data/spec/graphql/query_spec.rb +185 -83
  42. data/spec/graphql/root_call_spec.rb +55 -0
  43. data/spec/graphql/schema_spec.rb +128 -0
  44. data/spec/graphql/transform_spec.rb +124 -39
  45. data/spec/spec_helper.rb +2 -1
  46. data/spec/support/dummy_app.rb +43 -23
  47. data/spec/support/nodes.rb +145 -32
  48. metadata +78 -16
  49. data/lib/graphql/collection_edge.rb +0 -62
  50. data/lib/graphql/syntax/edge.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c86e3ad590a80d6430b2fd781fb097e433d5e5c4
4
- data.tar.gz: 60d41b9666ffaa6d63a73238fd572fdbbba38218
3
+ metadata.gz: 290e890d0a805c3587000af2fbbacf910cf8baf0
4
+ data.tar.gz: 745511749686efb279e0ee7b7af2484d0ccd82ed
5
5
  SHA512:
6
- metadata.gz: 27b1a4571a57b15e35dff3dcab058d8f8b9926c374361a6df52930296a1be705a49a5b345e23ad128d73eb81675a0b798e0b225c0c5fc92904cb76ebf3aaccfe
7
- data.tar.gz: 5594ef107005a1d7abca4e9f6099a6c4f515176679d4421126e7a332a433271f367d447a981263a89accd573b8060ec22b1ee7f71809cb5a8c23e19d534eccee
6
+ metadata.gz: 81ba92e0aed50365b5ccd78ab02c035bc0a1ae212669f7d5c668cbe7fec3e48158f9a53a5859b2ece3ebc54d3634187635477a85cf50f2b8d24a822476517b53
7
+ data.tar.gz: 3a46fb67c848c7b85768979071d28df3b2e21e29087395b5a3ab2297d0660bd51ba84c628678e098a1faae392ef9a4e30d8be0007575ef32468f98c2c043badf
data/lib/graphql.rb CHANGED
@@ -1,26 +1,95 @@
1
- require "parslet"
2
- require "active_support/core_ext/string/inflections"
3
1
  require "active_support/core_ext/object/blank"
2
+ require "active_support/core_ext/string/inflections"
3
+ require "json"
4
+ require "parslet"
5
+ require "singleton"
4
6
 
5
7
  module GraphQL
6
- VERSION = "0.0.1"
7
-
8
- autoload(:CollectionEdge, "graphql/collection_edge")
8
+ autoload(:Call, "graphql/call")
9
+ autoload(:Connection, "graphql/connection")
10
+ autoload(:Field, "graphql/field")
11
+ autoload(:FieldDefiner, "graphql/field_definer")
12
+ autoload(:Node, "graphql/node")
9
13
  autoload(:Parser, "graphql/parser")
10
14
  autoload(:Query, "graphql/query")
11
- autoload(:Node, "graphql/node")
15
+ autoload(:RootCall, "graphql/root_call")
16
+ autoload(:RootCallArgument, "graphql/root_call_argument")
17
+ autoload(:RootCallArgumentDefiner, "graphql/root_call_argument_definer")
18
+ autoload(:Schema, "graphql/schema")
12
19
  autoload(:Transform, "graphql/transform")
20
+ autoload(:VERSION, "graphql/version")
21
+
22
+ module Introspection
23
+ autoload(:CallNode, "graphql/introspection/call_node")
24
+ autoload(:Connection, "graphql/introspection/connection")
25
+ autoload(:FieldNode, "graphql/introspection/field_node")
26
+ autoload(:RootCallArgumentNode, "graphql/introspection/root_call_argument_node")
27
+ autoload(:RootCallNode, "graphql/introspection/root_call_node")
28
+ autoload(:SchemaCall, "graphql/introspection/schema_call")
29
+ autoload(:SchemaNode, "graphql/introspection/schema_node")
30
+ autoload(:TypeCall, "graphql/introspection/type_call")
31
+ autoload(:TypeNode, "graphql/introspection/type_node")
32
+ end
33
+
13
34
 
14
35
  module Syntax
15
36
  autoload(:Call, "graphql/syntax/call")
16
- autoload(:Edge, "graphql/syntax/edge")
17
37
  autoload(:Field, "graphql/syntax/field")
38
+ autoload(:Query, "graphql/syntax/query")
18
39
  autoload(:Node, "graphql/syntax/node")
40
+ autoload(:Variable, "graphql/syntax/variable")
41
+ end
42
+
43
+ module Types
44
+ autoload(:BooleanField, "graphql/types/boolean_field")
45
+ autoload(:ConnectionField, "graphql/types/connection_field")
46
+ autoload(:CursorField, "graphql/types/cursor_field")
47
+ autoload(:NumberField, "graphql/types/number_field")
48
+ autoload(:ObjectField, "graphql/types/object_field")
49
+ autoload(:StringField, "graphql/types/string_field")
50
+ autoload(:TypeField, "graphql/types/type_field")
51
+ end
52
+
53
+ class Error < RuntimeError; end
54
+ class FieldNotDefinedError < Error
55
+ def initialize(class_name, field_name)
56
+ super("#{class_name}##{field_name} was requested, but it isn't defined. Defined fields are: #{SCHEMA.field_names}")
57
+ end
58
+ end
59
+ class NodeNotDefinedError < Error
60
+ def initialize(node_name)
61
+ super("#{node_name} was requested but was not found. Defined nodes are: #{SCHEMA.type_names}")
62
+ end
63
+ end
64
+ class ConnectionNotDefinedError < Error
65
+ def initialize(node_name)
66
+ super("#{node_name} was requested but was not found. Defined connections are: #{SCHEMA.connection_names}")
67
+ end
68
+ end
69
+ class RootCallNotDefinedError < Error
70
+ def initialize(name)
71
+ super("Call '#{name}' was requested but was not found. Defined calls are: #{SCHEMA.call_names}")
72
+ end
73
+ end
74
+ class SyntaxError < Error
75
+ def initialize(line, col, string)
76
+ lines = string.split("\n")
77
+ super("Syntax Error at (#{line}, #{col}), check usage: #{string}")
78
+ end
79
+ end
80
+
81
+ class RootCallArgumentError < Error
82
+ def initialize(declaration, actual)
83
+ super("Wrong type for #{declaration.name}: expected a #{declaration.type} but got #{actual}")
84
+ end
19
85
  end
20
86
 
21
87
  PARSER = Parser.new
88
+ SCHEMA = Schema.instance
22
89
  TRANSFORM = Transform.new
23
-
24
- class FieldNotDefinedError < RuntimeError
90
+ # preload these so they're in SCHEMA
91
+ ["types", "introspection"].each do |preload_dir|
92
+ Dir["#{File.dirname(__FILE__)}/graphql/#{preload_dir}/*.rb"].each { |f| require f }
25
93
  end
94
+ Node.field.__type__(:__type__)
26
95
  end
@@ -0,0 +1,7 @@
1
+ class GraphQL::Call
2
+ attr_reader :name, :lambda
3
+ def initialize(name:, lambda:)
4
+ @name = name
5
+ @lambda = lambda
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ class GraphQL::Connection < GraphQL::Node
2
+ exposes "Array"
3
+ field.any(:edges)
4
+
5
+ attr_reader :calls, :syntax_fields, :query
6
+
7
+ def initialize(items, query:, fields: [])
8
+ @target = items
9
+ @syntax_fields = fields
10
+ @query = query
11
+ end
12
+
13
+ def items
14
+ @target
15
+ end
16
+
17
+ def edge_fields
18
+ @edge_fields ||= syntax_fields.find { |f| f.identifier == "edges" }.fields
19
+ end
20
+
21
+ def edges
22
+ raise "#{self.class} expected a connection, but got `nil`" if items.nil?
23
+ items.map do |item|
24
+ node_class = GraphQL::SCHEMA.type_for_object(item)
25
+ node = node_class.new(item, fields: edge_fields, query: query)
26
+ res = node.as_result
27
+ res
28
+ end
29
+ end
30
+
31
+ class << self
32
+ def default_schema_name
33
+ name.split("::").last.sub(/Connection$/, '').underscore
34
+ end
35
+
36
+ attr_accessor :default_connection
37
+ def default_connection!
38
+ GraphQL::Connection.default_connection = self
39
+ end
40
+
41
+ end
42
+
43
+ self.default_connection!
44
+ end
@@ -0,0 +1,117 @@
1
+ class GraphQL::Field
2
+ attr_reader :query, :owner, :calls, :fields
3
+ def initialize(query: nil, owner: nil, calls: [], fields: [])
4
+ @query = query
5
+ @owner = owner
6
+ @calls = calls
7
+ @fields = fields
8
+ end
9
+
10
+ def raw_value
11
+ owner.send(method)
12
+ end
13
+
14
+ def as_result
15
+ finished_value
16
+ end
17
+
18
+ def finished_value
19
+ @finished_value ||= begin
20
+ val = raw_value
21
+ calls.each do |call|
22
+ registered_call = self.class.calls[call.identifier]
23
+ if registered_call.nil?
24
+ raise "Call not found: #{self.class.name}##{call.identifier}"
25
+ end
26
+ val = registered_call.lambda.call(val, *call.arguments)
27
+ end
28
+ val
29
+ end
30
+ end
31
+
32
+ # instance `const_get` reaches up to class namespace
33
+ def const_get(const_name)
34
+ self.class.const_get(const_name)
35
+ rescue
36
+ nil
37
+ end
38
+
39
+ # delegate to class constant
40
+ ["name", "description"].each do |method_name|
41
+ define_method(method_name) do
42
+ const_get(method_name.upcase)
43
+ end
44
+ end
45
+
46
+ def method; name; end
47
+
48
+ class << self
49
+ def inherited(child_class)
50
+ GraphQL::SCHEMA.add_field(child_class)
51
+ end
52
+
53
+ def create_class(name:, owner_class:, type:, description: nil, connection_class_name: nil, node_class_name: nil)
54
+ if type.is_a?(Symbol)
55
+ type = GraphQL::SCHEMA.get_field(type)
56
+ end
57
+
58
+ field_superclass = type || self
59
+ new_class = Class.new(field_superclass)
60
+ new_class.const_set :NAME, name
61
+ new_class.const_set :OWNER_CLASS, owner_class
62
+ new_class.const_set :DESCRIPTION , description
63
+ new_class.const_set :CONNECTION_CLASS_NAME, connection_class_name
64
+ new_class.const_set :NODE_CLASS_NAME, node_class_name
65
+ new_class
66
+ end
67
+
68
+ def to_s
69
+ if const_defined?(:NAME)
70
+ "<FieldClass: #{const_get(:OWNER_CLASS).name}::#{const_get(:NAME)}>"
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ def type(value_type_name)
77
+ @value_type = value_type_name.to_s
78
+ GraphQL::SCHEMA.add_field(self)
79
+ end
80
+
81
+ def value_type
82
+ @value_type || superclass.value_type
83
+ end
84
+
85
+ def schema_name
86
+ @value_type || (name.present? ? default_schema_name : nil)
87
+ end
88
+
89
+ def default_schema_name
90
+ name.split("::").last.sub(/Field$/, '').underscore
91
+ end
92
+
93
+ def field_name
94
+ const_get(:NAME)
95
+ end
96
+
97
+ def description
98
+ const_get(:DESCRIPTION)
99
+ end
100
+
101
+ def calls
102
+ superclass.calls.merge(_calls)
103
+ rescue NoMethodError
104
+ {}
105
+ end
106
+
107
+ def _calls
108
+ @calls ||= {}
109
+ end
110
+
111
+ def call(name, lambda)
112
+ _calls[name.to_s] = GraphQL::Call.new(name: name.to_s, lambda: lambda)
113
+ end
114
+ end
115
+
116
+ type :any
117
+ end
@@ -0,0 +1,29 @@
1
+ class GraphQL::FieldDefiner
2
+ attr_reader :owner_class
3
+ def initialize(owner_class)
4
+ @owner_class = owner_class
5
+ end
6
+
7
+ def method_missing(method_name, *args, &block)
8
+ type = GraphQL::SCHEMA.get_field(method_name)
9
+ if type.present?
10
+ create_field(args[0], type: type, description: args[1])
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def create_field(field_name, type: nil, description: nil)
17
+ field_name = field_name.to_s
18
+ field_class = GraphQL::Field.create_class({
19
+ name: field_name,
20
+ type: type,
21
+ owner_class: owner_class,
22
+ description: description,
23
+ })
24
+
25
+ field_class_name = field_name.camelize + "Field"
26
+ owner_class.const_set(field_class_name, field_class)
27
+ owner_class.own_fields[field_name] = field_class
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ class GraphQL::Introspection::CallNode < GraphQL::Node
2
+ exposes "GraphQL::Call"
3
+ field.string(:name)
4
+ field.string(:arguments)
5
+
6
+ def arguments
7
+ args = target.lambda.parameters
8
+ args.shift
9
+ args.map { |p| "#{p[1]} (#{p[0]})" }.join(", ")
10
+ rescue StandardError => e
11
+ raise "Errored on #{name} (#{self}): #{e}"
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ class GraphQL::Introspection::Connection < GraphQL::Connection
2
+ type :introspection_connection
3
+
4
+ field.number(:count)
5
+
6
+ def count
7
+ size
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class GraphQL::Introspection::FieldNode < GraphQL::Node
2
+ exposes "GraphQL::Field"
3
+ field.string(:name)
4
+ field.string(:type)
5
+ field.string(:description)
6
+ field.connection(:calls)
7
+
8
+ def calls
9
+ target.calls.values
10
+ end
11
+
12
+ def name
13
+ schema_name
14
+ end
15
+
16
+ def type
17
+ value_type
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ class GraphQL::Introspection::RootCallArgumentNode < GraphQL::Node
2
+ exposes "GraphQL::RootCallArgument"
3
+ field.string(:name)
4
+ field.string(:type)
5
+ end
@@ -0,0 +1,16 @@
1
+ class GraphQL::Introspection::RootCallNode < GraphQL::Node
2
+ exposes "GraphQL::RootCall"
3
+ field.string(:name)
4
+ field.string(:returns)
5
+ field.connection(:arguments)
6
+
7
+
8
+ def returns
9
+ return_declarations = @target.return_declarations
10
+ return_declarations.keys.map(&:to_s)
11
+ end
12
+
13
+ def name
14
+ schema_name
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ class GraphQL::Introspection::SchemaCall < GraphQL::RootCall
2
+ returns :schema
3
+ argument.none
4
+
5
+ def execute!
6
+ GraphQL::SCHEMA
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ class GraphQL::Introspection::SchemaNode < GraphQL::Node
2
+ exposes "GraphQL::Schema"
3
+ field.connection(:calls)
4
+ field.connection(:types)
5
+
6
+ def cursor
7
+ "schema"
8
+ end
9
+
10
+ def types
11
+ @target.types.values
12
+ end
13
+
14
+ def calls
15
+ target.calls.values
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ class GraphQL::Introspection::TypeCall < GraphQL::RootCall
2
+ returns :type
3
+ argument.string("type_name")
4
+
5
+ def execute!(type_name)
6
+ GraphQL::SCHEMA.get_type(type_name)
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ class GraphQL::Introspection::TypeNode < GraphQL::Node
2
+ exposes "GraphQL::Node"
3
+ field.string(:name)
4
+ field.string(:description)
5
+ field.connection(:fields)
6
+
7
+ cursor :name
8
+
9
+ def fields
10
+ target.all_fields.values
11
+ end
12
+
13
+ def name
14
+ schema_name
15
+ end
16
+ end