graphql-repl 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 (6) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/graphql-repl +27 -0
  5. data/lib/graphql/repl.rb +199 -0
  6. metadata +90 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 747c28926505ecac64db4e2c981ab95832690b3d
4
+ data.tar.gz: 88256b18cb342a255a7042618c8399df26933f7e
5
+ SHA512:
6
+ metadata.gz: 15c3ba2c8bfcfc87eb0299d9d59cc50faeb86252667de1d329a5908e8e6d80b24059b9f073055074677f9b7cae4c85000b01dba6d1d08ee8fe87da9e15b485b6
7
+ data.tar.gz: bde1260e3ecc07487ab92ea4920dfe544ab9e197f3a1d2055b74a7f40868b791540ab3ebd2256adf4bfb519969d66bde9fc6e1db4477f381365930a9db8accc5
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 Joshua Peek
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # GraphQL REPL
2
+
3
+ ```
4
+ $ bin/graphql-repl -a "Bearer abc123" https://api.github.com/graphql
5
+ > viewer.login
6
+ "josh"
7
+
8
+ > node(id: "MDEwOlJlcG9zaXRvcnk4MDkwNTI3NQ==").on(Repository).url
9
+ "https://github.com/josh/graphql-repl"
10
+
11
+ > viewer.lo<tab>
12
+ viewer.location viewer.login
13
+ ```
data/bin/graphql-repl ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'graphql/repl'
5
+ require 'optparse'
6
+ require 'readline'
7
+
8
+ options = {
9
+ headers: {}
10
+ }
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = "Usage: graphql-repl url [options]"
14
+
15
+ opts.on("-a=", "--authorization=", "Set Authorization HTTP header") do |v|
16
+ options[:headers]["Authorization"] = v
17
+ end
18
+ end.parse!
19
+
20
+ repl = GraphQL::REPL.new(ARGV[0], options[:headers])
21
+
22
+ Readline.completion_append_character = ""
23
+ Readline.completion_proc = proc { |s| repl.completions(s) }
24
+
25
+ while line = Readline.readline('> ', true)
26
+ p repl.eval(line.chomp)
27
+ end
@@ -0,0 +1,199 @@
1
+ require 'graphql'
2
+ require 'json'
3
+ require 'net/http'
4
+
5
+ module GraphQL
6
+ class REPL
7
+ IntrospectionDocument = GraphQL.parse(GraphQL::Introspection::INTROSPECTION_QUERY)
8
+
9
+ def initialize(uri, headers = {})
10
+ @uri = URI.parse(uri)
11
+ @headers = headers
12
+
13
+ @connection = Net::HTTP.new(@uri.host, @uri.port).tap do |client|
14
+ client.use_ssl = @uri.scheme == "https"
15
+ end
16
+
17
+ @schema = GraphQL::Schema::Loader.load(request(IntrospectionDocument.to_query_string))
18
+ end
19
+
20
+ def request(query)
21
+ request = Net::HTTP::Post.new(@uri.request_uri)
22
+ request["Accept"] = "application/json"
23
+ request["Content-Type"] = "application/json"
24
+ @headers.each { |k, v| request[k] = v }
25
+ request.body = JSON.generate({"query" => query})
26
+ response = @connection.request(request)
27
+ JSON.parse(response.body)
28
+ end
29
+
30
+ def eval(str)
31
+ expr = Expression.new(@schema, str)
32
+
33
+ if error = expr.error_message
34
+ return error
35
+ end
36
+
37
+ result = request(expr.document.to_query_string)
38
+
39
+ if result["errors"] && result["errors"][0]
40
+ result["errors"][0]["message"]
41
+ else
42
+ expr.response_path.reduce(result["data"]) { |obj, key| obj[key] }
43
+ end
44
+ end
45
+
46
+ def completions(str)
47
+ Expression.new(@schema, str).completions
48
+ end
49
+
50
+ class Proxy
51
+ def self.const_missing(name)
52
+ name
53
+ end
54
+
55
+ attr_accessor :calls
56
+
57
+ def initialize
58
+ @calls = []
59
+ end
60
+
61
+ def get_binding
62
+ binding
63
+ end
64
+
65
+ def method_missing(*args)
66
+ @calls << args
67
+ self
68
+ end
69
+ end
70
+
71
+ class Expression
72
+ def initialize(schema, str)
73
+ @schema = schema
74
+ @str = str
75
+ end
76
+
77
+ def calls
78
+ proxy = Proxy.new
79
+ b = proxy.get_binding
80
+ b.eval(@str).calls
81
+ end
82
+
83
+ def completions
84
+ first, partial = @str.scan(/^(.+)\.(.*)$/)[0]
85
+
86
+ if first
87
+ type = Expression.new(@schema, first).return_type
88
+ else
89
+ partial = @str
90
+ type = @schema.query.to_s
91
+ end
92
+
93
+ if type
94
+ @schema.types[type].all_fields.map(&:name).grep(/^#{partial}/).map { |name|
95
+ [first, name].compact.join(".")
96
+ }
97
+ else
98
+ []
99
+ end
100
+ end
101
+
102
+ def response_path
103
+ calls.reduce([]) do |path, (name, *args)|
104
+ case name
105
+ when :query, :mutation, :on
106
+ path
107
+ when :[]
108
+ path << args[0]
109
+ else
110
+ path << name.to_s
111
+ end
112
+ end
113
+ end
114
+
115
+ def document
116
+ calls = self.calls
117
+
118
+ operation_type = case calls[0][0]
119
+ when :query
120
+ calls.shift
121
+ "query"
122
+ when :mutation
123
+ calls.shift
124
+ "mutation"
125
+ else
126
+ "query"
127
+ end
128
+
129
+ selection = calls.reverse.reduce(nil) do |child, call|
130
+ case call[0]
131
+ when :[]
132
+ child
133
+ when :on
134
+ type = GraphQL::Language::Nodes::TypeName.new(name: call[1].to_s)
135
+ node = GraphQL::Language::Nodes::InlineFragment.new(type: type)
136
+ node.selections = [child] if child
137
+ node
138
+ else
139
+ arguments = []
140
+
141
+ (call[1] || {}).each do |name, value|
142
+ arguments << GraphQL::Language::Nodes::Argument.new(name: name.to_s, value: value)
143
+ end
144
+
145
+ node = GraphQL::Language::Nodes::Field.new(name: call[0].to_s, arguments: arguments)
146
+ node.selections = [child] if child
147
+ node
148
+ end
149
+ end
150
+
151
+ operation = GraphQL::Language::Nodes::OperationDefinition.new(operation_type: operation_type, selections: [selection])
152
+ GraphQL::Language::Nodes::Document.new(definitions: [operation])
153
+ end
154
+
155
+ def return_type
156
+ visitor = GraphQL::Language::Visitor.new(document)
157
+ type_stack = GraphQL::StaticValidation::TypeStack.new(@schema, visitor)
158
+
159
+ stack = []
160
+
161
+ visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, _parent) do
162
+ stack << type_stack.object_types.last
163
+ end
164
+ visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, _parent) do
165
+ stack << type_stack.object_types.last
166
+ end
167
+ visitor[GraphQL::Language::Nodes::Field] << ->(node, _parent) do
168
+ stack << type_stack.field_definitions.last&.type
169
+ end
170
+ visitor[GraphQL::Language::Nodes::InlineFragment] << ->(node, _parent) do
171
+ stack << type_stack.object_types.last
172
+ end
173
+ visitor.visit
174
+
175
+ type = stack.last
176
+ type = type.of_type if type.is_a?(GraphQL::NonNullType)
177
+
178
+ if type.is_a?(GraphQL::ListType)
179
+ if response_path.last.is_a?(Integer)
180
+ type.unwrap.name
181
+ else
182
+ "List"
183
+ end
184
+ elsif type
185
+ type.name
186
+ else
187
+ nil
188
+ end
189
+ end
190
+
191
+ def error_message
192
+ validator = GraphQL::StaticValidation::Validator.new(schema: @schema)
193
+ query = GraphQL::Query.new(@schema, document: document)
194
+ errors = validator.validate(query)
195
+ errors.fetch(:errors)[0]&.message
196
+ end
197
+ end
198
+ end
199
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql-repl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Peek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphql
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ description: ''
56
+ email: josh@joshpeek.com
57
+ executables:
58
+ - graphql-repl
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - README.md
64
+ - bin/graphql-repl
65
+ - lib/graphql/repl.rb
66
+ homepage: https://github.com/josh/graphql-repl
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 2.3.3
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.5.2
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: GraphQL REPL
90
+ test_files: []