graphql-rb 0.0.2

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +26 -0
  3. data/lib/graphql/configuration/configurable.rb +46 -0
  4. data/lib/graphql/configuration/configuration.rb +92 -0
  5. data/lib/graphql/configuration/slot.rb +124 -0
  6. data/lib/graphql/configuration.rb +3 -0
  7. data/lib/graphql/errors/error.rb +3 -0
  8. data/lib/graphql/errors.rb +1 -0
  9. data/lib/graphql/executor.rb +83 -0
  10. data/lib/graphql/introspection/meta_fields.rb +54 -0
  11. data/lib/graphql/introspection/query.rb +81 -0
  12. data/lib/graphql/introspection/schema.rb +158 -0
  13. data/lib/graphql/introspection.rb +3 -0
  14. data/lib/graphql/language/argument.rb +11 -0
  15. data/lib/graphql/language/directive.rb +5 -0
  16. data/lib/graphql/language/document.rb +23 -0
  17. data/lib/graphql/language/field.rb +55 -0
  18. data/lib/graphql/language/fragment_definition.rb +23 -0
  19. data/lib/graphql/language/fragment_spread.rb +5 -0
  20. data/lib/graphql/language/inline_fragment.rb +23 -0
  21. data/lib/graphql/language/list_type.rb +15 -0
  22. data/lib/graphql/language/name.rb +5 -0
  23. data/lib/graphql/language/named_type.rb +15 -0
  24. data/lib/graphql/language/non_null_type.rb +15 -0
  25. data/lib/graphql/language/operation_definition.rb +33 -0
  26. data/lib/graphql/language/parser.rb +331 -0
  27. data/lib/graphql/language/selection_set.rb +107 -0
  28. data/lib/graphql/language/transform.rb +101 -0
  29. data/lib/graphql/language/value.rb +24 -0
  30. data/lib/graphql/language/variable.rb +11 -0
  31. data/lib/graphql/language/variable_definition.rb +34 -0
  32. data/lib/graphql/language.rb +40 -0
  33. data/lib/graphql/type/argument.rb +16 -0
  34. data/lib/graphql/type/directive.rb +37 -0
  35. data/lib/graphql/type/directives.rb +25 -0
  36. data/lib/graphql/type/enum_type.rb +100 -0
  37. data/lib/graphql/type/field.rb +50 -0
  38. data/lib/graphql/type/input_object_type.rb +47 -0
  39. data/lib/graphql/type/interface_type.rb +64 -0
  40. data/lib/graphql/type/list.rb +23 -0
  41. data/lib/graphql/type/non_null.rb +25 -0
  42. data/lib/graphql/type/object_type.rb +57 -0
  43. data/lib/graphql/type/scalar_type.rb +137 -0
  44. data/lib/graphql/type/schema.rb +49 -0
  45. data/lib/graphql/type/union_type.rb +39 -0
  46. data/lib/graphql/type.rb +82 -0
  47. data/lib/graphql/validator.rb +43 -0
  48. data/lib/graphql/version.rb +3 -0
  49. data/lib/graphql.rb +21 -0
  50. data/spec/configuration/configuration_spec.rb +4 -0
  51. data/spec/data.rb +89 -0
  52. data/spec/introspection/full_spec.rb +12 -0
  53. data/spec/introspection/simple_spec.rb +153 -0
  54. data/spec/language/parser_spec.rb +73 -0
  55. data/spec/schema.rb +145 -0
  56. data/spec/spec_helper.rb +99 -0
  57. data/spec/type/enum_spec.rb +27 -0
  58. data/spec/type/input_object_spec.rb +21 -0
  59. data/spec/type/list_spec.rb +16 -0
  60. data/spec/type/non_null_spec.rb +22 -0
  61. data/spec/type/scalar_type_spec.rb +117 -0
  62. data/spec/type/schema_spec.rb +13 -0
  63. metadata +202 -0
@@ -0,0 +1,137 @@
1
+ require 'graphql/configuration'
2
+
3
+ module GraphQL
4
+
5
+
6
+ # GraphQL Scalar Type Configuration
7
+ #
8
+ class GraphQLScalarTypeConfiguration < GraphQL::Configuration::Base
9
+ slot :name, String
10
+ slot :description, String, null: true
11
+ slot :serialize, Proc
12
+ slot :parse_value, Proc
13
+ slot :parse_literal, Proc
14
+ end
15
+
16
+ # GraphQL Scalar Type
17
+ #
18
+ class GraphQLScalarType < GraphQL::Configuration::Configurable
19
+
20
+ include GraphQLType
21
+ include GraphQLInputType
22
+ include GraphQLOutputType
23
+ include GraphQLLeafType
24
+ include GraphQLNullableType
25
+ include GraphQLNamedType
26
+
27
+ configure_with GraphQLScalarTypeConfiguration
28
+
29
+ def initialize(configuration)
30
+ super
31
+ raise RuntimeError.new("Name should present in #{self.class}") if name.nil? || name.size == 0
32
+ end
33
+
34
+ def serialize(value)
35
+ @configuration.serialize.call(value)
36
+ end
37
+
38
+ def parse_value(value)
39
+ @configuration.parse_value.call(value)
40
+ end
41
+
42
+ def parse_literal(ast)
43
+ @configuration.parse_literal.call(ast)
44
+ end
45
+
46
+ def to_s
47
+ name
48
+ end
49
+
50
+ end
51
+
52
+ # Int
53
+ #
54
+ GraphQLInt = GraphQLScalarType.new do
55
+ name 'Int'
56
+
57
+ serialize lambda { |value|
58
+ value = value.to_s
59
+ return nil unless value =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
60
+ value.to_f.round
61
+ }
62
+
63
+ parse_value serialize #-> (value) { serialize(value) }
64
+
65
+ parse_literal lambda { |ast|
66
+ ast[:kind] == :int ? ast[:value].to_i : nil
67
+ }
68
+ end
69
+
70
+ # Float
71
+ #
72
+ GraphQLFloat = GraphQLScalarType.new do
73
+ name 'Float'
74
+
75
+ serialize lambda { |value|
76
+ value = value.to_s
77
+ return nil unless value =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
78
+ value.to_f
79
+ }
80
+
81
+ parse_value serialize
82
+
83
+ parse_literal lambda { |ast|
84
+ ast[:kind] == :int || ast[:kind] == :float ? ast[:value].to_f : nil
85
+ }
86
+ end
87
+
88
+ # String
89
+ #
90
+ GraphQLString = GraphQLScalarType.new do
91
+ name 'String'
92
+
93
+ serialize lambda { |value|
94
+ value.to_s
95
+ }
96
+
97
+ parse_value serialize
98
+
99
+ parse_literal lambda { |ast|
100
+ ast[:kind] == :string ? ast[:value] : nil
101
+ }
102
+ end
103
+
104
+ # Boolean
105
+ #
106
+ GraphQLBoolean = GraphQLScalarType.new do
107
+ name 'Boolean'
108
+
109
+ serialize lambda { |value|
110
+ return false if value == 0
111
+ !!value
112
+ }
113
+
114
+ parse_value serialize
115
+
116
+ parse_literal lambda { |ast|
117
+ ast[:kind] == :boolean ? ast[:value] : nil
118
+ }
119
+ end
120
+
121
+ # ID
122
+ #
123
+ GraphQLID = GraphQLScalarType.new do
124
+ name 'ID'
125
+
126
+ serialize lambda { |value|
127
+ value.to_s
128
+ }
129
+
130
+ parse_value serialize
131
+
132
+ parse_literal lambda { |ast|
133
+ ast[:kind] == :string || ast[:kind] == :int ? ast[:value] : nil
134
+ }
135
+ end
136
+
137
+ end
@@ -0,0 +1,49 @@
1
+ module GraphQL
2
+
3
+ class GraphQLSchemaConfiguration < GraphQL::Configuration::Base
4
+
5
+ slot :name, String
6
+ slot :query, GraphQLObjectType
7
+ slot :mutation, GraphQLObjectType, null: true
8
+
9
+ end
10
+
11
+ class GraphQLSchema < GraphQL::Configuration::Configurable
12
+
13
+ configure_with GraphQLSchemaConfiguration
14
+
15
+ def query_type
16
+ @configuration.query
17
+ end
18
+
19
+ def mutation_type
20
+ @configuration.mutation
21
+ end
22
+
23
+ def type_map
24
+ @type_map ||= [query_type, mutation_type, Introspection::Schema__].reduce({}, &TypeMapReducer)
25
+ end
26
+
27
+ def type_names
28
+ @type_names ||= type_map.keys
29
+ end
30
+
31
+ def types
32
+ @types ||= type_map.values
33
+ end
34
+
35
+ def type(name)
36
+ type_map[name]
37
+ end
38
+
39
+ def directives
40
+ @directives ||= [GraphQLSkipDirective, GraphQLIncludeDirective]
41
+ end
42
+
43
+ def to_s
44
+ name
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,39 @@
1
+ module GraphQL
2
+
3
+ class GraphQLUnionTypeConfiguration < GraphQL::Configuration::Base
4
+ slot :name, String
5
+ slot :types, [-> { GraphQLObjectType }], singular: :type
6
+ slot :resolve_type, Proc, null: true
7
+ slot :description, String, null: true
8
+ end
9
+
10
+ class GraphQLUnionType < GraphQL::Configuration::Configurable
11
+
12
+ include GraphQLType
13
+ include GraphQLOutputType
14
+ include GraphQLCompositeType
15
+ include GraphQLAbstractType
16
+ include GraphQLNullableType
17
+ include GraphQLNamedType
18
+
19
+ configure_with GraphQLUnionTypeConfiguration
20
+
21
+ def possible_types
22
+ @configuration.types
23
+ end
24
+
25
+ def possible_type?(type)
26
+ @configuration.types.include?(type)
27
+ end
28
+
29
+ def resolve_type(type)
30
+ @configuration.resolve_type.nil? ? type_of(type) : @configuration.resolve_type.call(type)
31
+ end
32
+
33
+ def to_s
34
+ name
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,82 @@
1
+ module GraphQL
2
+
3
+ module GraphQLType
4
+ def +@
5
+ GraphQLList.new(self)
6
+ end
7
+ end
8
+
9
+ module GraphQLInputType
10
+ end
11
+
12
+ module GraphQLOutputType
13
+ end
14
+
15
+ module GraphQLLeafType
16
+ end
17
+
18
+ module GraphQLCompositeType
19
+ end
20
+
21
+ module GraphQLNamedType
22
+ end
23
+
24
+ module GraphQLAbstractType
25
+
26
+ def type_of(value)
27
+ raise "Not implemented. Yet."
28
+ end
29
+
30
+ end
31
+
32
+ module GraphQLNullableType
33
+
34
+ def !
35
+ GraphQLNonNull.new(self)
36
+ end
37
+
38
+ end
39
+
40
+ def self.named_type(type)
41
+ type = type.of_type while type.is_a?(GraphQLList) || type.is_a?(GraphQLNonNull)
42
+ end
43
+
44
+ TypeMapReducer = lambda do |memo, type|
45
+ return TypeMapReducer.call(memo, type.of_type) if type.is_a?(GraphQLNonNull) || type.is_a?(GraphQLList)
46
+
47
+ return memo if type.nil? || memo.keys.include?(type.name)
48
+
49
+ memo[type.name] = type
50
+
51
+ if type.is_a?(GraphQLUnionType) || type.is_a?(GraphQLInterfaceType)
52
+ memo = type.possible_types.reduce(memo, &TypeMapReducer)
53
+ end
54
+
55
+ if type.is_a?(GraphQLObjectType)
56
+ memo = type.interfaces.reduce(memo, &TypeMapReducer)
57
+ end
58
+
59
+ if type.is_a?(GraphQLObjectType) || type.is_a?(GraphQLInterfaceType)
60
+ type.field_map.each do |name, field|
61
+ memo = field.args.map(&:type).reduce(memo, &TypeMapReducer)
62
+ memo = TypeMapReducer.call(memo, field.type)
63
+ end
64
+ end
65
+
66
+ memo
67
+ end
68
+
69
+ end
70
+
71
+ require_relative 'type/scalar_type'
72
+ require_relative 'type/object_type'
73
+ require_relative 'type/interface_type'
74
+ require_relative 'type/union_type'
75
+ require_relative 'type/enum_type'
76
+ require_relative 'type/input_object_type'
77
+ require_relative 'type/field'
78
+ require_relative 'type/argument'
79
+ require_relative 'type/list'
80
+ require_relative 'type/non_null'
81
+ require_relative 'type/schema'
82
+ require_relative 'type/directive'
@@ -0,0 +1,43 @@
1
+ module GraphQL
2
+ class Validator
3
+
4
+ def self.coerce_value(value, type)
5
+ case type
6
+ when GraphQLNonNull
7
+ coerce_value(value, type.of_type)
8
+ when value.nil?
9
+ nil
10
+ when GraphQLList
11
+ values = value.is_a?(Array) ? value : [value]
12
+ values.map { |value| coerce_value(value, type.of_type) }
13
+ when GraphQLInputObjectType
14
+ raise "Not. Implemented. Yet."
15
+ when GraphQLScalarType, GraphQLEnumType
16
+ type.parse_value(value)
17
+ else
18
+ raise "Must be input type"
19
+ end
20
+ end
21
+
22
+ def self.valid_value?(value, type)
23
+ case type
24
+ when GraphQLNonNull
25
+ return false if value.nil?
26
+ valid_value?(value, type.of_type)
27
+ when value.nil?
28
+ true
29
+ when GraphQLList
30
+ values = value.is_a?(Array) ? value : [value]
31
+ values.all? { |value| valid_value?(value, type.of_type) }
32
+ when GraphQLInputObjectType
33
+ raise "Not. Implemented. Yet."
34
+ when GraphQLScalarType, GraphQLEnumType
35
+ !type.parse_value(value).nil?
36
+ else
37
+ raise "Must be input type"
38
+ end
39
+ end
40
+
41
+
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module GraphQL
2
+ VERSION = '0.0.2'
3
+ end
data/lib/graphql.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'graphql/errors'
2
+ require 'graphql/configuration'
3
+ require 'graphql/type'
4
+ require 'graphql/introspection'
5
+ require 'graphql/language'
6
+ require 'graphql/version'
7
+ require 'graphql/executor'
8
+ require 'graphql/validator'
9
+
10
+ module GraphQL
11
+
12
+ def self.graphql(schema, query, root, params, operation = nil)
13
+ document = GraphQL::Language.parse(query)
14
+ executor = GraphQL::Executor.new(document, schema)
15
+ result = executor.execute(root, params, operation)
16
+ { data: result }
17
+ rescue StandardError => e
18
+ { errors: [e] }
19
+ end
20
+
21
+ end
@@ -0,0 +1,4 @@
1
+ require 'graphql'
2
+
3
+ RSpec.describe GraphQL::Configuration do
4
+ end
data/spec/data.rb ADDED
@@ -0,0 +1,89 @@
1
+ require 'celluloid/current'
2
+ require 'logger'
3
+
4
+ module StarWars
5
+ class Data
6
+ include Celluloid
7
+
8
+ Human = Struct.new('Human', :id, :name, :friends, :appears_in, :home_planet)
9
+ Droid = Struct.new('Droid', :id, :name, :friends, :appears_in, :primary_function)
10
+
11
+ Luke = Human.new('1000', 'Like Skywalker', ['1002', '1003', '2000', '2001'], [4, 5, 6], 'Tatooine')
12
+ Vader = Human.new('1001', 'Darth Vader', ['1004'], [4, 5, 6], 'Tatooine')
13
+ Han = Human.new('1002', 'Han Solo', ['1000', '1003', '2001'], [4, 5, 6])
14
+ Leia = Human.new('1003', 'Lea Organa', ['1000', '1002', '2000', '2001'], [4, 5, 6], 'Alderaan')
15
+ Tarkin = Human.new('1004', 'Wilhuff Tarkin', ['1001'], [4])
16
+
17
+ ThreePO = Droid.new('2000', 'C-3PO', ['1000', '1002', '1003', '2001'], [4, 5, 6], 'Protocol')
18
+ Artoo = Droid.new('2001', 'R2-D2', ['1000', '1002', '1003'], [4, 5, 6], 'Astromech')
19
+
20
+ Characters = [Luke, Vader, Han, Leia, Tarkin, ThreePO, Artoo]
21
+
22
+ def self.logger
23
+ @logger ||= Logger.new(STDOUT)
24
+ end
25
+
26
+ def self.get(id)
27
+ Fetcher.fetch(id)
28
+ end
29
+
30
+ def self.select(ids)
31
+ Fetcher.fetch(ids)
32
+ end
33
+
34
+
35
+ class Fetcher
36
+ include Celluloid
37
+
38
+ def self.logger
39
+ @logger ||= Logger.new(STDOUT)
40
+ end
41
+
42
+ def self.fetch(id_or_ids)
43
+ logger.info "Fetching `#{id_or_ids}`"
44
+ pool.future.fetch(id_or_ids)
45
+ end
46
+
47
+ def self.pool
48
+ @pool ||= new
49
+ end
50
+
51
+ def initialize
52
+ @timer = after(0.01) { perform_fetch }
53
+ @data = {}
54
+ end
55
+
56
+ def restart
57
+ @timer.reset
58
+ end
59
+
60
+ def fetch(id_or_ids)
61
+ condition = Celluloid::Condition.new
62
+ (@data[id_or_ids] ||= []) << lambda { |value| condition.signal(value) }
63
+ restart
64
+ condition.wait
65
+ end
66
+
67
+ def perform_fetch
68
+ self.class.logger.info "Performing fetch"
69
+ data = @data
70
+ @data = {}
71
+ ids = data.keys.flatten.uniq
72
+ self.class.logger.info "select * from table where id in #{ids}"
73
+ # sleep 1
74
+ characters = Data::Characters.select { |c| ids.include?(c.id) }
75
+ data.each do |key, blocks|
76
+ result = if key.is_a?(Array)
77
+ result = characters.select { |c| key.include?(c.id) }
78
+ else
79
+ result = characters.find { |c| key == (c.id) }
80
+ end
81
+ blocks.each { |block| block.call(result) }
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+
88
+ end
89
+ end
@@ -0,0 +1,12 @@
1
+ require 'graphql'
2
+ require_relative '../schema'
3
+
4
+ RSpec.describe "Introspection - Full" do
5
+
6
+ it "Should allow querying the schema" do
7
+ expect {
8
+ GraphQL::graphql(StarWars::Schema, GraphQL::Introspection::Query, {}, {})
9
+ }.not_to raise_error
10
+ end
11
+
12
+ end
@@ -0,0 +1,153 @@
1
+ require 'graphql'
2
+ require_relative '../schema'
3
+
4
+ RSpec.describe "Introspection - Simple" do
5
+
6
+ def q1
7
+ %Q(
8
+ query Introspection {
9
+ __schema {
10
+ types {
11
+ name
12
+ }
13
+ }
14
+ }
15
+ )
16
+ end
17
+
18
+
19
+ def q2
20
+ %Q(
21
+ query Introspection {
22
+ __schema {
23
+ queryType {
24
+ name
25
+ }
26
+ }
27
+ }
28
+ )
29
+ end
30
+
31
+
32
+ def q3
33
+ %Q(
34
+ query Introspection {
35
+ __type(name: "Droid") {
36
+ name
37
+ }
38
+ }
39
+ )
40
+ end
41
+
42
+
43
+ def q4
44
+ %Q(
45
+ query Introspection {
46
+ __type(name: "Droid") {
47
+ name
48
+ kind
49
+ }
50
+ }
51
+ )
52
+ end
53
+
54
+
55
+ def q5
56
+ %Q(
57
+ query Introspection {
58
+ __type(name: "Character") {
59
+ name
60
+ kind
61
+ }
62
+ }
63
+ )
64
+ end
65
+
66
+
67
+ def q6
68
+ %Q(
69
+ query Introspection {
70
+ __type(name: "Droid") {
71
+ name
72
+ fields {
73
+ name
74
+ type {
75
+ name
76
+ kind
77
+ }
78
+ }
79
+ }
80
+ }
81
+ )
82
+ end
83
+
84
+
85
+ it "Should allow querying the schema for types" do
86
+ expectation = {
87
+ data: {
88
+ __schema: {
89
+ types: [
90
+ { name: "Query" },
91
+ { name: "Episode" },
92
+ { name: "Character" },
93
+ { name: "Human" },
94
+ { name: "String" },
95
+ { name: "Droid" },
96
+ { name: "__Schema" },
97
+ { name: "__Type" },
98
+ { name: "__TypeKind" },
99
+ { name: "Boolean" },
100
+ { name: "__Field" },
101
+ { name: "__InputValue" },
102
+ { name: "__EnumValue" }
103
+ ]
104
+ }
105
+ }
106
+ }
107
+
108
+ result = GraphQL::graphql(StarWars::Schema, q1, {}, {})
109
+
110
+ expect(result).to eql(expectation)
111
+ end
112
+
113
+ it "Should allow querying the schema for query type" do
114
+ expectation = {
115
+ data: { __schema: { queryType: { name: "Query" } } }
116
+ }
117
+
118
+ result = GraphQL::graphql(StarWars::Schema, q2, {}, {})
119
+
120
+ expect(result).to eql(expectation)
121
+ end
122
+
123
+ it "Should allow querying the schema for a specific type" do
124
+ expectation = {
125
+ data: { __type: { name: 'Droid' } }
126
+ }
127
+
128
+ result = GraphQL::graphql(StarWars::Schema, q3, {}, {})
129
+
130
+ expect(result).to eql(expectation)
131
+ end
132
+
133
+ it "Should allow querying the schema for an object kind" do
134
+ expectation = {
135
+ data: { __type: { name: 'Droid', kind: 'OBJECT' } }
136
+ }
137
+
138
+ result = GraphQL::graphql(StarWars::Schema, q4, {}, {})
139
+
140
+ expect(result).to eql(expectation)
141
+ end
142
+
143
+ it "Should allow querying the schema for an interface kind" do
144
+ expectation = {
145
+ data: { __type: { name: 'Character', kind: 'INTERFACE' } }
146
+ }
147
+
148
+ result = GraphQL::graphql(StarWars::Schema, q5, {}, {})
149
+
150
+ expect(result).to eql(expectation)
151
+ end
152
+
153
+ end
@@ -0,0 +1,73 @@
1
+ require 'awesome_print'
2
+ require 'graphql'
3
+ require_relative '../schema'
4
+
5
+ RSpec.describe GraphQL::Language do
6
+
7
+
8
+ def hero_query
9
+ %Q(
10
+ query getHero($episode: Episode!, $heroes: [String] = ["1001", "1002"]) {
11
+ hero(episode: JEDI) {
12
+ id
13
+ name
14
+ friends {
15
+ name
16
+
17
+ ... humanFields
18
+
19
+ ... on Droid {
20
+ name
21
+ primary_function
22
+ }
23
+ }
24
+
25
+ ... humanFields
26
+
27
+ ... on Droid {
28
+ name
29
+ primary_function
30
+ }
31
+ }
32
+ }
33
+
34
+ fragment humanFields on Human {
35
+ name
36
+ home_planet
37
+ }
38
+ )
39
+ end
40
+
41
+
42
+ it "Should parse query" do
43
+
44
+ expectation = {
45
+ data: {
46
+ hero: {
47
+ id: "1000",
48
+ name: "Like Skywalker",
49
+ friends: [
50
+ {
51
+ name: "Han Solo",
52
+ home_planet: nil
53
+ }, {
54
+ name: "Lea Organa",
55
+ home_planet: "Alderaan"
56
+ }, {
57
+ name: "C-3PO",
58
+ primary_function: "Protocol"
59
+ }, {
60
+ name: "R2-D2",
61
+ primary_function: "Astromech"
62
+ }
63
+ ],
64
+ home_planet: "Tatooine"
65
+ }
66
+ }
67
+ }
68
+
69
+ result = GraphQL::graphql(StarWars::Schema, hero_query, nil, { episode: 'JEDI', heroes: ['1001'] })
70
+ expect(result).to eql(expectation)
71
+ end
72
+
73
+ end