diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77063ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.idea/ +.bundle/ +.yardoc +_yardoc/ +coverage/ +doc/ +pkg/ +spec/reports/ +tmp/ + +*.gem +gemfile.lock + +.rvmrc +.ruby-version + +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..bb69742 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--require spec_helper +--format documentation +--color \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..5f10ba8 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' +gemspec diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b7aabc5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Jan van der Pas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..84eb6d2 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Faraday Adapter Template + +This repo is a template for building [Faraday][faraday] adapters. +Faraday is an HTTP client library that provides a common interface over many adapters. +Every adapter is defined into it's own gem. Use this repository to create your own adapter gem. + +## Getting Started + +### Setting up and cloning the repo + +You can start using GitHub's [Use this template][use-template] button. +![Use this template](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) + +This will create a repository based off from this template. +After that is created, you can clone it locally to start working on it. + +### Refactoring the template + +The next step is for you to find and replace all the "parametrised" names in this template and change them to make it unique. +First of all, you should decide on the name of your adapter. +The current convention (which is by no means mandatory) is to call adapter gems as `faraday-`. +Here are some examples: + +* `HTTP`: [`faraday-http`][faraday-http] +* `Net::HTTP`: [`faraday-net_http`][faraday-net_http] + +In this template repository, the placeholder for your chosen adapter name is `MyAdapter` (`my_adapter`). +So once you decide on the final name you want to use you should update all occurrences of `MyAdapter` and all files with `my_adapter` in their name with the new name you chose. + +### Main implementation + +The bulk of the implementation is in the `Faraday::Adapter::MyAdapter` class. +We've added lots of comments in there to guide you through it, but if you have any doubt/question please don't hesitate to get in touch! + +[faraday]: https://github.com/lostisland/faraday +[faraday-http]: https://github.com/lostisland/faraday-http +[faraday-net_http]: https://github.com/lostisland/faraday-net_http +[use-template]: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..82bb534 --- /dev/null +++ b/Rakefile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +task default: :spec diff --git a/bin/ci-lint b/bin/ci-lint new file mode 100755 index 0000000..adaad73 --- /dev/null +++ b/bin/ci-lint @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle exec rubocop --format progress diff --git a/bin/ci-test b/bin/ci-test new file mode 100755 index 0000000..cc823e5 --- /dev/null +++ b/bin/ci-test @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle exec rspec diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..d9d0639 --- /dev/null +++ b/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' +require 'faraday-my_adapter' + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require 'irb' +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..1ba28af --- /dev/null +++ b/bin/setup @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +gem install bundler +bundle install diff --git a/bin/test b/bin/test new file mode 100755 index 0000000..92920be --- /dev/null +++ b/bin/test @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle exec rubocop -a --format progress +bundle exec rspec diff --git a/faraday-my_adapter.gemspec b/faraday-my_adapter.gemspec new file mode 100644 index 0000000..79d7d77 --- /dev/null +++ b/faraday-my_adapter.gemspec @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative 'lib/faraday/my_adapter/version' + +Gem::Specification.new do |spec| + spec.name = 'faraday-my_adapter' + spec.version = Faraday::MyAdapter::VERSION + spec.authors = ['Your Name'] + spec.email = ['your_name@gmail.com'] + + spec.summary = 'Faraday adapter for MyAdapter' + spec.description = 'Faraday adapter for MyAdapter' + spec.homepage = 'https://github.com/lostisland/faraday-my_adapter' + spec.license = 'MIT' + + spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0') + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/lostisland/faraday-my_adapter' + spec.metadata['changelog_uri'] = 'https://github.com/lostisland/faraday-my_adapter' + + spec.files = Dir.glob('lib/**/*') + %w[README.md LICENSE.md] + spec.require_paths = ['lib'] + + spec.add_development_dependency 'faraday', '~> 1.0' + + spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'simplecov', '~> 0.19.0' + + spec.add_development_dependency 'multipart-parser', '~> 0.1.1' + spec.add_development_dependency 'webmock', '~> 3.4' +end diff --git a/lib/faraday/adapter/my_adapter.rb b/lib/faraday/adapter/my_adapter.rb new file mode 100644 index 0000000..0a667f9 --- /dev/null +++ b/lib/faraday/adapter/my_adapter.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Faraday + class Adapter + # This class provides the main implementation for your adapter. + # There are some key responsibilities that your adapter should satisfy: + # * Initialize and store internally the client you chose (e.g. Net::HTTP) + # * Process requests and save the response (see `#call`) + class MyAdapter < Faraday::Adapter + # The initialize method is lazy-called ONCE when the connection stack is built. + # See https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb + # + # @param app [#call] the "rack app" wrapped in middleware. See https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L157 + # @param opts [Hash] the options hash with all the options necessary for the adapter to correctly configure itself. + # These are automatically stored into `@connection_options` when you call `super`. + # @param block [Proc] the configuration block for the adapter. This usually provides the possibility to access the internal client from the outside + # and set properties that are not included in Faraday's API. It's automatically stored into `@config_block` when you call `super`. + def initialize(app = nil, opts = {}, &block) + super(app, opts, &block) + end + + # This is the main method in your adapter. Since an adapter is a middleware, this method will be called FOR EVERY REQUEST. + # The main task of this method is to perform a call using the internal client and save the response. + # Since this method is not called directly f`rom the outside, you'll need to use `env` in order to: + # * Get the request parameters (see `Faraday::Env` and `Faraday::RequestOptions` for the full list). This includes processing: + # * The request method, url, headers, parameters and body + # * The SSL configuration (env[:ssl]) + # * The request configuration (env[:request]), i.e. things like: timeout, proxy, etc... + # * Set the response attributes. This can be done easily by calling `save_response`. These include: + # * Response headers and body + # * Response status and reason_phrase + # + # @param env [Faraday::Env] the environment of the request being processed + def call(env) + # First thing to do should be to call `super`. This will take care of the following for you: + # * Clear the body if the request supports a body + # * Initialize `env.response` to a `Faraday::Response` + super + + # Next you want to configure your client for the request and perform it, obtaining the response. + response = {} # Make call using client + + # Now that you got the response in the client's format, you need to call `save_response` to store the necessary + # details into the `env`. This method accepts a block to make it easier for you to set response headers using + # `Faraday::Utils::Headers`. Simply provide a block that given a `response_headers` hash sets the necessary key/value pairs. + # Faraday will automatically take care of things like capitalising keys and coercing values. + save_response(env, response.status, response.body, response.headers, response.reason_phrase) do |response_headers| + response.headers.each do |key, value| + response_headers[key] = value + end + end + + # NOTE: An adapter `call` MUST return the `env.response`. If `save_response` is the last line in your `call` + # method implementation, it will automatically return the response for you. + # Otherwise, you'll need to manually do so. You can do this with any (not both) of the following lines: + # * @app.call(env) + # * env.response + # Finally, it's good practice to rescue client-specific exceptions (e.g. Timeout, ConnectionFailed, etc...) + # and re-raise them as Faraday Errors. Check `Faraday::Error` for a list of all errors. + rescue MyAdapterTimeout => e + # Most errors allow you to provide the original exception and optionally (if available) the response, to + # make them available outside of the middleware stack. + raise Faraday::TimeoutError, e + end + end + end +end diff --git a/lib/faraday/my_adapter.rb b/lib/faraday/my_adapter.rb new file mode 100644 index 0000000..4bfead3 --- /dev/null +++ b/lib/faraday/my_adapter.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative 'adapter/my_adapter' +require_relative 'my_adapter/version' + +module Faraday + module MyAdapter + # Faraday allows you to register your middleware for easier configuration. + # This step is totally optional, but it basically allows users to use a custom symbol (in this case, `:my_adapter`), + # to use your adapter in their connections. + # After calling this line, the following are both valid ways to set the adapter in a connection: + # * conn.adapter Faraday::Adapter::MyAdapter + # * conn.adapter :my_adapter + # Without this line, only the former method is valid. + Faraday::Adapter.register_middleware(my_adapter: Faraday::Adapter::MyAdapter) + end +end diff --git a/lib/faraday/my_adapter/version.rb b/lib/faraday/my_adapter/version.rb new file mode 100644 index 0000000..b19a19b --- /dev/null +++ b/lib/faraday/my_adapter/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Faraday + module MyAdapter + VERSION = '0.1.0' + end +end diff --git a/spec/faraday/adapter/my_adapter_spec.rb b/spec/faraday/adapter/my_adapter_spec.rb new file mode 100644 index 0000000..899bf56 --- /dev/null +++ b/spec/faraday/adapter/my_adapter_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.describe Faraday::Adapter::MyAdapter do + # Since not all adapters support all the features Faraday has to offer, you can use the `features` method to turn on + # only the ones you know you can support. + # TODO: provide the full list of available features! + features :request_body_on_query_methods, + :reason_phrase_parse, + :compression, + :streaming, + :trace_method + + # Runs the tests provide by Faraday, according to the features specified above. + it_behaves_like 'an adapter' + + # You can then add any other test specific to this adapter here... +end diff --git a/spec/faraday/my_adapter_spec.rb b/spec/faraday/my_adapter_spec.rb new file mode 100644 index 0000000..81443ef --- /dev/null +++ b/spec/faraday/my_adapter_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe Faraday::MyAdapter do + it 'has a version number' do + expect(Faraday::MyAdapter::VERSION).to be_a(String) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..182ab28 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'faraday' +require 'faraday/my_adapter' +# This is the magic bit. It requires a tests suite from the Faraday gem that you can run against your adapter +require 'faraday_specs_setup' + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_status' + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end + + config.order = :random +end