gqli 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 (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +31 -0
  5. data/.rubocop_todo.yml +44 -0
  6. data/.travis.yml +13 -0
  7. data/.yardopts +4 -0
  8. data/CHANGELOG.md +9 -0
  9. data/Gemfile +9 -0
  10. data/Gemfile.lock +154 -0
  11. data/Guardfile +24 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +284 -0
  14. data/Rakefile +33 -0
  15. data/coverage/.last_run.json +5 -0
  16. data/coverage/.resultset.json +512 -0
  17. data/coverage/.resultset.json.lock +0 -0
  18. data/coverage/assets/0.10.2/application.css +799 -0
  19. data/coverage/assets/0.10.2/application.js +1707 -0
  20. data/coverage/assets/0.10.2/colorbox/border.png +0 -0
  21. data/coverage/assets/0.10.2/colorbox/controls.png +0 -0
  22. data/coverage/assets/0.10.2/colorbox/loading.gif +0 -0
  23. data/coverage/assets/0.10.2/colorbox/loading_background.png +0 -0
  24. data/coverage/assets/0.10.2/favicon_green.png +0 -0
  25. data/coverage/assets/0.10.2/favicon_red.png +0 -0
  26. data/coverage/assets/0.10.2/favicon_yellow.png +0 -0
  27. data/coverage/assets/0.10.2/loading.gif +0 -0
  28. data/coverage/assets/0.10.2/magnify.png +0 -0
  29. data/coverage/assets/0.10.2/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  30. data/coverage/assets/0.10.2/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  31. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  32. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  33. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  34. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  35. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  36. data/coverage/assets/0.10.2/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  37. data/coverage/assets/0.10.2/smoothness/images/ui-icons_222222_256x240.png +0 -0
  38. data/coverage/assets/0.10.2/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  39. data/coverage/assets/0.10.2/smoothness/images/ui-icons_454545_256x240.png +0 -0
  40. data/coverage/assets/0.10.2/smoothness/images/ui-icons_888888_256x240.png +0 -0
  41. data/coverage/assets/0.10.2/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  42. data/coverage/index.html +3255 -0
  43. data/doc/GQLi/Base.html +628 -0
  44. data/doc/GQLi/Client.html +830 -0
  45. data/doc/GQLi/DSL.html +421 -0
  46. data/doc/GQLi/Fragment.html +405 -0
  47. data/doc/GQLi/Introspection.html +837 -0
  48. data/doc/GQLi/Node.html +413 -0
  49. data/doc/GQLi/Query.html +390 -0
  50. data/doc/GQLi/Response.html +370 -0
  51. data/doc/GQLi.html +157 -0
  52. data/doc/_index.html +236 -0
  53. data/doc/class_list.html +51 -0
  54. data/doc/css/common.css +1 -0
  55. data/doc/css/full_list.css +58 -0
  56. data/doc/css/style.css +496 -0
  57. data/doc/file.CHANGELOG.html +76 -0
  58. data/doc/file.LICENSE.html +70 -0
  59. data/doc/file.README.html +365 -0
  60. data/doc/file_list.html +66 -0
  61. data/doc/frames.html +17 -0
  62. data/doc/index.html +365 -0
  63. data/doc/js/app.js +292 -0
  64. data/doc/js/full_list.js +216 -0
  65. data/doc/js/jquery.js +4 -0
  66. data/doc/method_list.html +355 -0
  67. data/doc/top-level-namespace.html +110 -0
  68. data/gqli.gemspec +37 -0
  69. data/lib/gqli/base.rb +53 -0
  70. data/lib/gqli/client.rb +59 -0
  71. data/lib/gqli/dsl.rb +37 -0
  72. data/lib/gqli/fragment.rb +25 -0
  73. data/lib/gqli/introspection.rb +215 -0
  74. data/lib/gqli/node.rb +48 -0
  75. data/lib/gqli/query.rb +29 -0
  76. data/lib/gqli/response.rb +15 -0
  77. data/lib/gqli/version.rb +7 -0
  78. data/lib/gqli.rb +6 -0
  79. data/spec/fixtures/vcr_cassettes/catCollection.yml +63 -0
  80. data/spec/fixtures/vcr_cassettes/client.yml +171 -0
  81. data/spec/fixtures/vcr_cassettes/validation_error.yml +62 -0
  82. data/spec/lib/gqli/client_spec.rb +90 -0
  83. data/spec/lib/gqli/dsl_spec.rb +205 -0
  84. data/spec/lib/gqli/introspection_spec.rb +125 -0
  85. data/spec/spec_helper.rb +25 -0
  86. data/usage.rb +100 -0
  87. data/usage_introspection.rb +63 -0
  88. data/usage_with_inlined_dsl.rb +69 -0
  89. metadata +387 -0
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.16
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="GQLi.html" title="GQLi (module)">GQLi</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Mon Oct 15 13:10:42 2018 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.16 (ruby-2.5.1).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
data/gqli.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ require File.expand_path('../lib/gqli/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'gqli'
5
+ gem.version = GQLi::VERSION
6
+ gem.summary = 'GraphQL client for humans'
7
+ gem.description = 'GraphQL client with simple interface, designed for developer happiness'
8
+ gem.license = 'MIT'
9
+ gem.authors = ['Contentful GmbH (David Litvak Bruno)']
10
+ gem.email = 'rubygems@contentful.com'
11
+ gem.homepage = 'https://github.com/contentful-labs/gqli.rb'
12
+
13
+ gem.files = Dir['{**/}{.*,*}'].select { |path| File.file?(path) && !path.start_with?('pkg') }
14
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^spec/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_dependency 'http', '> 0.8', '< 3.0'
19
+ gem.add_dependency 'hashie', '~> 3.0'
20
+ gem.add_dependency 'multi_json', '~> 1'
21
+
22
+ gem.add_development_dependency 'bundler', '~> 1.5'
23
+ gem.add_development_dependency 'rake', '< 11.0'
24
+ gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
25
+
26
+ gem.add_development_dependency 'guard'
27
+ gem.add_development_dependency 'guard-rspec'
28
+ gem.add_development_dependency 'guard-rubocop'
29
+ gem.add_development_dependency 'guard-yard'
30
+ gem.add_development_dependency 'rubocop', '~> 0.49.1'
31
+ gem.add_development_dependency 'rspec', '~> 3'
32
+ gem.add_development_dependency 'rr'
33
+ gem.add_development_dependency 'vcr'
34
+ gem.add_development_dependency 'simplecov'
35
+ gem.add_development_dependency 'webmock', '~> 1', '>= 1.17.3'
36
+ gem.add_development_dependency 'tins', '~> 1.6.0'
37
+ end
data/lib/gqli/base.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GQLi
4
+ # Base class for GraphQL type wrappers
5
+ class Base
6
+ attr_reader :__name, :__depth, :__nodes
7
+
8
+ def initialize(name = nil, depth = 0, &block)
9
+ @__name = name
10
+ @__depth = depth
11
+ @__nodes = []
12
+ instance_eval(&block) unless block.nil?
13
+ end
14
+
15
+ # Inlines fragment nodes into current node
16
+ def ___(fragment)
17
+ @__nodes += __clone_nodes(fragment)
18
+ end
19
+
20
+ # Adds type match node
21
+ def __on(type_name, &block)
22
+ require_relative './node'
23
+ @__nodes << Node.new("... on #{type_name}", [], __depth + 1, &block)
24
+ end
25
+
26
+ protected
27
+
28
+ def __clone_nodes(node_container)
29
+ require_relative './node'
30
+ __clone(node_container.__nodes).map do |n|
31
+ node = Node.new(n.__name, n.__params, __depth + 1)
32
+ node.instance_variable_set(
33
+ :@__nodes,
34
+ node.send(:__clone_nodes, n)
35
+ )
36
+ node
37
+ end
38
+ end
39
+
40
+ def __clone(obj)
41
+ Marshal.load(Marshal.dump(obj))
42
+ end
43
+
44
+ def __params_from_args(args)
45
+ args.empty? ? {} : args[0]
46
+ end
47
+
48
+ def method_missing(name, *args, &block)
49
+ require_relative './node'
50
+ @__nodes << Node.new(name.to_s, __params_from_args(args), __depth + 1, &block)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'http'
4
+ require 'json'
5
+ require_relative './response'
6
+ require_relative './introspection'
7
+ require_relative './version'
8
+
9
+ module GQLi
10
+ # GraphQL HTTP Client
11
+ class Client
12
+ attr_reader :url, :params, :headers, :validate_query, :schema
13
+
14
+ def initialize(url, params: {}, headers: {}, validate_query: true)
15
+ @url = url
16
+ @params = params
17
+ @headers = headers
18
+ @validate_query = validate_query
19
+
20
+ @schema = Introspection.new(self) if validate_query
21
+ end
22
+
23
+ # Executes a query
24
+ # If validations are enabled, will perform validation check before request.
25
+ def execute(query)
26
+ fail 'Validation Error: query is invalid - HTTP Request not sent' unless valid?(query)
27
+
28
+ execute!(query)
29
+ end
30
+
31
+ # Executres a query
32
+ # Ignores validations
33
+ def execute!(query)
34
+ http_response = HTTP.headers(request_headers).post(@url, params: @params, json: { query: query.to_gql })
35
+
36
+ fail "Error: #{http_response.reason}\nBody: #{http_response.body}" if http_response.status >= 300
37
+
38
+ data = JSON.parse(http_response.to_s)['data']
39
+
40
+ Response.new(data, query)
41
+ end
42
+
43
+ # Validates a query against the schema
44
+ def valid?(query)
45
+ return true unless validate_query
46
+
47
+ @schema.valid?(query)
48
+ end
49
+
50
+ protected
51
+
52
+ def request_headers
53
+ {
54
+ accept: 'application/json',
55
+ user_agent: "gqli.rb/#{VERSION}; http.rb/#{HTTP::VERSION}"
56
+ }.merge(@headers)
57
+ end
58
+ end
59
+ end
data/lib/gqli/dsl.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './query'
4
+ require_relative './fragment'
5
+
6
+ module GQLi
7
+ # GraphQL-like DSL methods
8
+ module DSL
9
+ # Creates a Query object
10
+ #
11
+ # Can be used at a class level
12
+ def self.query(name = nil, &block)
13
+ Query.new(name, &block)
14
+ end
15
+
16
+ # Creates a Fragment object
17
+ #
18
+ # Can be used at a class level
19
+ def self.fragment(name, on, &block)
20
+ Fragment.new(name, on, &block)
21
+ end
22
+
23
+ # Creates a Query object
24
+ #
25
+ # Can be used at an instance level
26
+ def query(name = nil, &block)
27
+ Query.new(name, &block)
28
+ end
29
+
30
+ # Creates a Fragment object
31
+ #
32
+ # Can be used at an instance level
33
+ def fragment(name, on, &block)
34
+ Fragment.new(name, on, &block)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './base'
4
+ require_relative './node'
5
+
6
+ module GQLi
7
+ # Fragment wrapper
8
+ class Fragment < Base
9
+ attr_reader :__on_type
10
+
11
+ def initialize(name, on, &block)
12
+ super(name, 0, &block)
13
+ @__on_type = on
14
+ end
15
+
16
+ # Serializes to a GraphQL string
17
+ def to_gql
18
+ <<~GQL
19
+ fragment #{__name} on #{__on_type} {
20
+ #{__nodes.map(&:to_gql).join("\n")}
21
+ }
22
+ GQL
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './dsl'
4
+
5
+ module GQLi
6
+ # Introspection schema and validator
7
+ class Introspection
8
+ extend DSL
9
+
10
+ # Specific type kind introspection fragment
11
+ TypeRef = fragment('TypeRef', '__Type') {
12
+ kind
13
+ name
14
+ ofType {
15
+ kind
16
+ name
17
+ ofType {
18
+ kind
19
+ name
20
+ ofType {
21
+ kind
22
+ name
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ # Input value introspection fragment
29
+ InputValue = fragment('InputValue', '__InputValue') {
30
+ name
31
+ description
32
+ type { ___ TypeRef }
33
+ defaultValue
34
+ }
35
+
36
+ # Type introspection fragment
37
+ FullType = fragment('FullType', '__Type') {
38
+ kind
39
+ name
40
+ description
41
+ fields(includeDeprecated: true) {
42
+ name
43
+ description
44
+ args { ___ InputValue }
45
+ type { ___ TypeRef }
46
+ isDeprecated
47
+ deprecationReason
48
+ }
49
+ inputFields { ___ InputValue }
50
+ interfaces { ___ TypeRef }
51
+ enumValues(includeDeprecated: true) {
52
+ name
53
+ description
54
+ isDeprecated
55
+ deprecationReason
56
+ }
57
+ possibleTypes { ___ TypeRef }
58
+ }
59
+
60
+ # Query for fetching the complete schema
61
+ IntrospectionQuery = query {
62
+ __schema {
63
+ queryType { name }
64
+ mutationType { name }
65
+ subscriptionType { name }
66
+ types { ___ FullType }
67
+ directives {
68
+ name
69
+ description
70
+ args { ___ InputValue }
71
+ onOperation
72
+ onFragment
73
+ onField
74
+ }
75
+ }
76
+ }
77
+
78
+ attr_reader :schema, :query_type, :mutation_type, :subscription_type, :types
79
+
80
+ def initialize(client)
81
+ @schema = client.execute!(IntrospectionQuery).data.__schema
82
+ @query_type = schema.queryType
83
+ @mutation_type = schema.mutationType
84
+ @subscription_type = schema.subscriptionType
85
+ @types = schema.types
86
+ end
87
+
88
+ # Returns wether the query is valid or not
89
+ def valid?(query)
90
+ return false unless query.is_a?(Query)
91
+
92
+ query_type = types.find { |t| t.name.casecmp('query').zero? }
93
+ query.__nodes.each do |node|
94
+ return false unless valid_node?(query_type, node)
95
+ end
96
+
97
+ true
98
+ end
99
+
100
+ private
101
+
102
+ def valid_node?(parent_type, node)
103
+ return true if parent_type.kind == 'SCALAR'
104
+
105
+ return valid_match_node?(parent_type, node) if node.__name.start_with?('... on')
106
+
107
+ node_type = parent_type.fetch('fields', []).find { |f| f.name == node.__name }
108
+ return false if node_type.nil?
109
+
110
+ return false unless valid_params?(node_type, node)
111
+
112
+ resolved_node_type = type_for(node_type)
113
+ return false if resolved_node_type.nil?
114
+
115
+ return false unless valid_nesting_node?(resolved_node_type, node)
116
+
117
+ node.__nodes.all? { |n| valid_node?(resolved_node_type, n) }
118
+ end
119
+
120
+ def valid_match_node?(parent_type, node)
121
+ return true if parent_type.fetch('possibleTypes', []).find { |t| t.name == node.__name.gsub('... on ', '') }
122
+ false
123
+ end
124
+
125
+ def valid_params?(node_type, node)
126
+ node.__params.each do |param, value|
127
+ arg = node_type.fetch('args', []).find { |a| a.name == param.to_s }
128
+ return false if arg.nil?
129
+
130
+ arg_type = type_for(arg)
131
+ return false if arg_type.nil?
132
+
133
+ return false unless valid_value_for_type?(arg_type, value)
134
+ end
135
+
136
+ true
137
+ end
138
+
139
+ def valid_nesting_node?(node_type, node)
140
+ return false unless valid_object_node?(node_type, node)
141
+ return false unless valid_array_node?(node_type, node)
142
+ true
143
+ end
144
+
145
+ def valid_object_node?(node_type, node)
146
+ return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
147
+ true
148
+ end
149
+
150
+ def valid_array_node?(node_type, node)
151
+ return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
152
+ true
153
+ end
154
+
155
+ def valid_value_for_type?(arg_type, value)
156
+ case value
157
+ when ::String
158
+ return false unless arg_type.name == 'String' || arg_type.name == 'ID'
159
+ when ::Integer
160
+ return false unless arg_type.name == 'Int'
161
+ when ::Float
162
+ return false unless arg_type.name == 'Float'
163
+ when ::Hash
164
+ return valid_hash_value?(arg_type, value)
165
+ when true, false
166
+ return false unless arg_type.name == 'Boolean'
167
+ else
168
+ return false
169
+ end
170
+
171
+ true
172
+ end
173
+
174
+ def valid_hash_value?(arg_type, value)
175
+ return false unless arg_type.kind == 'INPUT_OBJECT'
176
+
177
+ type = types.find { |f| f.name == arg_type.name }
178
+ return false if type.nil?
179
+
180
+ value.each do |k, v|
181
+ input_field = type.fetch('inputFields', []).find { |f| f.name == k.to_s }
182
+ return false if input_field.nil?
183
+
184
+ input_field_type = type_for(input_field)
185
+ return false if input_field_type.nil?
186
+
187
+ return false unless valid_value_for_type?(input_field_type, v)
188
+ end
189
+ end
190
+
191
+ def type_for(field_type)
192
+ type = case field_type.type.kind
193
+ when 'NON_NULL'
194
+ non_null_type(field_type.type.ofType)
195
+ when 'LIST'
196
+ field_type.type.ofType
197
+ when 'OBJECT', 'INTERFACE', 'INPUT_OBJECT'
198
+ field_type.type
199
+ when 'SCALAR'
200
+ field_type.type
201
+ end
202
+
203
+ types.find { |t| t.name == type.name }
204
+ end
205
+
206
+ def non_null_type(non_null)
207
+ case non_null.kind
208
+ when 'LIST'
209
+ non_null.ofType
210
+ else
211
+ non_null
212
+ end
213
+ end
214
+ end
215
+ end
data/lib/gqli/node.rb ADDED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './base'
4
+
5
+ module GQLi
6
+ # Node wrapper
7
+ class Node < Base
8
+ attr_reader :__params
9
+
10
+ def initialize(name, params = {}, depth = 1, &block)
11
+ super(name, depth, &block)
12
+ @__params = params
13
+ end
14
+
15
+ # Serializes to a GraphQL string
16
+ def to_gql
17
+ result = ' ' * __depth + __name
18
+ result += '(' + __params_to_s(__params, true) + ')' unless __params.empty?
19
+ unless __nodes.empty?
20
+ result += " {\n"
21
+ result += __nodes.map(&:to_gql).join("\n")
22
+ result += "\n#{' ' * __depth}}"
23
+ end
24
+
25
+ result
26
+ end
27
+
28
+ private
29
+
30
+ def __params_to_s(params, initial = false)
31
+ case params
32
+ when ::Hash
33
+ result = params.map do |k, v|
34
+ "#{k}: #{__params_to_s(v)}"
35
+ end.join(', ')
36
+
37
+ return result if initial
38
+ "{#{result}}"
39
+ when ::Array
40
+ "[#{params.map { |p| __params_to_s(p) }.join(', ')}]"
41
+ when ::String
42
+ "\"#{params}\""
43
+ else
44
+ params.to_s
45
+ end
46
+ end
47
+ end
48
+ end
data/lib/gqli/query.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './base'
4
+
5
+ module GQLi
6
+ # Query node
7
+ class Query < Base
8
+ # Serializes to a GraphQL string
9
+ def to_gql
10
+ result = <<~GQL
11
+ query #{__name ? __name + ' ' : ''}{
12
+ #{__nodes.map(&:to_gql).join("\n")}
13
+ }
14
+ GQL
15
+
16
+ result.lstrip
17
+ end
18
+
19
+ # Delegates itself to the client to be executed
20
+ def __execute(client)
21
+ client.execute(self)
22
+ end
23
+
24
+ # Serializes to a GraphQL string
25
+ def to_s
26
+ to_gql
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hashie/mash'
4
+
5
+ module GQLi
6
+ # Response object wrapper
7
+ class Response
8
+ attr_reader :data, :query
9
+
10
+ def initialize(data, query)
11
+ @data = Hashie::Mash.new(data)
12
+ @query = query
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GraphQL Client and DSL library
4
+ module GQLi
5
+ # Gem version
6
+ VERSION = '0.1.0'
7
+ end
data/lib/gqli.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './gqli/dsl'
4
+ require_relative './gqli/client'
5
+ require_relative './gqli/introspection'
6
+ require_relative './gqli/version'