diff --git a/.rubocop.yml b/.rubocop.yml index 6f907c4..8b2cd92 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,19 @@ Style/FrozenStringLiteralComment: Enabled: false +Style/Documentation: + Enabled: false + +Metrics/LineLength: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/BlockLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false + inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bbb3dab..3264073 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,31 +1,17 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-10-26 10:54:26 -0400 using RuboCop version 0.47.1. +# on 2017-10-26 13:24:56 -0400 using RuboCop version 0.47.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 1 -Lint/UselessAssignment: +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: Exclude: - - 'spec/graphlient/client_query_spec.rb' - -# Offense count: 11 -# Configuration parameters: CountComments, ExcludedMethods. -Metrics/BlockLength: - Max: 185 - -# Offense count: 20 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 177 - -# Offense count: 2 -# Configuration parameters: CountComments. -Metrics/MethodLength: - Max: 12 + - 'lib/graphlient/adapters/http/http_adapter.rb' # Offense count: 1 # Configuration parameters: EnforcedStyle, SupportedStyles. @@ -34,18 +20,6 @@ Style/ClassAndModuleChildren: Exclude: - 'spec/graphlient/static_client_query_spec.rb' -# Offense count: 6 -Style/Documentation: - Exclude: - - 'spec/**/*' - - 'test/**/*' - - 'lib/graphlient/adapters/faraday_adapter.rb' - - 'lib/graphlient/client.rb' - - 'lib/graphlient/errors/error.rb' - - 'lib/graphlient/errors/graphql.rb' - - 'lib/graphlient/extensions/query.rb' - - 'lib/graphlient/query.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6331fd5..8ca0afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### 0.0.9 (Next) * [#28](https://github.com/ashkan18/graphlient/pull/28): Raise errors in `execute`, not only `query` - [@dblock](https://github.com/dblock). +* [#29](https://github.com/ashkan18/graphlient/pull/29): Added `Graphlient::Adapters::HTTP::HTTPAdapter` that replaces Faraday with `Net::HTTP` - [@dblock](https://github.com/dblock). * Your contribution here. ### 0.0.8 (10/26/2017) diff --git a/README.md b/README.md index 3a37f8f..25d8197 100644 --- a/README.md +++ b/README.md @@ -265,9 +265,19 @@ query.to_s # "\nquery{\n invoice(id: 10){\n line_items\n }\n }\n" ``` +### Swapping the HTTP Stack + +You can swap the default Faraday adapter for `Net::HTTP`. + +```ruby +client = Graphlient::Client.new('https://test-graphql.biz/graphql', + http: Graphlient::Adapters::HTTP::HTTPAdapter +) +``` + ### Testing with Graphlient and RSpec -Use Graphlient inside your RSpec tests in a Rails application or with `Rack::Test`, no more messy HTTP POSTs. +Use Graphlient inside your RSpec tests in a Rails application or with `Rack::Test` against your actual application. ```ruby require 'spec_helper' @@ -307,6 +317,26 @@ describe App do end ``` +Alternately you can `stub_request` with Webmock. + +```ruby +describe App do + let(:url) { 'http://example.com/graphql' } + let(:client) { Graphlient::Client.new(url) } + + before do + stub_request(:post, url).to_return( + status: 200, + body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json + ) + end + + it 'retrieves schema' do + expect(client.schema).to be_a GraphQL::Schema + end +end +``` + ## License MIT License, see [LICENSE](LICENSE) diff --git a/lib/graphlient.rb b/lib/graphlient.rb index 063168b..16af36a 100644 --- a/lib/graphlient.rb +++ b/lib/graphlient.rb @@ -1,5 +1,7 @@ +require 'graphql/client' require 'graphlient/version' require 'graphlient/extensions' require 'graphlient/errors' require 'graphlient/query' +require 'graphlient/adapters' require 'graphlient/client' diff --git a/lib/graphlient/adapters.rb b/lib/graphlient/adapters.rb new file mode 100644 index 0000000..4f19f0f --- /dev/null +++ b/lib/graphlient/adapters.rb @@ -0,0 +1 @@ +require_relative 'adapters/http' diff --git a/lib/graphlient/adapters/faraday_adapter.rb b/lib/graphlient/adapters/faraday_adapter.rb deleted file mode 100644 index b71a935..0000000 --- a/lib/graphlient/adapters/faraday_adapter.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'faraday' -require 'faraday_middleware' - -module Graphlient - module Adapters - class FaradayAdapter - attr_accessor :url, :headers - - def initialize(url, headers:, &_block) - @url = url - @headers = headers.dup if headers - yield self if block_given? - end - - def execute(document:, operation_name:, variables:, context:) - response = connection.post do |req| - req.headers.merge!(context[:headers] || {}) - req.body = { - query: document.to_query_string, - operationName: operation_name, - variables: variables.to_json - }.to_json - end - response.body - rescue Faraday::ClientError => e - raise Graphlient::Errors::Server.new(e.message, e) - end - - def connection - @connection ||= Faraday.new(url: url, headers: headers) do |c| - c.use Faraday::Response::RaiseError - c.request :json - c.response :json - if block_given? - yield c - else - c.use Faraday::Adapter::NetHttp - end - end - end - end - end -end diff --git a/lib/graphlient/adapters/http.rb b/lib/graphlient/adapters/http.rb new file mode 100644 index 0000000..c9d87e3 --- /dev/null +++ b/lib/graphlient/adapters/http.rb @@ -0,0 +1,3 @@ +require_relative 'http/adapter' +require_relative 'http/faraday_adapter' +require_relative 'http/http_adapter' diff --git a/lib/graphlient/adapters/http/adapter.rb b/lib/graphlient/adapters/http/adapter.rb new file mode 100644 index 0000000..f3169a3 --- /dev/null +++ b/lib/graphlient/adapters/http/adapter.rb @@ -0,0 +1,23 @@ +module Graphlient + module Adapters + module HTTP + class Adapter + attr_accessor :url, :options + + def initialize(url, options = {}, &_block) + @url = url + @options = options.dup if options + yield self if block_given? + end + + def headers + options[:headers] if options + end + + def execute(*) + raise NotImplementedError + end + end + end + end +end diff --git a/lib/graphlient/adapters/http/faraday_adapter.rb b/lib/graphlient/adapters/http/faraday_adapter.rb new file mode 100644 index 0000000..9f3f171 --- /dev/null +++ b/lib/graphlient/adapters/http/faraday_adapter.rb @@ -0,0 +1,37 @@ +require 'faraday' +require 'faraday_middleware' + +module Graphlient + module Adapters + module HTTP + class FaradayAdapter < Adapter + def execute(document:, operation_name:, variables:, context:) + response = connection.post do |req| + req.headers.merge!(context[:headers] || {}) + req.body = { + query: document.to_query_string, + operationName: operation_name, + variables: variables.to_json + }.to_json + end + response.body + rescue Faraday::ClientError => e + raise Graphlient::Errors::Server.new(e.message, e) + end + + def connection + @connection ||= Faraday.new(url: url, headers: headers) do |c| + c.use Faraday::Response::RaiseError + c.request :json + c.response :json + if block_given? + yield c + else + c.use Faraday::Adapter::NetHttp + end + end + end + end + end + end +end diff --git a/lib/graphlient/adapters/http/http_adapter.rb b/lib/graphlient/adapters/http/http_adapter.rb new file mode 100644 index 0000000..bb0d975 --- /dev/null +++ b/lib/graphlient/adapters/http/http_adapter.rb @@ -0,0 +1,39 @@ +require 'graphql/client/http' + +module Graphlient + module Adapters + module HTTP + class HTTPAdapter < Adapter + attr_reader :uri + + def execute(document:, operation_name: nil, variables: {}, context: {}) + request = Net::HTTP::Post.new(url) + + request['Accept'] = 'application/json' + request['Content-Type'] = 'application/json' + headers&.each { |name, value| request[name] = value } + + body = {} + body['query'] = document.to_query_string + body['variables'] = variables if variables.any? + body['operationName'] = operation_name if operation_name + request.body = JSON.generate(body) + + response = connection.request(request) + raise Graphlient::Errors::Server.new("the server responded with status #{response.code}", response) unless response.is_a?(Net::HTTPOK) + JSON.parse(response.body) + end + + def uri + @uri ||= URI(url) + end + + def connection + Net::HTTP.new(uri.host, uri.port).tap do |client| + client.use_ssl = uri.scheme == 'https' + end + end + end + end + end +end diff --git a/lib/graphlient/client.rb b/lib/graphlient/client.rb index e72d1fd..1baffb6 100644 --- a/lib/graphlient/client.rb +++ b/lib/graphlient/client.rb @@ -1,6 +1,3 @@ -require 'graphql/client' -require 'graphlient/adapters/faraday_adapter' - module Graphlient class Client attr_accessor :uri, :options @@ -40,8 +37,12 @@ def query(query_or_variables = nil, variables = nil, &block) end end + def http_adapter_class + options[:http] || Adapters::HTTP::FaradayAdapter + end + def http(&block) - @http ||= Adapters::FaradayAdapter.new(@url, headers: @options[:headers], &block) + @http ||= http_adapter_class.new(@url, headers: @options[:headers], &block) end def schema diff --git a/spec/graphlient/adapters/faraday_adapter_spec.rb b/spec/graphlient/adapters/http/faraday_adapter_spec.rb similarity index 69% rename from spec/graphlient/adapters/faraday_adapter_spec.rb rename to spec/graphlient/adapters/http/faraday_adapter_spec.rb index d156cf2..8b6feef 100644 --- a/spec/graphlient/adapters/faraday_adapter_spec.rb +++ b/spec/graphlient/adapters/http/faraday_adapter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Graphlient::Adapters::FaradayAdapter do +describe Graphlient::Adapters::HTTP::FaradayAdapter do let(:app) { Object.new } context 'with a custom middleware' do @@ -41,4 +41,20 @@ expect(client.http.headers).to eq headers end end + + context 'default' do + let(:url) { 'http://example.com/graphql' } + let(:client) { Graphlient::Client.new(url) } + + before do + stub_request(:post, url).to_return( + status: 200, + body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json + ) + end + + it 'retrieves schema' do + expect(client.schema).to be_a GraphQL::Schema + end + end end diff --git a/spec/graphlient/adapters/http/http_adapter_spec.rb b/spec/graphlient/adapters/http/http_adapter_spec.rb new file mode 100644 index 0000000..7fb207b --- /dev/null +++ b/spec/graphlient/adapters/http/http_adapter_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Graphlient::Adapters::HTTP::HTTPAdapter do + let(:app) { Object.new } + + context 'with custom url and headers' do + let(:url) { 'http://example.com/graphql' } + let(:headers) { { 'Foo' => 'bar' } } + let(:client) do + Graphlient::Client.new(url, headers: headers, http: Graphlient::Adapters::HTTP::HTTPAdapter) + end + + it 'sets adapter' do + expect(client.http).to be_a Graphlient::Adapters::HTTP::HTTPAdapter + end + + it 'sets url' do + expect(client.http.url).to eq url + end + + it 'sets headers' do + expect(client.http.headers).to eq headers + end + end + + context 'default' do + let(:url) { 'http://example.com/graphql' } + let(:client) { Graphlient::Client.new(url, http: Graphlient::Adapters::HTTP::HTTPAdapter) } + + before do + stub_request(:post, url).to_return( + status: 200, + body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json + ) + end + + it 'retrieves schema' do + expect(client.schema).to be_a GraphQL::Schema + end + end +end diff --git a/spec/graphlient/client_query_spec.rb b/spec/graphlient/client_query_spec.rb index f0b47ac..37cb535 100644 --- a/spec/graphlient/client_query_spec.rb +++ b/spec/graphlient/client_query_spec.rb @@ -78,7 +78,7 @@ it 'fails when wrong input type' do expect do - rc = client.execute(query, ids: ['42']) + client.execute(query, ids: ['42']) end.to raise_error Graphlient::Errors::GraphQL do |e| expect(e.to_s).to eq "Variable ids of type [Int] was provided invalid value\n 0: Could not coerce value \"42\" to Int" end