graphlient 0.7.0 → 0.9.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  4. data/.github/workflows/ci.yml +33 -29
  5. data/.github/workflows/danger-comment.yml +10 -0
  6. data/.github/workflows/danger.yml +13 -22
  7. data/.github/workflows/rubocop.yml +19 -19
  8. data/.gitignore +59 -52
  9. data/.rspec +1 -1
  10. data/.rubocop.yml +26 -28
  11. data/.rubocop_todo.yml +72 -48
  12. data/CHANGELOG.md +128 -107
  13. data/CONTRIBUTING.md +125 -125
  14. data/Dangerfile +24 -24
  15. data/Gemfile +35 -27
  16. data/LICENSE +21 -21
  17. data/README.md +800 -508
  18. data/RELEASING.md +63 -63
  19. data/Rakefile +15 -15
  20. data/UPGRADING.md +23 -23
  21. data/graphlient.gemspec +19 -19
  22. data/lib/graphlient/adapters/http/adapter.rb +41 -41
  23. data/lib/graphlient/adapters/http/faraday_adapter.rb +79 -45
  24. data/lib/graphlient/adapters/http/http_adapter.rb +40 -39
  25. data/lib/graphlient/adapters/http.rb +3 -3
  26. data/lib/graphlient/adapters.rb +1 -1
  27. data/lib/graphlient/client.rb +98 -73
  28. data/lib/graphlient/errors/client_error.rb +6 -6
  29. data/lib/graphlient/errors/connection_failed_error.rb +6 -6
  30. data/lib/graphlient/errors/error.rb +13 -12
  31. data/lib/graphlient/errors/execution_error.rb +29 -29
  32. data/lib/graphlient/errors/faraday_server_error.rb +12 -12
  33. data/lib/graphlient/errors/graphql_error.rb +53 -52
  34. data/lib/graphlient/errors/http_options_error.rb +6 -6
  35. data/lib/graphlient/errors/http_server_error.rb +13 -13
  36. data/lib/graphlient/errors/server_error.rb +7 -7
  37. data/lib/graphlient/errors/timeout_error.rb +6 -6
  38. data/lib/graphlient/errors.rb +10 -10
  39. data/lib/graphlient/extensions/query.rb +15 -15
  40. data/lib/graphlient/extensions.rb +1 -1
  41. data/lib/graphlient/query/directive.rb +47 -0
  42. data/lib/graphlient/query/serializer/arguments.rb +67 -0
  43. data/lib/graphlient/query/serializer/directives.rb +17 -0
  44. data/lib/graphlient/query/serializer/evaluator.rb +15 -0
  45. data/lib/graphlient/query/serializer/fragments.rb +28 -0
  46. data/lib/graphlient/query/serializer/scalars.rb +63 -0
  47. data/lib/graphlient/query/serializer.rb +153 -0
  48. data/lib/graphlient/query.rb +29 -128
  49. data/lib/graphlient/schema.rb +27 -26
  50. data/lib/graphlient/version.rb +3 -3
  51. data/lib/graphlient.rb +8 -8
  52. data/spec/graphlient/adapters/http/faraday_adapter_spec.rb +172 -112
  53. data/spec/graphlient/adapters/http/http_adapter_spec.rb +60 -60
  54. data/spec/graphlient/client_dsl_spec.rb +153 -0
  55. data/spec/graphlient/client_query_spec.rb +369 -346
  56. data/spec/graphlient/client_schema_spec.rb +75 -55
  57. data/spec/graphlient/extensions/query_spec.rb +16 -16
  58. data/spec/graphlient/github_query_spec.rb +32 -32
  59. data/spec/graphlient/query_dsl_spec.rb +231 -0
  60. data/spec/graphlient/query_spec.rb +105 -105
  61. data/spec/graphlient/schema_spec.rb +56 -56
  62. data/spec/graphlient/static_client_query_spec.rb +75 -68
  63. data/spec/graphlient/webmock_client_query_spec.rb +41 -41
  64. data/spec/spec_helper.rb +26 -14
  65. data/spec/support/context/dummy_client.rb +33 -26
  66. data/spec/support/context/github_client.rb +18 -18
  67. data/spec/support/dummy_app.rb +21 -19
  68. data/spec/support/dummy_schema.rb +17 -17
  69. data/spec/support/fixtures/github/schema.yml +14282 -14282
  70. data/spec/support/fixtures/github/user.yml +76 -76
  71. data/spec/support/fixtures/github/viewer.yml +76 -76
  72. data/spec/support/fixtures/invoice_api.json +1288 -1288
  73. data/spec/support/mutations/create_invoice.rb +18 -18
  74. data/spec/support/queries/query.rb +48 -47
  75. data/spec/support/schema/github.json +44479 -44479
  76. data/spec/support/types/invoice_type.rb +13 -13
  77. data/spec/support/types/mutation_type.rb +5 -5
  78. data/spec/support/vcr.rb +9 -9
  79. metadata +13 -8
  80. data/Gemfile.danger +0 -5
@@ -1,112 +1,172 @@
1
- require 'spec_helper'
2
-
3
- describe Graphlient::Adapters::HTTP::FaradayAdapter do
4
- let(:app) { Object.new }
5
-
6
- context 'with a custom middleware' do
7
- let(:client) do
8
- Graphlient::Client.new('http://example.com/graphql') do |client|
9
- client.http do |h|
10
- h.connection do |c|
11
- c.adapter Faraday::Adapter::Rack, app
12
- end
13
- end
14
- end
15
- end
16
-
17
- it 'inserts a middleware into the connection' do
18
- expect(client.http.connection.adapter).to eq Faraday::Adapter::Rack
19
- expect(client.http.connection.builder.handlers).to eq(
20
- [
21
- Faraday::Response::RaiseError,
22
- Faraday::Request::Json,
23
- Faraday::Response::Json
24
- ]
25
- )
26
- end
27
- end
28
-
29
- context 'with custom url, headers and http_options' do
30
- let(:url) { 'http://example.com/graphql' }
31
- let(:headers) { { 'Foo' => 'bar' } }
32
- let(:http_options) { { timeout: timeout, write_timeout: write_timeout } }
33
- let(:timeout) { 123 }
34
- let(:write_timeout) { 234 }
35
- let(:client) do
36
- Graphlient::Client.new(url, headers: headers, http_options: http_options)
37
- end
38
-
39
- it 'sets url' do
40
- expect(client.http.url).to eq url
41
- end
42
-
43
- it 'sets headers' do
44
- expect(client.http.headers).to eq headers
45
- end
46
-
47
- it 'sets http_options' do
48
- expect(client.http.connection.options.timeout).to eq(timeout)
49
- expect(client.http.connection.options.write_timeout).to eq(write_timeout)
50
- end
51
-
52
- context 'when http_options contains invalid option' do
53
- let(:http_options) { { an_invalid_option: 'an invalid option' } }
54
-
55
- it 'raises Graphlient::Errors::HttpOptionsError' do
56
- expect { client.http.connection }.to raise_error(Graphlient::Errors::HttpOptionsError)
57
- end
58
- end
59
- end
60
-
61
- context 'default' do
62
- let(:url) { 'http://example.com/graphql' }
63
- let(:client) { Graphlient::Client.new(url) }
64
-
65
- before do
66
- stub_request(:post, url).to_return(
67
- status: 200,
68
- body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json,
69
- headers: { 'Content-Type' => 'application/json' }
70
- )
71
- end
72
-
73
- it 'retrieves schema' do
74
- expect(client.schema).to be_a Graphlient::Schema
75
- end
76
- end
77
-
78
- context 'Failed to open TCP connection error' do
79
- let(:url) { 'http://example.com/graphql' }
80
- let(:client) { Graphlient::Client.new(url) }
81
- let(:error_message) do
82
- 'Failed to open TCP connection to localhost:3000 (Connection refused - connect(2) for "localhost" port 3000)'
83
- end
84
-
85
- before do
86
- wrapped_error = Errno::ECONNREFUSED.new(error_message)
87
- error = Faraday::ConnectionFailed.new(wrapped_error)
88
-
89
- stub_request(:post, url).to_raise(error)
90
- end
91
-
92
- specify do
93
- expected_error_message = "Connection refused - #{error_message}"
94
- expect { client.schema }.to raise_error(Graphlient::Errors::ConnectionFailedError, expected_error_message)
95
- end
96
- end
97
-
98
- context 'Faraday Timeout Error' do
99
- let(:url) { 'http://example.com/graphql' }
100
- let(:client) { Graphlient::Client.new(url) }
101
- let(:error_message) { 'Failed to Connect' }
102
-
103
- before do
104
- stub_request(:post, url).to_raise(Faraday::TimeoutError.new(Net::ReadTimeout.new(error_message)))
105
- end
106
- it 'raises a Graphlient Timeout' do
107
- expect { client.schema }.to raise_error(Graphlient::Errors::TimeoutError) { |error|
108
- expect(error.message).to include(error_message)
109
- }
110
- end
111
- end
112
- end
1
+ require 'spec_helper'
2
+
3
+ describe Graphlient::Adapters::HTTP::FaradayAdapter do
4
+ let(:app) { Object.new }
5
+
6
+ context 'with a custom middleware' do
7
+ let(:client) do
8
+ Graphlient::Client.new('http://example.com/graphql') do |client|
9
+ client.http do |h|
10
+ h.connection do |c|
11
+ c.adapter Faraday::Adapter::Rack, app
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ it 'inserts a middleware into the connection' do
18
+ expect(client.http.connection.adapter).to eq Faraday::Adapter::Rack
19
+ expect(client.http.connection.builder.handlers).to eq(
20
+ [
21
+ Faraday::Response::RaiseError,
22
+ Faraday::Request::Json,
23
+ Faraday::Response::Json
24
+ ]
25
+ )
26
+ end
27
+ end
28
+
29
+ context 'with custom url, headers and http_options' do
30
+ let(:url) { 'http://example.com/graphql' }
31
+ let(:headers) { { 'Foo' => 'bar' } }
32
+ let(:http_options) { { timeout: timeout, write_timeout: write_timeout } }
33
+ let(:timeout) { 123 }
34
+ let(:write_timeout) { 234 }
35
+ let(:client) do
36
+ Graphlient::Client.new(url, headers: headers, http_options: http_options)
37
+ end
38
+
39
+ it 'sets url' do
40
+ expect(client.http.url).to eq url
41
+ end
42
+
43
+ it 'sets headers' do
44
+ expect(client.http.headers).to eq headers
45
+ end
46
+
47
+ it 'sets http_options' do
48
+ expect(client.http.connection.options.timeout).to eq(timeout)
49
+ expect(client.http.connection.options.write_timeout).to eq(write_timeout)
50
+ end
51
+
52
+ context 'when http_options contains invalid option' do
53
+ let(:http_options) { { an_invalid_option: 'an invalid option' } }
54
+
55
+ it 'raises Graphlient::Errors::HttpOptionsError' do
56
+ expect { client.http.connection }.to raise_error(Graphlient::Errors::HttpOptionsError)
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'default' do
62
+ let(:url) { 'http://example.com/graphql' }
63
+ let(:client) { Graphlient::Client.new(url) }
64
+
65
+ before do
66
+ stub_request(:post, url).to_return(
67
+ status: 200,
68
+ body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json,
69
+ headers: { 'Content-Type' => 'application/json' }
70
+ )
71
+ end
72
+
73
+ it 'retrieves schema' do
74
+ expect(client.schema).to be_a Graphlient::Schema
75
+ end
76
+ end
77
+
78
+ context 'a non-error response without an appropriate JSON header' do
79
+ let(:url) { 'http://example.com/graphql' }
80
+ let(:headers) { { 'Content-Type' => 'application/notjson' } }
81
+ let(:client) { Graphlient::Client.new(url) }
82
+ let(:response_headers) { { 'Content-Type' => 'application/notjson' } }
83
+
84
+ context 'when the response body is valid JSON' do
85
+ before do
86
+ stub_request(:post, url).to_return(
87
+ status: 200,
88
+ headers: response_headers,
89
+ body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json
90
+ )
91
+ end
92
+
93
+ it 'retrieves schema' do
94
+ expect(client.schema).to be_a Graphlient::Schema
95
+ end
96
+ end
97
+
98
+ context 'when the response body is not valid JSON' do
99
+ before do
100
+ stub_request(:post, url).to_return(
101
+ status: 200,
102
+ headers: response_headers,
103
+ body: ''
104
+ )
105
+ end
106
+
107
+ it 'raises Graphlient::Errors::ServerError' do
108
+ expect { client.schema }.to raise_error(Graphlient::Errors::ServerError) { |error|
109
+ expect(error.message).to include('Failed to parse response body as JSON')
110
+ }
111
+ end
112
+ end
113
+
114
+ context 'when the Faraday response body object is not a type we expect from Faraday' do
115
+ before do
116
+ stub_request(:post, url).to_return(
117
+ status: 200,
118
+ headers: response_headers
119
+ )
120
+ end
121
+
122
+ it 'raises Graphlient::Errors::ClientError' do
123
+ mock_response = double('response', body: nil)
124
+ allow(client.http.connection).to receive(:post).and_return(mock_response)
125
+
126
+ expect { client.schema }.to raise_error(Graphlient::Errors::ClientError) { |error|
127
+ expect(error.message).to include "Unexpected response body type 'NilClass'"
128
+ }
129
+ end
130
+ end
131
+ end
132
+
133
+ context 'Failed to open TCP connection error' do
134
+ let(:url) { 'http://example.com/graphql' }
135
+ let(:client) { Graphlient::Client.new(url) }
136
+ let(:error_message) do
137
+ 'Failed to open TCP connection to localhost:3000 (Connection refused - connect(2) for "localhost" port 3000)'
138
+ end
139
+
140
+ before do
141
+ wrapped_error = Errno::ECONNREFUSED.new(error_message)
142
+ error = Faraday::ConnectionFailed.new(wrapped_error)
143
+
144
+ stub_request(:post, url).to_raise(error)
145
+ end
146
+
147
+ specify do
148
+ # Use a regex so the test passes on both Linux ("Connection refused - ...")
149
+ # and Windows (where Errno::ECONNREFUSED prepends a different OS message).
150
+ # Both platforms include the core error_message string in the result.
151
+ expect { client.schema }.to raise_error(
152
+ Graphlient::Errors::ConnectionFailedError,
153
+ /#{Regexp.escape(error_message)}/
154
+ )
155
+ end
156
+ end
157
+
158
+ context 'Faraday Timeout Error' do
159
+ let(:url) { 'http://example.com/graphql' }
160
+ let(:client) { Graphlient::Client.new(url) }
161
+ let(:error_message) { 'Failed to Connect' }
162
+
163
+ before do
164
+ stub_request(:post, url).to_raise(Faraday::TimeoutError.new(Net::ReadTimeout.new(error_message)))
165
+ end
166
+ it 'raises a Graphlient Timeout' do
167
+ expect { client.schema }.to raise_error(Graphlient::Errors::TimeoutError) { |error|
168
+ expect(error.message).to include(error_message)
169
+ }
170
+ end
171
+ end
172
+ end
@@ -1,60 +1,60 @@
1
- require 'spec_helper'
2
-
3
- describe Graphlient::Adapters::HTTP::HTTPAdapter do
4
- let(:app) { Object.new }
5
-
6
- context 'with custom url, headers and http_options' do
7
- let(:url) { 'http://example.com/graphql' }
8
- let(:headers) { { 'Foo' => 'bar' } }
9
- let(:http_options) { { read_timeout: read_timeout } }
10
- let(:read_timeout) { nil }
11
- let(:client) do
12
- Graphlient::Client.new(
13
- url,
14
- headers: headers,
15
- http_options: http_options,
16
- http: Graphlient::Adapters::HTTP::HTTPAdapter
17
- )
18
- end
19
-
20
- it 'sets adapter' do
21
- expect(client.http).to be_a Graphlient::Adapters::HTTP::HTTPAdapter
22
- end
23
-
24
- it 'sets url' do
25
- expect(client.http.url).to eq url
26
- end
27
-
28
- it 'sets headers' do
29
- expect(client.http.headers).to eq headers
30
- end
31
-
32
- it 'sets http_options' do
33
- expect(client.http.connection.read_timeout).to eq(read_timeout)
34
- end
35
-
36
- context 'when http_options contains invalid option' do
37
- let(:http_options) { { an_invalid_option: 'an invalid option' } }
38
-
39
- it 'raises Graphlient::Errors::HttpOptionsError' do
40
- expect { client.http.connection }.to raise_error(Graphlient::Errors::HttpOptionsError)
41
- end
42
- end
43
- end
44
-
45
- context 'default' do
46
- let(:url) { 'http://example.com/graphql' }
47
- let(:client) { Graphlient::Client.new(url, http: Graphlient::Adapters::HTTP::HTTPAdapter) }
48
-
49
- before do
50
- stub_request(:post, url).to_return(
51
- status: 200,
52
- body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json
53
- )
54
- end
55
-
56
- it 'retrieves schema' do
57
- expect(client.schema).to be_a Graphlient::Schema
58
- end
59
- end
60
- end
1
+ require 'spec_helper'
2
+
3
+ describe Graphlient::Adapters::HTTP::HTTPAdapter do
4
+ let(:app) { Object.new }
5
+
6
+ context 'with custom url, headers and http_options' do
7
+ let(:url) { 'http://example.com/graphql' }
8
+ let(:headers) { { 'Foo' => 'bar' } }
9
+ let(:http_options) { { read_timeout: read_timeout } }
10
+ let(:read_timeout) { nil }
11
+ let(:client) do
12
+ Graphlient::Client.new(
13
+ url,
14
+ headers: headers,
15
+ http_options: http_options,
16
+ http: Graphlient::Adapters::HTTP::HTTPAdapter
17
+ )
18
+ end
19
+
20
+ it 'sets adapter' do
21
+ expect(client.http).to be_a Graphlient::Adapters::HTTP::HTTPAdapter
22
+ end
23
+
24
+ it 'sets url' do
25
+ expect(client.http.url).to eq url
26
+ end
27
+
28
+ it 'sets headers' do
29
+ expect(client.http.headers).to eq headers
30
+ end
31
+
32
+ it 'sets http_options' do
33
+ expect(client.http.connection.read_timeout).to eq(read_timeout)
34
+ end
35
+
36
+ context 'when http_options contains invalid option' do
37
+ let(:http_options) { { an_invalid_option: 'an invalid option' } }
38
+
39
+ it 'raises Graphlient::Errors::HttpOptionsError' do
40
+ expect { client.http.connection }.to raise_error(Graphlient::Errors::HttpOptionsError)
41
+ end
42
+ end
43
+ end
44
+
45
+ context 'default' do
46
+ let(:url) { 'http://example.com/graphql' }
47
+ let(:client) { Graphlient::Client.new(url, http: Graphlient::Adapters::HTTP::HTTPAdapter) }
48
+
49
+ before do
50
+ stub_request(:post, url).to_return(
51
+ status: 200,
52
+ body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json
53
+ )
54
+ end
55
+
56
+ it 'retrieves schema' do
57
+ expect(client.schema).to be_a Graphlient::Schema
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ describe Graphlient::Client do
4
+ let(:client) { described_class.new('http://example.com/graphql') }
5
+
6
+ describe '#to_query_string' do
7
+ it 'returns a String' do
8
+ result = client.to_query_string do
9
+ query do
10
+ invoice(id: 10) do
11
+ id
12
+ feeInCents
13
+ end
14
+ end
15
+ end
16
+ expect(result).to be_a String
17
+ end
18
+
19
+ it 'builds a simple query without HTTP or schema access' do
20
+ result = client.to_query_string do
21
+ query do
22
+ invoice(id: 10) do
23
+ id
24
+ feeInCents
25
+ end
26
+ end
27
+ end
28
+ expect(result).to include('query')
29
+ expect(result).to include('invoice')
30
+ expect(result).to include('feeInCents')
31
+ end
32
+
33
+ it 'builds a parameterized query with variable declarations' do
34
+ result = client.to_query_string do
35
+ query(id: :int) do
36
+ invoice(id: :id) do
37
+ id
38
+ feeInCents
39
+ end
40
+ end
41
+ end
42
+ expect(result).to include('$id: Int')
43
+ expect(result).to include('invoice(id: $id)')
44
+ end
45
+
46
+ it 'builds a mutation query string' do
47
+ result = client.to_query_string do
48
+ mutation(input: :CreateInvoiceInput!) do
49
+ createInvoice(input: :input) do
50
+ invoice do
51
+ id
52
+ end
53
+ errors
54
+ end
55
+ end
56
+ end
57
+ expect(result).to include('mutation')
58
+ expect(result).to include('createInvoice')
59
+ expect(result).to include('$input: CreateInvoiceInput!')
60
+ end
61
+
62
+ it 'does not interact with the HTTP adapter' do
63
+ expect_any_instance_of(Graphlient::Adapters::HTTP::FaradayAdapter).not_to receive(:execute)
64
+ client.to_query_string do
65
+ query do
66
+ invoice(id: 1) { id }
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'with spread helper' do
72
+ it 'includes a named fragment spread in the query string' do
73
+ result = client.to_query_string do
74
+ query do
75
+ invoice(id: 10) do
76
+ spread :InvoiceFields
77
+ end
78
+ end
79
+ end
80
+ expect(result).to include('...InvoiceFields')
81
+ end
82
+
83
+ it 'supports multiple spreads at the same level' do
84
+ result = client.to_query_string do
85
+ query do
86
+ nodes do
87
+ spread :FragmentA
88
+ spread :FragmentB
89
+ end
90
+ end
91
+ end
92
+ expect(result).to include('...FragmentA')
93
+ expect(result).to include('...FragmentB')
94
+ end
95
+ end
96
+
97
+ it 'builds a query with a directive' do
98
+ result = client.to_query_string do
99
+ query(skip_fee: :boolean!) do
100
+ invoice(id: 10) do
101
+ id
102
+ feeInCents _skip(if: :skip_fee)
103
+ end
104
+ end
105
+ end
106
+ expect(result).to include('feeInCents @skip(if: $skip_fee)')
107
+ expect(result).to include('$skip_fee: Boolean!')
108
+ end
109
+
110
+ it 'builds a query with an inline fragment' do
111
+ result = client.to_query_string do
112
+ query do
113
+ invoice(id: 10) do
114
+ spread(on: :PaidInvoice) { amountPaid }
115
+ end
116
+ end
117
+ end
118
+ expect(result).to include('... on PaidInvoice')
119
+ expect(result).to include('amountPaid')
120
+ end
121
+
122
+ it 'builds a query with an inline fragment definition' do
123
+ result = client.to_query_string do
124
+ fragment(:InvoiceFields, on: :Invoice) do
125
+ id
126
+ feeInCents
127
+ end
128
+ query { invoice(id: 10) { spread :InvoiceFields } }
129
+ end
130
+ expect(result).to include('...InvoiceFields')
131
+ expect(result).to include('fragment InvoiceFields on Invoice')
132
+ end
133
+ end
134
+
135
+ describe '#scalar (custom scalar registration)' do
136
+ let(:client_with_scalars) do
137
+ described_class.new('http://example.com/graphql') do |c|
138
+ c.scalar :date, 'Date'
139
+ c.scalar :uuid, 'UUID'
140
+ end
141
+ end
142
+
143
+ it 'registers custom scalars via the client block' do
144
+ result = client_with_scalars.to_query_string do
145
+ query(created_after: :date, id: :uuid!) do
146
+ invoices(created_after: :created_after) { total }
147
+ end
148
+ end
149
+ expect(result).to include('$created_after: Date')
150
+ expect(result).to include('$id: UUID!')
151
+ end
152
+ end
153
+ end