graphql-rb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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