pax_global_header00006660000000000000000000000064136400724540014517gustar00rootroot0000000000000052 comment=243239feedb9713ff89479c4b6535ea709d014fa faraday_middleware-1.0.0/000077500000000000000000000000001364007245400153215ustar00rootroot00000000000000faraday_middleware-1.0.0/.github/000077500000000000000000000000001364007245400166615ustar00rootroot00000000000000faraday_middleware-1.0.0/.github/workflows/000077500000000000000000000000001364007245400207165ustar00rootroot00000000000000faraday_middleware-1.0.0/.github/workflows/ci.yml000066400000000000000000000037001364007245400220340ustar00rootroot00000000000000name: CI on: pull_request: push: branches: - master env: GIT_COMMIT_SHA: ${{ github.sha }} GIT_BRANCH: ${{ github.ref }} CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} jobs: linting: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up Ruby 2.6 uses: actions/setup-ruby@v1 with: ruby-version: 2.6.x - name: Rubocop run: | gem install rubocop rubocop-performance --no-document rubocop --require rubocop-performance --format progress - name: Yard-Junk run: | gem install yard-junk --no-document yard-junk --path lib build: needs: [linting] runs-on: ubuntu-latest strategy: matrix: ruby: ['2.4', '2.5', '2.6', '2.7'] steps: - uses: actions/checkout@v1 - name: Install dependencies run: | sudo apt-get install libcurl4-openssl-dev - name: Set up RVM run: | curl -sSL https://get.rvm.io | bash - name: Set up Ruby run: | source $HOME/.rvm/scripts/rvm rvm install ${{ matrix.ruby }} --disable-binary rvm --default use ${{ matrix.ruby }} - name: Build run: | source $HOME/.rvm/scripts/rvm sudo apt-get install libcurl4-openssl-dev gem install bundler -v '<2' bundle install --jobs 4 --retry 3 - name: Setup Code Climate if: matrix.ruby == '2.6' run: | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter chmod +x ./cc-test-reporter ./cc-test-reporter before-build - name: Test run: | source $HOME/.rvm/scripts/rvm bundle exec rake - name: Run Code Climate Test Reporter if: success() && matrix.ruby == '2.6' run: ./cc-test-reporter after-build --coverage-input-type simplecov --exit-code $? continue-on-error: true faraday_middleware-1.0.0/.github/workflows/publish.yml000066400000000000000000000013061364007245400231070ustar00rootroot00000000000000name: Publish on: release: types: [published] jobs: build: name: Publish to Rubygems runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Set up Ruby 2.6 uses: actions/setup-ruby@v1 with: ruby-version: 2.6.x - name: Publish to RubyGems run: | mkdir -p $HOME/.gem touch $HOME/.gem/credentials chmod 0600 $HOME/.gem/credentials printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials gem build faraday_middleware.gemspec gem push faraday_middleware-*.gem env: GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_AUTH_TOKEN }} faraday_middleware-1.0.0/.gitignore000066400000000000000000000003401364007245400173060ustar00rootroot00000000000000# TextMate *.tmproj tmtags # emails *~ \#* .\#* # vim *.swp # General .ruby-version bin coverage rdoc doc log .yardoc tmp # Bundler *.gem .bundle Gemfile*.lock pkg *.gem vendor/bundle # Rubinius *.rbc # RubyMine .idea faraday_middleware-1.0.0/.rspec000066400000000000000000000000551364007245400164360ustar00rootroot00000000000000--require spec_helper --color --order random faraday_middleware-1.0.0/.rubocop.yml000066400000000000000000000013121364007245400175700ustar00rootroot00000000000000inherit_from: .rubocop_todo.yml require: - rubocop-performance AllCops: DisplayCopNames: true DisplayStyleGuide: true TargetRubyVersion: 2.3 Metrics/BlockLength: Exclude: - spec/**/*.rb - examples/**/*.rb Layout/LineLength: Exclude: - spec/**/*.rb - examples/**/*.rb Naming/PredicateName: Exclude: - 'lib/faraday_middleware/request/encode_json.rb' Security/MarshalLoad: Exclude: - 'lib/faraday_middleware/response/parse_marshal.rb' - 'spec/unit/caching_spec.rb' Style/AccessModifierDeclarations: Exclude: - 'spec/unit/follow_redirects_spec.rb' Style/DoubleNegation: Enabled: false Style/Documentation: Exclude: - 'spec/**/*' - 'examples/**/*' faraday_middleware-1.0.0/.rubocop_todo.yml000066400000000000000000000010461364007245400206210ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2020-02-07 22:38:48 +0100 using RuboCop version 0.79.0. # 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: 5 Metrics/AbcSize: Max: 20 # Offense count: 10 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 17 faraday_middleware-1.0.0/CONTRIBUTING.md000066400000000000000000000034251364007245400175560ustar00rootroot00000000000000## Contributing In the spirit of [free software][free-sw], **everyone** is encouraged to help improve this project. [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html Here are some ways *you* can contribute: * by using alpha, beta, and prerelease versions * by reporting bugs * by suggesting new features * by writing or editing documentation * by writing specifications * by writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace) * by refactoring code * by fixing [issues][] * by reviewing patches [issues]: https://github.com/lostisland/faraday_middleware/issues ## Submitting an Issue We use the [GitHub issue tracker][issues] to track bugs and features. Before submitting a bug report or feature request, check to make sure it hasn't already been submitted. When submitting a bug report, please include a [Gist][] that includes a stack trace and any details that may be necessary to reproduce the bug, including your gem version, Ruby version, and operating system. Ideally, a bug report should include a pull request with failing specs. [gist]: https://gist.github.com/ ## Submitting a Pull Request 1. [Fork the repository.][fork] 2. [Create a topic branch.][branch] 3. Add specs for your unimplemented feature or bug fix. 4. Run `bundle exec rake spec`. If your specs pass, return to step 3. 5. Implement your feature or bug fix. 6. Run `COVERAGE=true bundle exec rake spec`. If your specs fail, return to step 5. 7. Run `open coverage/index.html`. If your changes are not completely covered by your tests, return to step 3. 8. Add, commit, and push your changes. 9. [Submit a pull request.][pr] [fork]: http://help.github.com/fork-a-repo/ [branch]: http://learn.github.com/p/branching.html [pr]: http://help.github.com/send-pull-requests/ faraday_middleware-1.0.0/Gemfile000066400000000000000000000007701364007245400166200ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gem 'brotli', '>= 0.1.8', platforms: :mri gem 'hashie', '>= 1.2' gem 'json', '< 3' gem 'multi_xml', '>= 0.5.3' gem 'rack', '< 2' gem 'rack-cache', '>= 1.1', '< 1.3' gem 'rake', '>= 12.3.3' gem 'rash_alt', '>= 0.4.3' gem 'safe_yaml' gem 'simple_oauth', '>= 0.1', '< 0.3' group :test do gem 'addressable', '< 2.4' gem 'rspec', '>= 3' gem 'rubocop-performance', '~> 1.0' gem 'simplecov', '~> 0.12.0' gem 'webmock', '< 2' end gemspec faraday_middleware-1.0.0/LICENSE.md000066400000000000000000000020771364007245400167330ustar00rootroot00000000000000Copyright (c) 2011 Erik Michaels-Ober, Wynn Netherland, et al. 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. faraday_middleware-1.0.0/README.md000066400000000000000000000052731364007245400166070ustar00rootroot00000000000000Faraday Middleware ================== [![Gem Version](https://badge.fury.io/rb/faraday_middleware.svg)](https://rubygems.org/gems/faraday_middleware) ![GitHub Actions CI](https://github.com/lostisland/faraday_middleware/workflows/CI/badge.svg) [![Maintainability](https://api.codeclimate.com/v1/badges/a971ee5025b269c39d93/maintainability)](https://codeclimate.com/github/lostisland/faraday_middleware/maintainability) A collection of useful [Faraday][] middleware. [See the documentation][docs]. gem install faraday_middleware Dependencies ------------ Ruby >= 2.3.0 #### As of v0.16.0, `faraday` and `faraday_middleware` no longer officially support JRuby or Rubinius. Some dependent libraries are needed only when using specific middleware: | Middleware | Library | Notes | | --------------------------- | -------------- | ----- | | [FaradayMiddleware::Instrumentation](https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/instrumentation.rb) | [`activesupport`](https://rubygems.org/gems/activesupport) | | | [FaradayMiddleware::OAuth](https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/request/oauth.rb) | [`simple_oauth`](https://rubygems.org/gems/simple_oauth) | | | [FaradayMiddleware::ParseXml](https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/response/parse_xml.rb) | [`multi_xml`](https://rubygems.org/gems/multi_xml) | | | [FaradayMiddleware::ParseYaml](https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/response/parse_yaml.rb) | [`safe_yaml`](https://rubygems.org/gems/safe_yaml) | Not backwards compatible with versions of this middleware prior to `faraday_middleware` v0.12. See code comments for alternatives. | | [FaradayMiddleware::Mashify](https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/response/mashify.rb) | [`hashie`](https://rubygems.org/gems/hashie) | | | [FaradayMiddleware::Rashify](https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/response/rashify.rb) | [`rash_alt`](https://rubygems.org/gems/rash_alt) | Make sure to uninstall original `rash` gem to avoid conflict. | Examples -------- ``` rb require 'faraday_middleware' connection = Faraday.new 'http://example.com/api' do |conn| conn.request :oauth2, 'TOKEN' conn.request :json conn.response :xml, content_type: /\bxml$/ conn.response :json, content_type: /\bjson$/ conn.use :instrumentation conn.adapter Faraday.default_adapter end ``` [faraday]: https://github.com/lostisland/faraday#readme [docs]: https://github.com/lostisland/faraday_middleware/wiki faraday_middleware-1.0.0/Rakefile000066400000000000000000000006471364007245400167750ustar00rootroot00000000000000# frozen_string_literal: true ruby_mri = !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby' default_gemfile = ENV['BUNDLE_GEMFILE'] =~ /Gemfile$/ if ruby_mri && default_gemfile task default: %i[enable_coverage spec] else task default: [:spec] end require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task :enable_coverage do ENV['COVERAGE'] = 'yes' end faraday_middleware-1.0.0/docs/000077500000000000000000000000001364007245400162515ustar00rootroot00000000000000faraday_middleware-1.0.0/docs/caching_responses.md000066400000000000000000000033231364007245400222710ustar00rootroot00000000000000# Caching responses ## Simple response body caching FaradayMiddleware::Caching can be configured with a cache store that responds to `read`, `write` and `fetch`, such as one of ActiveSupport::Cache stores. Example use: ```rb cache_dir = File.join(ENV['TMPDIR'] || '/tmp', 'cache') conn.response :caching, :ignore_params => %w[access_token] do ActiveSupport::Cache::FileStore.new cache_dir, :namespace => 'my_namespace', :expires_in => 3600 # one hour end ``` In the above example, the return value of the block represents the cache store that the middleware will use. It's configured to cache each GET response for 1 hour. ## Advanced HTTP caching FaradayMiddleware::RackCompatible can be used to mount [rack-cache][] to the middleware stack in order to perform caching per HTTP spec. ```rb conn.use FaradayMiddleware::RackCompatible, Rack::Cache::Context, :metastore => "file:#{cache_dir}/rack/meta", :entitystore => "file:#{cache_dir}/rack/body", :ignore_headers => %w[Set-Cookie X-Content-Digest] ``` In the above example, the stack is configured to cache successful responses to disk according to HTTP freshness/expiration information, and subsequent requests will be validated using information in Last-Modified/ETag headers. The `:ignore_headers` option is important to enable caching even if the server where the data is coming from uses Rack::Cache, too. This is due to [rack-cache issue #59][bug]. **Using RackCompatible middleware to mount Rack::Cache is kind of a hack**. Consider using [faraday-http-cache] instead. [rack-cache]: http://rtomayko.github.com/rack-cache/ [bug]: https://github.com/rtomayko/rack-cache/issues/59 [faraday-http-cache]: https://github.com/plataformatec/faraday-http-cache faraday_middleware-1.0.0/docs/gzip.md000066400000000000000000000006521364007245400175470ustar00rootroot00000000000000# Gzip Compression Use `FaradayMiddleware::Gzip` to automatically decompress response bodies. If the "Accept-Encoding" header wasn't set in the request, this sets it to "gzip,deflate" and appropriately handles the compressed response from the server. This resembles what Ruby does internally in Net::HTTP#get. This middleware is NOT necessary when these adapters are used: - `net_http` - `net_http_persistent` - `em_http` faraday_middleware-1.0.0/docs/index.md000066400000000000000000000026631364007245400177110ustar00rootroot00000000000000# faraday_middleware documentation This is a collection of middleware for the [Faraday][] project. Example use: ```rb require 'faraday_middleware' connection = Faraday.new 'http://example.com/api' do |conn| conn.request :oauth2, 'TOKEN' conn.request :json conn.response :xml, :content_type => /\bxml$/ conn.response :json, :content_type => /\bjson$/ conn.use :instrumentation conn.adapter Faraday.default_adapter end ``` **Important:** same as with Rack middleware, the order of middleware on a Faraday stack is significant. General guidelines: 1. put request middleware first, in order of importance; 2. put response middleware second, in the reverse order of importance; 3. ensure that the adapter is always last. ## Request middleware: * FaradayMiddleware::EncodeJson * FaradayMiddleware::OAuth * FaradayMiddleware::OAuth2 * [[FaradayMiddleware::MethodOverride|method override]] ## Response middleware: * [[Parsing responses]]: * FaradayMiddleware::ParseJson * FaradayMiddleware::ParseXml * FaradayMiddleware::ParseYaml * FaradayMiddleware::ParseMarshal * [[FaradayMiddleware::Caching|Caching]] * FaradayMiddleware::FollowRedirects * FaradayMiddleware::Mashify * FaradayMiddleware::Rashify ## Other middleware: * [[FaradayMiddleware::Instrumentation|Instrumentation]] * [[FaradayMiddleware::RackCompatible|Caching]] * [[FaradayMiddleware::Gzip|Gzip Compression]] [faraday]: https://github.com/lostisland/faraday#readme faraday_middleware-1.0.0/docs/instrumentation.md000066400000000000000000000012411364007245400220340ustar00rootroot00000000000000# Instrumenting requests with Active Support FaradayMiddleware::Instrumentation uses Active Support to instrument requests. It records information about each request and time spent performing it. ```rb conn.use :instrumentation ``` The default key under which all requests are instrumented is "request.faraday". You can subscribe to these events and log them accordingly: ```rb ActiveSupport::Notifications.subscribe('request.faraday') do |name, start_time, end_time, _, env| url = env[:url] http_method = env[:method].to_s.upcase duration = end_time - start_time $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration] end ``` faraday_middleware-1.0.0/docs/method_override.md000066400000000000000000000014241364007245400217530ustar00rootroot00000000000000# Method override Changes the request method to POST and writes the original HTTP method to the "X-Http-Method-Override" header. This can be used to work around technical issues with making non-POST requests, e.g. a faulty HTTP client or server router. This header is recognized in Rack apps by default, courtesy of the [Rack::MethodOverride](https://www.rubydoc.info/github/rack/rack/Rack/MethodOverride) module. ```rb connection = Faraday.new 'http://example.com/api' do |conn| # rewrite all non-GET/POST requests: conn.request :method_override # rewrite just PATCH and OPTIONS requests: conn.request :method_override, rewrite: [:patch, :options] end connection.patch('users/12', payload) #=> sends the request as POST, but with "X-Http-Method-Override: PATCH" header ``` faraday_middleware-1.0.0/docs/oauth.md000066400000000000000000000017211364007245400177140ustar00rootroot00000000000000# OAuth Middleware for token authentication Available middleware: * FaradayMiddleware::OAuth * FaradayMiddleware::OAuth2 Example use (OAuth 2): ```rb connection = Faraday.new('http://example.com/api') do |conn| conn.request :oauth2, 'token' conn.adapter Faraday.default_adapter end ``` This will cause the 'token' to be inserted in both the query params (as `access_token`) and headers (as `Token token=`). As of FaradayMiddleware 0.11, you can specify the `token_type` option as `:bearer`: ```rb connection = Faraday.new('http://example.com/api') do |conn| conn.request :oauth2, 'token', token_type: :bearer conn.adapter Faraday.default_adapter end ``` This will cause the token to be inserted ONLY as a header (as `Bearer `), which is more standard-compliant. ## DEPRECATION WARNING Inserting the token as a parameter is now considered a security issue, therefore the next major release of Faraday will only add tokens on headers. faraday_middleware-1.0.0/faraday_middleware.gemspec000066400000000000000000000013011364007245400224650ustar00rootroot00000000000000# frozen_string_literal: true lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'faraday_middleware/version' Gem::Specification.new do |spec| spec.name = 'faraday_middleware' spec.version = FaradayMiddleware::VERSION spec.summary = 'Various middleware for Faraday' spec.authors = ['Erik Michaels-Ober', 'Wynn Netherland'] spec.email = ['sferik@gmail.com', 'wynn.netherland@gmail.com'] spec.homepage = 'https://github.com/lostisland/faraday_middleware' spec.licenses = ['MIT'] spec.required_ruby_version = '>= 2.3' spec.add_dependency 'faraday', '~> 1.0' spec.files = `git ls-files -z lib LICENSE.md README.md`.split("\0") end faraday_middleware-1.0.0/lib/000077500000000000000000000000001364007245400160675ustar00rootroot00000000000000faraday_middleware-1.0.0/lib/faraday_middleware.rb000066400000000000000000000041671364007245400222300ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' # Main FaradayMiddleware module. module FaradayMiddleware autoload :OAuth, 'faraday_middleware/request/oauth' autoload :OAuth2, 'faraday_middleware/request/oauth2' autoload :EncodeJson, 'faraday_middleware/request/encode_json' autoload :MethodOverride, 'faraday_middleware/request/method_override' autoload :Mashify, 'faraday_middleware/response/mashify' autoload :Rashify, 'faraday_middleware/response/rashify' autoload :ParseJson, 'faraday_middleware/response/parse_json' autoload :ParseXml, 'faraday_middleware/response/parse_xml' autoload :ParseMarshal, 'faraday_middleware/response/parse_marshal' autoload :ParseYaml, 'faraday_middleware/response/parse_yaml' autoload :ParseDates, 'faraday_middleware/response/parse_dates' autoload :Caching, 'faraday_middleware/response/caching' autoload :Chunked, 'faraday_middleware/response/chunked' autoload :RackCompatible, 'faraday_middleware/rack_compatible' autoload :RedirectLimitReached, 'faraday_middleware/redirect_limit_reached' autoload :FollowRedirects, 'faraday_middleware/response/follow_redirects' autoload :Instrumentation, 'faraday_middleware/instrumentation' autoload :Gzip, 'faraday_middleware/gzip' if Faraday::Middleware.respond_to? :register_middleware Faraday::Request.register_middleware \ oauth: -> { OAuth }, oauth2: -> { OAuth2 }, json: -> { EncodeJson }, method_override: -> { MethodOverride } Faraday::Response.register_middleware \ mashify: -> { Mashify }, rashify: -> { Rashify }, json: -> { ParseJson }, json_fix: -> { ParseJson::MimeTypeFix }, xml: -> { ParseXml }, marshal: -> { ParseMarshal }, yaml: -> { ParseYaml }, dates: -> { ParseDates }, caching: -> { Caching }, follow_redirects: -> { FollowRedirects }, chunked: -> { Chunked } Faraday::Middleware.register_middleware \ instrumentation: -> { Instrumentation }, gzip: -> { Gzip } end end require 'faraday_middleware/backwards_compatibility' faraday_middleware-1.0.0/lib/faraday_middleware/000077500000000000000000000000001364007245400216735ustar00rootroot00000000000000faraday_middleware-1.0.0/lib/faraday_middleware/backwards_compatibility.rb000066400000000000000000000013171364007245400271140ustar00rootroot00000000000000# frozen_string_literal: true module Faraday # Autoload classes for Faraday::Request. class Request autoload :OAuth, 'faraday_middleware/request/oauth' autoload :OAuth2, 'faraday_middleware/request/oauth2' end # Autoload classes for Faraday::Request. class Response autoload :Mashify, 'faraday_middleware/response/mashify' autoload :Rashify, 'faraday_middleware/response/rashify' autoload :ParseJson, 'faraday_middleware/response/parse_json' autoload :ParseXml, 'faraday_middleware/response/parse_xml' autoload :ParseMarshal, 'faraday_middleware/response/parse_marshal' autoload :ParseYaml, 'faraday_middleware/response/parse_yaml' end end faraday_middleware-1.0.0/lib/faraday_middleware/gzip.rb000066400000000000000000000047631364007245400232030ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' module FaradayMiddleware # Middleware to automatically decompress response bodies. If the # "Accept-Encoding" header wasn't set in the request, this sets it to # "gzip,deflate" and appropriately handles the compressed response from the # server. This resembles what Ruby 1.9+ does internally in Net::HTTP#get. # # This middleware is NOT necessary when these adapters are used: # - net_http on Ruby 1.9+ # - net_http_persistent on Ruby 2.0+ # - em_http class Gzip < Faraday::Middleware dependency 'zlib' def self.optional_dependency(lib = nil) lib ? require(lib) : yield true rescue LoadError, NameError false end BROTLI_SUPPORTED = optional_dependency 'brotli' def self.supported_encodings encodings = %w[gzip deflate] encodings << 'br' if BROTLI_SUPPORTED encodings end ACCEPT_ENCODING = 'Accept-Encoding' CONTENT_ENCODING = 'Content-Encoding' CONTENT_LENGTH = 'Content-Length' SUPPORTED_ENCODINGS = supported_encodings.join(',').freeze def call(env) env[:request_headers][ACCEPT_ENCODING] ||= SUPPORTED_ENCODINGS @app.call(env).on_complete do |response_env| if response_env[:body].empty? reset_body(response_env, &method(:raw_body)) else case response_env[:response_headers][CONTENT_ENCODING] when 'gzip' reset_body(response_env, &method(:uncompress_gzip)) when 'deflate' reset_body(response_env, &method(:inflate)) when 'br' reset_body(response_env, &method(:brotli_inflate)) end end end end def reset_body(env) env[:body] = yield(env[:body]) env[:response_headers].delete(CONTENT_ENCODING) env[:response_headers][CONTENT_LENGTH] = env[:body].length end def uncompress_gzip(body) io = StringIO.new(body) gzip_reader = Zlib::GzipReader.new(io, encoding: 'ASCII-8BIT') gzip_reader.read end def inflate(body) # Inflate as a DEFLATE (RFC 1950+RFC 1951) stream Zlib::Inflate.inflate(body) rescue Zlib::DataError # Fall back to inflating as a "raw" deflate stream which # Microsoft servers return inflate = Zlib::Inflate.new(-Zlib::MAX_WBITS) begin inflate.inflate(body) ensure inflate.close end end def brotli_inflate(body) Brotli.inflate(body) end def raw_body(body) body end end end faraday_middleware-1.0.0/lib/faraday_middleware/instrumentation.rb000066400000000000000000000017561364007245400254740ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' module FaradayMiddleware # Public: Instruments requests using Active Support. # # Measures time spent only for synchronous requests. # # Examples # # ActiveSupport::Notifications. # subscribe('request.faraday') do |name, starts, ends, _, env| # url = env[:url] # http_method = env[:method].to_s.upcase # duration = ends - starts # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, # http_method, # url.request_uri, # duration] # end class Instrumentation < Faraday::Middleware dependency 'active_support/notifications' def initialize(app, options = {}) super(app) @name = options.fetch(:name, 'request.faraday') end def call(env) ::ActiveSupport::Notifications.instrument(@name, env) do @app.call(env) end end end end faraday_middleware-1.0.0/lib/faraday_middleware/rack_compatible.rb000066400000000000000000000053751364007245400253510ustar00rootroot00000000000000# frozen_string_literal: true require 'stringio' module FaradayMiddleware # Wraps a handler originally written for Rack for Faraday compatibility. # # Experimental. Only handles changes in request headers. class RackCompatible def initialize(app, rack_handler, *args) # tiny middleware that decomposes a Faraday::Response to standard Rack # array: [status, headers, body] compatible_app = lambda do |rack_env| env = restore_env(rack_env) response = app.call(env) [response.status, response.headers, Array(response.body)] end @rack = rack_handler.new(compatible_app, *args) end def call(env) rack_env = prepare_env(env) rack_response = @rack.call(rack_env) finalize_response(env, rack_response) end NON_PREFIXED_HEADERS = %w[CONTENT_LENGTH CONTENT_TYPE].freeze # faraday to rack-compatible def prepare_env(faraday_env) env = headers_to_rack(faraday_env) url = faraday_env[:url] env['rack.url_scheme'] = url.scheme env['PATH_INFO'] = url.path env['SERVER_PORT'] = if url.respond_to?(:inferred_port) url.inferred_port else url.port end env['QUERY_STRING'] = url.query env['REQUEST_METHOD'] = faraday_env[:method].to_s.upcase env['rack.errors'] ||= StringIO.new env['faraday'] = faraday_env env end def headers_to_rack(env) rack_env = {} env[:request_headers].each do |name, value| name = name.upcase.tr('-', '_') name = "HTTP_#{name}" unless NON_PREFIXED_HEADERS.include? name rack_env[name] = value end rack_env end # rack to faraday-compatible def restore_env(rack_env) env = rack_env.fetch('faraday') headers = env[:request_headers] headers.clear rack_env.each do |name, value| next unless name.is_a?(String) && value.is_a?(String) if NON_PREFIXED_HEADERS.include?(name) || name.start_with?('HTTP_') name = name.sub(/^HTTP_/, '').downcase.tr('_', '-') headers[name] = value end end env[:method] = rack_env['REQUEST_METHOD'].downcase.to_sym env[:rack_errors] = rack_env['rack.errors'] env end def finalize_response(env, rack_response) status, headers, body = rack_response body = body.inject { |str, part| str << part } unless headers.is_a?(Faraday::Utils::Headers) headers = Faraday::Utils::Headers.new(headers) end env.update status: status.to_i, body: body, response_headers: headers env[:response] ||= Faraday::Response.new(env) env[:response] end end end faraday_middleware-1.0.0/lib/faraday_middleware/redirect_limit_reached.rb000066400000000000000000000005651364007245400267000ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' module FaradayMiddleware # Exception thrown when the maximum amount of requests is # exceeded. class RedirectLimitReached < Faraday::ClientError attr_reader :response def initialize(response) super "too many redirects; last one to: #{response['location']}" @response = response end end end faraday_middleware-1.0.0/lib/faraday_middleware/request/000077500000000000000000000000001364007245400233635ustar00rootroot00000000000000faraday_middleware-1.0.0/lib/faraday_middleware/request/encode_json.rb000066400000000000000000000026271364007245400262050ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' module FaradayMiddleware # Request middleware that encodes the body as JSON. # # Processes only requests with matching Content-type or those without a type. # If a request doesn't have a type but has a body, it sets the Content-type # to JSON MIME-type. # # Doesn't try to encode bodies that already are in string form. class EncodeJson < Faraday::Middleware CONTENT_TYPE = 'Content-Type' MIME_TYPE = 'application/json' MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}.freeze dependency do require 'json' unless defined?(::JSON) end def call(env) match_content_type(env) do |data| env[:body] = encode data end @app.call env end def encode(data) ::JSON.dump data end def match_content_type(env) return unless process_request?(env) env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE yield env[:body] unless env[:body].respond_to?(:to_str) end def process_request?(env) type = request_type(env) has_body?(env) && (type.empty? || MIME_TYPE_REGEX =~ type) end def has_body?(env) (body = env[:body]) && !(body.respond_to?(:to_str) && body.empty?) end def request_type(env) type = env[:request_headers][CONTENT_TYPE].to_s type = type.split(';', 2).first if type.index(';') type end end end faraday_middleware-1.0.0/lib/faraday_middleware/request/method_override.rb000066400000000000000000000031011364007245400270620ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' module FaradayMiddleware # Public: Writes the original HTTP method to "X-Http-Method-Override" header # and sends the request as POST. # # This can be used to work around technical issues with making non-POST # requests, e.g. faulty HTTP client or server router. # # This header is recognized in Rack apps by default, courtesy of the # Rack::MethodOverride module. See # http://rack.rubyforge.org/doc/classes/Rack/MethodOverride.html class MethodOverride < Faraday::Middleware HEADER = 'X-Http-Method-Override' # Public: Initialize the middleware. # # app - the Faraday app to wrap # options - (optional) # :rewrite - Array of HTTP methods to rewrite # (default: all but GET and POST) def initialize(app, options = nil) super(app) @methods = options&.fetch(:rewrite)&.map do |method| method = method.downcase if method.respond_to? :downcase method.to_sym end end def call(env) method = env[:method] rewrite_request(env, method) if rewrite_request?(method) @app.call(env) end def rewrite_request?(method) if @methods.nil? || @methods.empty? (method != :get) && (method != :post) else @methods.include? method end end # Internal: Write the original HTTP method to header, change method to POST. def rewrite_request(env, original_method) env[:request_headers][HEADER] = original_method.to_s.upcase env[:method] = :post end end end faraday_middleware-1.0.0/lib/faraday_middleware/request/oauth.rb000066400000000000000000000051211364007245400250270ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' require 'forwardable' module FaradayMiddleware # Public: Uses the simple_oauth library to sign requests according the # OAuth protocol. # # The options for this middleware are forwarded to SimpleOAuth::Header: # :consumer_key, :consumer_secret, :token, :token_secret. All these # parameters are optional. # # The signature is added to the "Authorization" HTTP request header. If the # value for this header already exists, it is not overriden. # # If no Content-Type header is specified, this middleware assumes that # request body parameters should be included while signing the request. # Otherwise, it only includes them if the Content-Type is # "application/x-www-form-urlencoded", as per OAuth 1.0. # # For better performance while signing requests, this middleware should be # positioned before UrlEncoded middleware on the stack, but after any other # body-encoding middleware (such as EncodeJson). class OAuth < Faraday::Middleware dependency 'simple_oauth' AUTH_HEADER = 'Authorization' CONTENT_TYPE = 'Content-Type' TYPE_URLENCODED = 'application/x-www-form-urlencoded' extend Forwardable def_delegator :'Faraday::Utils', :parse_nested_query def initialize(app, options) super(app) @options = options end def call(env) if sign_request?(env) env[:request_headers][AUTH_HEADER] ||= oauth_header(env).to_s end @app.call(env) end def oauth_header(env) SimpleOAuth::Header.new env[:method], env[:url].to_s, signature_params(body_params(env)), oauth_options(env) end def sign_request?(env) !!env[:request].fetch(:oauth, true) end def oauth_options(env) if (extra = env[:request][:oauth]) && extra.is_a?(Hash) && !extra.empty? @options.merge extra else @options end end def body_params(env) if include_body_params?(env) if env[:body].respond_to?(:to_str) parse_nested_query env[:body] else env[:body] end end || {} end def include_body_params?(env) # see RFC 5849, section 3.4.1.3.1 for details !(type = env[:request_headers][CONTENT_TYPE]) || (type == TYPE_URLENCODED) end def signature_params(params) if params.empty? params else params.reject { |_k, v| v.respond_to?(:content_type) } end end end end # deprecated alias Faraday::Request::OAuth = FaradayMiddleware::OAuth faraday_middleware-1.0.0/lib/faraday_middleware/request/oauth2.rb000066400000000000000000000053771364007245400251260ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' require 'forwardable' module FaradayMiddleware # Public: A simple middleware that adds an access token to each request. # # By default, the token is added as both "access_token" query parameter # and the "Authorization" HTTP request header. It can alternatively be # added exclusively as a bearer token "Authorization" header by specifying # a "token_type" option of "bearer". However, an explicit "access_token" # parameter or "Authorization" header for the current request are not # overriden. # # Examples # # # configure default token: # OAuth2.new(app, 'abc123') # # # configure query parameter name: # OAuth2.new(app, 'abc123', :param_name => 'my_oauth_token') # # # use bearer token authorization header only # OAuth2.new(app, 'abc123', :token_type => 'bearer') # # # default token value is optional: # OAuth2.new(app, :param_name => 'my_oauth_token') class OAuth2 < Faraday::Middleware PARAM_NAME = 'access_token' TOKEN_TYPE = 'param' AUTH_HEADER = 'Authorization' attr_reader :param_name, :token_type extend Forwardable def_delegators :'Faraday::Utils', :parse_query, :build_query def call(env) params = { param_name => @token }.update query_params(env[:url]) token = params[param_name] if token.respond_to?(:empty?) && !token.empty? case @token_type.downcase when 'param' env[:url].query = build_query params env[:request_headers][AUTH_HEADER] ||= %(Token token="#{token}") when 'bearer' env[:request_headers][AUTH_HEADER] ||= %(Bearer #{token}) end end @app.call env end def initialize(app, token = nil, options = {}) super(app) if token.is_a? Hash options = token token = nil end @token = token&.to_s @param_name = options.fetch(:param_name, PARAM_NAME).to_s @token_type = options.fetch(:token_type, TOKEN_TYPE).to_s if @token_type == 'param' && @param_name.empty? raise ArgumentError, ":param_name can't be blank" end return unless options[:token_type].nil? warn "\nWarning: FaradayMiddleware::OAuth2 initialized with default "\ 'token_type - token will be added as both a query string parameter '\ 'and an Authorization header. In the next major release, tokens will '\ 'be added exclusively as an Authorization header by default. Please '\ 'see https://github.com/lostisland/faraday_middleware/wiki.' end def query_params(url) if url.query.nil? || url.query.empty? {} else parse_query url.query end end end end # deprecated alias Faraday::Request::OAuth2 = FaradayMiddleware::OAuth2 faraday_middleware-1.0.0/lib/faraday_middleware/response/000077500000000000000000000000001364007245400235315ustar00rootroot00000000000000faraday_middleware-1.0.0/lib/faraday_middleware/response/caching.rb000066400000000000000000000074301364007245400254560ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' require 'forwardable' require 'digest/sha1' module FaradayMiddleware # Public: Caches GET responses and pulls subsequent ones from the cache. class Caching < Faraday::Middleware attr_reader :cache # Internal: List of status codes that can be cached: # * 200 - 'OK' # * 203 - 'Non-Authoritative Information' # * 300 - 'Multiple Choices' # * 301 - 'Moved Permanently' # * 302 - 'Found' # * 404 - 'Not Found' # * 410 - 'Gone' CACHEABLE_STATUS_CODES = [200, 203, 300, 301, 302, 404, 410].freeze extend Forwardable def_delegators :'Faraday::Utils', :parse_query, :build_query # Public: initialize the middleware. # # cache - An object that responds to read and write (default: nil). # options - An options Hash (default: {}): # :ignore_params - String name or Array names of query # params that should be ignored when forming # the cache key (default: []). # :write_options - Hash of settings that should be passed as the # third options parameter to the cache's #write # method. If not specified, no options parameter # will be passed. # :full_key - Boolean - use full URL as cache key: # (url.host + url.request_uri) # # Yields if no cache is given. The block should return a cache object. def initialize(app, cache = nil, options = {}) super(app) if cache.is_a?(Hash) && block_given? options = cache cache = nil end @cache = cache || yield @options = options end def call(env) if env[:method] == :get if env[:parallel_manager] # callback mode cache_on_complete(env) else # synchronous mode key = cache_key(env) unless (response = cache.read(key)) && response response = @app.call(env) store_response_in_cache(key, response) end finalize_response(response, env) end else @app.call(env) end end def cache_key(env) url = env[:url].dup if url.query && params_to_ignore.any? params = parse_query url.query params.reject! { |k,| params_to_ignore.include? k } url.query = params.any? ? build_query(params) : nil end url.normalize! digest = full_key? ? url.host + url.request_uri : url.request_uri Digest::SHA1.hexdigest(digest) end def params_to_ignore @params_to_ignore ||= Array(@options[:ignore_params]).map(&:to_s) end def full_key? @full_key ||= @options[:full_key] end def cache_on_complete(env) key = cache_key(env) if (cached_response = cache.read(key)) finalize_response(cached_response, env) else # response.status is nil at this point # any checks need to be done inside on_complete block @app.call(env).on_complete do |response_env| store_response_in_cache(key, response_env.response) response_env end end end def store_response_in_cache(key, response) return unless CACHEABLE_STATUS_CODES.include?(response.status) if @options[:write_options] cache.write(key, response, @options[:write_options]) else cache.write(key, response) end end def finalize_response(response, env) response = response.dup if response.frozen? env[:response] = response unless env[:response_headers] env.update response.env # FIXME: omg hax response.instance_variable_set('@env', env) end response end end end faraday_middleware-1.0.0/lib/faraday_middleware/response/chunked.rb000066400000000000000000000017461364007245400255070ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday_middleware/response_middleware' module FaradayMiddleware # Public: Parse a Transfer-Encoding. Chunks response to just the original data class Chunked < FaradayMiddleware::ResponseMiddleware TRANSFER_ENCODING = 'transfer-encoding' define_parser do |raw_body| decoded_body = [] until raw_body.empty? chunk_len, raw_body = raw_body.split("\r\n", 2) chunk_len = chunk_len.split(';', 2).first.hex break if chunk_len.zero? decoded_body << raw_body[0, chunk_len] # The 2 is to strip the extra CRLF at the end of the chunk raw_body = raw_body[chunk_len + 2, raw_body.length - chunk_len - 2] end decoded_body.join('') end def parse_response?(env) super && chunked_encoding?(env[:response_headers]) end def chunked_encoding?(headers) (encoding = headers[TRANSFER_ENCODING]) && encoding.split(',').include?('chunked') end end end faraday_middleware-1.0.0/lib/faraday_middleware/response/follow_redirects.rb000066400000000000000000000125561364007245400274350ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' require 'set' module FaradayMiddleware # Public: Follow HTTP 301, 302, 303, 307, and 308 redirects. # # For HTTP 301, 302, and 303, the original GET, POST, PUT, DELETE, or PATCH # request gets converted into a GET. With `:standards_compliant => true`, # however, the HTTP method after 301/302 remains unchanged. This allows you # to opt into HTTP/1.1 compliance and act unlike the major web browsers. # # This middleware currently only works with synchronous requests; i.e. it # doesn't support parallelism. # # If you wish to persist cookies across redirects, you could use # the faraday-cookie_jar gem: # # Faraday.new(:url => url) do |faraday| # faraday.use FaradayMiddleware::FollowRedirects # faraday.use :cookie_jar # faraday.adapter Faraday.default_adapter # end class FollowRedirects < Faraday::Middleware # HTTP methods for which 30x redirects can be followed ALLOWED_METHODS = Set.new %i[head options get post put patch delete] # HTTP redirect status codes that this middleware implements REDIRECT_CODES = Set.new [301, 302, 303, 307, 308] # Keys in env hash which will get cleared between requests ENV_TO_CLEAR = Set.new %i[status response response_headers] # Default value for max redirects followed FOLLOW_LIMIT = 3 # Regex that matches characters that need to be escaped in URLs, sans # the "%" character which we assume already represents an escaped sequence. URI_UNSAFE = %r{[^\-_.!~*'()a-zA-Z\d;/?:@&=+$,\[\]%]}.freeze AUTH_HEADER = 'Authorization' # Public: Initialize the middleware. # # options - An options Hash (default: {}): # :limit - A Numeric redirect limit (default: 3) # :standards_compliant - A Boolean indicating whether to respect # the HTTP spec when following 301/302 # (default: false) # :callback - A callable used on redirects # with the old and new envs # :cookies - An Array of Strings (e.g. # ['cookie1', 'cookie2']) to choose # cookies to be kept, or :all to keep # all cookies (default: []). # :clear_authorization_header - A Boolean indicating whether the request # Authorization header should be cleared on # redirects (default: true) def initialize(app, options = {}) super(app) @options = options @convert_to_get = Set.new [303] @convert_to_get << 301 << 302 unless standards_compliant? end def call(env) perform_with_redirection(env, follow_limit) end private def convert_to_get?(response) !%i[head options].include?(response.env[:method]) && @convert_to_get.include?(response.status) end def perform_with_redirection(env, follows) request_body = env[:body] response = @app.call(env) response.on_complete do |response_env| if follow_redirect?(response_env, response) raise RedirectLimitReached, response if follows.zero? new_request_env = update_env(response_env.dup, request_body, response) callback&.call(response_env, new_request_env) response = perform_with_redirection(new_request_env, follows - 1) end end response end def update_env(env, request_body, response) redirect_from_url = env[:url].to_s redirect_to_url = safe_escape(response['location'] || '') env[:url] += redirect_to_url ENV_TO_CLEAR.each { |key| env.delete key } if convert_to_get?(response) env[:method] = :get env[:body] = nil else env[:body] = request_body end clear_authorization_header(env, redirect_from_url, redirect_to_url) env end def follow_redirect?(env, response) ALLOWED_METHODS.include?(env[:method]) && REDIRECT_CODES.include?(response.status) end def follow_limit @options.fetch(:limit, FOLLOW_LIMIT) end def standards_compliant? @options.fetch(:standards_compliant, false) end def callback @options[:callback] end # Internal: escapes unsafe characters from an URL which might be a path # component only or a fully qualified URI so that it can be joined onto an # URI:HTTP using the `+` operator. Doesn't escape "%" characters so to not # risk double-escaping. def safe_escape(uri) uri = uri.split('#')[0] # we want to remove the fragment if present uri.to_s.gsub(URI_UNSAFE) do |match| '%' + match.unpack('H2' * match.bytesize).join('%').upcase end end def clear_authorization_header(env, from_url, to_url) return env if redirect_to_same_host?(from_url, to_url) return env unless @options.fetch(:clear_authorization_header, true) env[:request_headers].delete(AUTH_HEADER) end def redirect_to_same_host?(from_url, to_url) return true if to_url.start_with?('/') from_uri = URI.parse(from_url) to_uri = URI.parse(to_url) [from_uri.scheme, from_uri.host, from_uri.port] == [to_uri.scheme, to_uri.host, to_uri.port] end end end faraday_middleware-1.0.0/lib/faraday_middleware/response/mashify.rb000066400000000000000000000014571364007245400255250ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' module FaradayMiddleware # Public: Converts parsed response bodies to a Hashie::Mash if they were of # Hash or Array type. class Mashify < Faraday::Response::Middleware attr_accessor :mash_class class << self attr_accessor :mash_class end dependency do require 'hashie/mash' self.mash_class = ::Hashie::Mash end def initialize(app = nil, options = {}) super(app) self.mash_class = options[:mash_class] || self.class.mash_class end def parse(body) case body when Hash mash_class.new(body) when Array body.map { |item| parse(item) } else body end end end end # deprecated alias Faraday::Response::Mashify = FaradayMiddleware::Mashify faraday_middleware-1.0.0/lib/faraday_middleware/response/parse_dates.rb000066400000000000000000000016151364007245400263530ustar00rootroot00000000000000# frozen_string_literal: true require 'time' require 'faraday' module FaradayMiddleware # Parse dates from response body class ParseDates < ::Faraday::Response::Middleware ISO_DATE_FORMAT = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)? (Z|((\+|-)\d{2}:?\d{2}))\Z/xm.freeze def initialize(app, options = {}) @regexp = options[:match] || ISO_DATE_FORMAT super(app) end def call(env) response = @app.call(env) parse_dates! response.env[:body] response end private def parse_dates!(value) case value when Hash value.each do |key, element| value[key] = parse_dates!(element) end when Array value.each_with_index do |element, index| value[index] = parse_dates!(element) end when @regexp Time.parse(value) else value end end end end faraday_middleware-1.0.0/lib/faraday_middleware/response/parse_json.rb000066400000000000000000000027451364007245400262310ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday_middleware/response_middleware' module FaradayMiddleware # Public: Parse response bodies as JSON. class ParseJson < ResponseMiddleware dependency do require 'json' unless defined?(::JSON) end define_parser do |body, parser_options| ::JSON.parse(body, parser_options || {}) unless body.strip.empty? end # Public: Override the content-type of the response with "application/json" # if the response body looks like it might be JSON, i.e. starts with an # open bracket. # # This is to fix responses from certain API providers that insist on serving # JSON with wrong MIME-types such as "text/javascript". class MimeTypeFix < ResponseMiddleware MIME_TYPE = 'application/json' def process_response(env) old_type = env[:response_headers][CONTENT_TYPE].to_s new_type = MIME_TYPE.dup new_type << ';' << old_type.split(';', 2).last if old_type.index(';') env[:response_headers][CONTENT_TYPE] = new_type end BRACKETS = %w-[ {-.freeze WHITESPACE = [' ', "\n", "\r", "\t"].freeze def parse_response?(env) super && BRACKETS.include?(first_char(env[:body])) end def first_char(body) idx = -1 char = body[idx += 1] char = body[idx += 1] while char && WHITESPACE.include?(char) char end end end end # deprecated alias Faraday::Response::ParseJson = FaradayMiddleware::ParseJson faraday_middleware-1.0.0/lib/faraday_middleware/response/parse_marshal.rb000066400000000000000000000006061364007245400267010ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday_middleware/response_middleware' module FaradayMiddleware # Public: Restore marshalled Ruby objects in response bodies. class ParseMarshal < ResponseMiddleware define_parser do |body| ::Marshal.load(body) unless body.empty? end end end # deprecated alias Faraday::Response::ParseMarshal = FaradayMiddleware::ParseMarshal faraday_middleware-1.0.0/lib/faraday_middleware/response/parse_xml.rb000066400000000000000000000006351364007245400260540ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday_middleware/response_middleware' module FaradayMiddleware # Public: parses response bodies with MultiXml. class ParseXml < ResponseMiddleware dependency 'multi_xml' define_parser do |body, parser_options| ::MultiXml.parse(body, parser_options || {}) end end end # deprecated alias Faraday::Response::ParseXml = FaradayMiddleware::ParseXml faraday_middleware-1.0.0/lib/faraday_middleware/response/parse_yaml.rb000066400000000000000000000022401364007245400262100ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday_middleware/response_middleware' module FaradayMiddleware # Public: Parse response bodies as YAML. # # Warning: This is not backwards compatible with versions of this middleware # prior to faraday_middleware v0.12 - prior to this version, we used # YAML.load rather than YAMl.safe_load, which exposes serious remote code # execution risks - see https://github.com/ruby/psych/issues/119 for details. # If you're sure you can trust YAML you're passing, you can set up an unsafe # version of this middleware like this: # # class UnsafelyParseYaml < FaradayMiddleware::ResponseMiddleware # dependency do # require 'yaml' # end # # define_parser do |body| # YAML.load body # end # end # # Faraday.new(..) do |config| # config.use UnsafelyParseYaml # ... # end class ParseYaml < ResponseMiddleware dependency 'safe_yaml/load' define_parser do |body, parser_options| SafeYAML.load(body, nil, parser_options || {}) end end end # deprecated alias Faraday::Response::ParseYaml = FaradayMiddleware::ParseYaml faraday_middleware-1.0.0/lib/faraday_middleware/response/rashify.rb000066400000000000000000000006311364007245400255230ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday_middleware/response/mashify' module FaradayMiddleware # Public: Converts parsed response bodies to a Hashie::Rash if they were of # Hash or Array type. class Rashify < Mashify dependency do require 'rash' self.mash_class = ::Hashie::Mash::Rash end end end # deprecated alias Faraday::Response::Rashify = FaradayMiddleware::Rashify faraday_middleware-1.0.0/lib/faraday_middleware/response_middleware.rb000066400000000000000000000057071364007245400262640ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' # Main FaradayMiddleware module. module FaradayMiddleware # Internal: The base class for middleware that parses responses. class ResponseMiddleware < Faraday::Middleware CONTENT_TYPE = 'Content-Type' class << self attr_accessor :parser end # Store a Proc that receives the body and returns the parsed result. def self.define_parser(parser = nil, &block) @parser = parser || block || raise(ArgumentError, 'Define parser with a block') end def self.inherited(subclass) super subclass.load_error = load_error if subclass.respond_to? :load_error= subclass.parser = parser end def initialize(app = nil, options = {}) super(app) @options = options @parser_options = options[:parser_options] @content_types = Array(options[:content_type]) end def call(environment) @app.call(environment).on_complete do |env| if process_response_type?(response_type(env)) && parse_response?(env) process_response(env) end end end def process_response(env) env[:raw_body] = env[:body] if preserve_raw?(env) env[:body] = parse(env[:body]) rescue Faraday::ParsingError => e raise Faraday::ParsingError.new(e, env[:response]) end # Parse the response body. # # Instead of overriding this method, consider using `define_parser`. def parse(body) if self.class.parser begin self.class.parser.call(body, @parser_options) rescue StandardError, SyntaxError => e raise e if e.is_a?(SyntaxError) && e.class.name != 'Psych::SyntaxError' raise Faraday::ParsingError, e end else body end end def response_type(env) type = env[:response_headers][CONTENT_TYPE].to_s type = type.split(';', 2).first if type.index(';') type end def process_response_type?(type) @content_types.empty? || @content_types.any? do |pattern| pattern.is_a?(Regexp) ? type =~ pattern : type == pattern end end def parse_response?(env) env[:body].respond_to? :to_str end def preserve_raw?(env) env[:request].fetch(:preserve_raw, @options[:preserve_raw]) end end # DRAGONS module OptionsExtension attr_accessor :preserve_raw def to_hash super.update(preserve_raw: preserve_raw) end def each return to_enum(:each) unless block_given? super yield :preserve_raw, preserve_raw end def fetch(key, *args) if key == :preserve_raw value = __send__(key) value.nil? ? args.fetch(0) : value else super end end end if defined?(Faraday::RequestOptions) begin Faraday::RequestOptions.from(preserve_raw: true) rescue NoMethodError Faraday::RequestOptions.include OptionsExtension end end end faraday_middleware-1.0.0/lib/faraday_middleware/version.rb000066400000000000000000000002351364007245400237050ustar00rootroot00000000000000# frozen_string_literal: true # Main FaradayMiddleware module. module FaradayMiddleware VERSION = '1.0.0' unless defined?(FaradayMiddleware::VERSION) end faraday_middleware-1.0.0/script/000077500000000000000000000000001364007245400166255ustar00rootroot00000000000000faraday_middleware-1.0.0/script/bootstrap000077500000000000000000000001141364007245400205640ustar00rootroot00000000000000#!/bin/bash set -e bundle install --path vendor/bundle bundle binstub rake faraday_middleware-1.0.0/script/console000077500000000000000000000002541364007245400202160ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/console # Starts an IRB console with this library loaded. gemspec="$(ls *.gemspec | head -1)" exec bundle exec irb -r "${gemspec%.*}" faraday_middleware-1.0.0/script/package000077500000000000000000000002301364007245400201410ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/gem # Updates the gemspec and builds a new gem in the pkg directory. mkdir -p pkg gem build *.gemspec mv *.gem pkg faraday_middleware-1.0.0/script/release000077500000000000000000000006041364007245400201730ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/release # Build the package, tag a commit, push it to origin, and then release the # package publicly. set -e version="$(script/package | grep Version: | awk '{print $2}')" [ -n "$version" ] || exit 1 git commit --allow-empty -a -m "faraday_middleware $version" git tag "v${version}" git push origin HEAD "v${version}" gem push pkg/*-${version}.gem faraday_middleware-1.0.0/spec/000077500000000000000000000000001364007245400162535ustar00rootroot00000000000000faraday_middleware-1.0.0/spec/integration/000077500000000000000000000000001364007245400205765ustar00rootroot00000000000000faraday_middleware-1.0.0/spec/integration/follow_redirects_spec.rb000066400000000000000000000047701364007245400255130ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::FollowRedirects do it 'redirects on 301' do stub_request(:get, 'http://www.site-a.com/').to_return( status: 301, headers: { 'Location' => 'https://www.site-b.com/' } ) stub_request(:get, 'https://www.site-b.com/') connection = Faraday.new do |conn| conn.use FaradayMiddleware::FollowRedirects conn.adapter Faraday.default_adapter end response = connection.get 'http://www.site-a.com' expect(response.env[:url].to_s).to eq('https://www.site-b.com/') end it 'redirects on 302' do stub_request(:get, 'http://www.site-a.com/').to_return( status: 302, headers: { 'Location' => 'https://www.site-b.com/' } ) stub_request(:get, 'https://www.site-b.com/') connection = Faraday.new do |conn| conn.use FaradayMiddleware::FollowRedirects conn.adapter Faraday.default_adapter end response = connection.get 'http://www.site-a.com' expect(response.env[:url].to_s).to eq('https://www.site-b.com/') end it 'redirects on 303' do stub_request(:get, 'http://www.site-a.com/').to_return( status: 303, headers: { 'Location' => 'https://www.site-b.com/' } ) stub_request(:get, 'https://www.site-b.com/') connection = Faraday.new do |conn| conn.use FaradayMiddleware::FollowRedirects conn.adapter Faraday.default_adapter end response = connection.get 'http://www.site-a.com' expect(response.env[:url].to_s).to eq('https://www.site-b.com/') end it 'redirects on 307' do stub_request(:get, 'http://www.site-a.com/').to_return( status: 307, headers: { 'Location' => 'https://www.site-b.com/' } ) stub_request(:get, 'https://www.site-b.com/') connection = Faraday.new do |conn| conn.use FaradayMiddleware::FollowRedirects conn.adapter Faraday.default_adapter end response = connection.get 'http://www.site-a.com' expect(response.env[:url].to_s).to eq('https://www.site-b.com/') end it 'redirects on 308' do stub_request(:get, 'http://www.site-a.com/').to_return( status: 308, headers: { 'Location' => 'https://www.site-b.com/' } ) stub_request(:get, 'https://www.site-b.com/') connection = Faraday.new do |conn| conn.use FaradayMiddleware::FollowRedirects conn.adapter Faraday.default_adapter end response = connection.get 'http://www.site-a.com' expect(response.env[:url].to_s).to eq('https://www.site-b.com/') end end faraday_middleware-1.0.0/spec/spec_helper.rb000066400000000000000000000024071364007245400210740ustar00rootroot00000000000000# frozen_string_literal: true if ENV['COVERAGE'] require 'simplecov' SimpleCov.start do # add_filter 'faraday_middleware.rb' add_filter 'backwards_compatibility.rb' end end require 'rspec' require 'webmock/rspec' require 'faraday' require 'faraday_middleware' module EnvCompatibility def faraday_env(env) if defined?(Faraday::Env) Faraday::Env.from(env) else env end end end module ResponseMiddlewareExampleGroup def self.included(base) base.let(:options) { {} } base.let(:headers) { {} } base.let(:middleware) do described_class.new(lambda { |env| Faraday::Response.new(env) }, options) end end def process(body, content_type = nil, options = {}) env = { body: body, request: options, request_headers: Faraday::Utils::Headers.new, response_headers: Faraday::Utils::Headers.new(headers) } env[:response_headers]['content-type'] = content_type if content_type yield(env) if block_given? middleware.call(faraday_env(env)) end end RSpec.configure do |config| config.include EnvCompatibility config.include ResponseMiddlewareExampleGroup, type: :response config.expect_with :rspec do |c| c.syntax = :expect end config.disable_monkey_patching! end faraday_middleware-1.0.0/spec/unit/000077500000000000000000000000001364007245400172325ustar00rootroot00000000000000faraday_middleware-1.0.0/spec/unit/caching_spec.rb000066400000000000000000000142641364007245400221740ustar00rootroot00000000000000# frozen_string_literal: true require 'rack/cache' RSpec.describe FaradayMiddleware::Caching do let(:caching_lint_middleware) do Struct.new(:app) do def call(env) app.call(env).on_complete do raise 'no headers' unless env[:response_headers].is_a? Hash raise 'no response' unless env[:response].is_a? Faraday::Response # raise "env not identical" unless env[:response].env.object_id == env.object_id end end end end before do @cache = TestCache.new request_count = 0 response = lambda { |_env| [200, { 'Content-Type' => 'text/plain' }, "request:#{request_count += 1}"] } broken = lambda { |_env| [500, { 'Content-Type' => 'text/plain' }, "request:#{request_count += 1}"] } @conn = Faraday.new do |b| b.use caching_lint_middleware b.use FaradayMiddleware::Caching, @cache, options b.adapter :test do |stub| stub.get('/', &response) stub.get('/?foo=bar', &response) stub.post('/', &response) stub.get('/other', &response) stub.get('/broken', &broken) stub.get('http://www.site-a.com/test', &response) stub.get('http://www.site-b.com/test', &response) end end end let(:options) { {} } extend Forwardable def_delegators :@conn, :get, :post it 'caches get requests' do expect(get('/').body).to eq('request:1') expect(get('/').body).to eq('request:1') expect(get('/other').body).to eq('request:2') expect(get('/other').body).to eq('request:2') end it 'includes request params in the response' do get('/') # make cache response = get('/') expect(response.env[:method]).to eq(:get) expect(response.env[:url].request_uri).to eq('/') end it 'caches requests with query params' do expect(get('/').body).to eq('request:1') expect(get('/?foo=bar').body).to eq('request:2') expect(get('/?foo=bar').body).to eq('request:2') expect(get('/').body).to eq('request:1') end it 'does not cache post requests' do expect(post('/').body).to eq('request:1') expect(post('/').body).to eq('request:2') expect(post('/').body).to eq('request:3') end it 'does not cache responses with invalid status code' do expect(get('/broken').body).to eq('request:1') expect(get('/broken').body).to eq('request:2') end context ':ignore_params' do let(:options) { { ignore_params: %w[utm_source utm_term] } } it 'strips ignored parameters from cache_key' do expect(get('/').body).to eq('request:1') expect(get('/?utm_source=a').body).to eq('request:1') expect(get('/?utm_source=a&utm_term=b').body).to eq('request:1') expect(get('/?utm_source=a&utm_term=b&foo=bar').body).to eq('request:2') expect(get('/?foo=bar').body).to eq('request:2') end end context ':full_key' do let(:options) { { full_key: true } } it 'use full URL as cache key' do expect(get('http://www.site-a.com/test').body).to eq('request:1') expect(get('http://www.site-b.com/test').body).to eq('request:2') end end context ':write_options' do let(:options) { { write_options: { expires_in: 9000 } } } it 'passes on the options when writing to the cache' do expect(@cache).to receive(:write).with(Digest::SHA1.hexdigest('/'), instance_of(Faraday::Response), options[:write_options]) get('/') end context 'with no :write_options' do let(:options) { {} } it "doesn't pass a third options parameter to the cache's #write" do expect(@cache).to receive(:write).with(Digest::SHA1.hexdigest('/'), instance_of(Faraday::Response)) get('/') end end end class TestCache < Hash def read(key) cached = self[key] return unless cached Marshal.load(cached) end def write(key, data, _options = nil) self[key] = Marshal.dump(data) end def fetch(key) read(key) || yield.tap { |data| write(key, data) } end end end # RackCompatible + Rack::Cache RSpec.describe FaradayMiddleware::RackCompatible, 'caching' do include FileUtils CACHE_DIR = File.expand_path('../tmp/cache', __dir__) let(:rack_errors_complainer_middleware) do # middleware to check whether "rack.errors" is free of error reports Struct.new(:app) do def call(env) response = app.call(env) error_stream = env[:rack_errors] if error_stream.respond_to?(:string) && error_stream.string.include?('error') raise format(%(unexpected error in 'rack.errors': %p), error: error_stream.string) end response end end end before do rm_r CACHE_DIR if File.exist? CACHE_DIR # force reinitializing cache dirs Rack::Cache::Storage.instance.clear request_count = 0 response = lambda { |_env| [200, { 'Content-Type' => 'text/plain', 'Cache-Control' => 'public, max-age=900' }, "request:#{request_count += 1}"] } @conn = Faraday.new do |b| b.use rack_errors_complainer_middleware b.use FaradayMiddleware::RackCompatible, Rack::Cache::Context, metastore: "file:#{CACHE_DIR}/rack/meta", entitystore: "file:#{CACHE_DIR}/rack/body", verbose: true b.adapter :test do |stub| stub.get('/', &response) stub.post('/', &response) end end end extend Forwardable def_delegators :@conn, :get, :post it 'caches get requests' do response = get('/', user_agent: 'test') expect(response.body).to eq('request:1') expect(response.env[:method]).to eq(:get) expect(response.status).to eq(200) response = get('/', user_agent: 'test') expect(response.body).to eq('request:1') expect(response['content-type']).to eq('text/plain') expect(response.env[:method]).to eq(:get) expect(response.env[:request].respond_to?(:fetch)).to be true expect(response.status). to eq(200) expect(post('/').body).to eq('request:2') end it 'does not cache post requests' do expect(get('/').body).to eq('request:1') expect(post('/').body).to eq('request:2') expect(post('/').body).to eq('request:3') end end faraday_middleware-1.0.0/spec/unit/chunked_spec.rb000066400000000000000000000043341364007245400222160ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::Chunked, type: :response do context 'no transfer-encoding' do it "doesn't change nil body" do expect(process(nil).body).to be_nil end it "doesn't change an empty body" do expect(process('').body).to eq('') end it "doesn't change a normal body" do expect(process('asdf').body).to eq('asdf') end end context 'transfer-encoding gzip' do let(:headers) { { 'transfer-encoding' => 'gzip' } } it "doesn't change nil body" do expect(process(nil).body).to be_nil end it "doesn't change an empty body" do expect(process('').body).to eq('') end it "doesn't change a normal body" do expect(process('asdf').body).to eq('asdf') end end context 'transfer-encoding chunked' do let(:headers) { { 'transfer-encoding' => 'chunked' } } it "doesn't change nil body" do expect(process(nil).body).to be_nil end it "doesn't change an empty body" do expect(process('').body).to eq('') end it 'parses a basic chunked body' do expect(process("10\r\nasdfghjklasdfghj\r\n0\r\n").body).to eq('asdfghjklasdfghj') end it 'parses a chunked body with no ending chunk' do expect(process("10\r\nasdfghjklasdfghj\r\n").body).to eq('asdfghjklasdfghj') end it 'parses a chunked body with no trailing CRLF on the data chunk' do expect(process("10\r\nasdfghjklasdfghj0\r\n").body).to eq('asdfghjklasdfghj') end it 'parses a chunked body with an extension' do expect(process("10;foo=bar\r\nasdfghjklasdfghj\r\n0\r\n").body).to eq('asdfghjklasdfghj') end it 'parses a chunked body with two extensions' do expect(process("10;foo=bar;bar=baz\r\nasdfghjklasdfghj\r\n0\r\n").body).to eq('asdfghjklasdfghj') end it 'parses a chunked body with two chunks' do expect(process("8\r\nasdfghjk\r\n8\r\nlasdfghj\r\n0\r\n").body).to eq('asdfghjklasdfghj') end end context 'transfer-encoding chunked,chunked' do let(:headers) { { 'transfer-encoding' => 'chunked,chunked' } } it 'parses a basic chunked body' do expect(process("10\r\nasdfghjklasdfghj\r\n0\r\n").body).to eq('asdfghjklasdfghj') end end end faraday_middleware-1.0.0/spec/unit/encode_json_spec.rb000066400000000000000000000047731364007245400230720ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::EncodeJson do let(:middleware) { described_class.new(->(env) { env }) } def process(body, content_type = nil) env = { body: body, request_headers: Faraday::Utils::Headers.new } env[:request_headers]['content-type'] = content_type if content_type middleware.call(faraday_env(env)) end def result_body result[:body] end def result_type result[:request_headers]['content-type'] end context 'no body' do let(:result) { process(nil) } it "doesn't change body" do expect(result_body).to be_nil end it "doesn't add content type" do expect(result_type).to be_nil end end context 'empty body' do let(:result) { process('') } it "doesn't change body" do expect(result_body).to be_empty end it "doesn't add content type" do expect(result_type).to be_nil end end context 'string body' do let(:result) { process('{"a":1}') } it "doesn't change body" do expect(result_body).to eq('{"a":1}') end it 'adds content type' do expect(result_type).to eq('application/json') end end context 'object body' do let(:result) { process(a: 1) } it 'encodes body' do expect(result_body).to eq('{"a":1}') end it 'adds content type' do expect(result_type).to eq('application/json') end end context 'empty object body' do let(:result) { process({}) } it 'encodes body' do expect(result_body).to eq('{}') end end context 'object body with json type' do let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') } it 'encodes body' do expect(result_body).to eq('{"a":1}') end it "doesn't change content type" do expect(result_type).to eq('application/json; charset=utf-8') end end context 'object body with vendor json type' do let(:result) { process({ a: 1 }, 'application/vnd.myapp.v1+json; charset=utf-8') } it 'encodes body' do expect(result_body).to eq('{"a":1}') end it "doesn't change content type" do expect(result_type).to eq('application/vnd.myapp.v1+json; charset=utf-8') end end context 'object body with incompatible type' do let(:result) { process({ a: 1 }, 'application/xml; charset=utf-8') } it "doesn't change body" do expect(result_body).to eq(a: 1) end it "doesn't change content type" do expect(result_type).to eq('application/xml; charset=utf-8') end end end faraday_middleware-1.0.0/spec/unit/follow_redirects_spec.rb000066400000000000000000000321101364007245400241340ustar00rootroot00000000000000# frozen_string_literal: true # expose a method in Test adapter that should have been public Faraday::Adapter::Test::Stubs.class_eval { public :new_stub } RSpec.describe FaradayMiddleware::FollowRedirects do let(:middleware_options) { {} } let(:lint_middleware) do # checks env hash in request phase for basic validity Struct.new(:app) do def call(env) if env[:status] || env[:response] || env[:response_headers] raise "invalid request: #{env.inspect}" end if defined?(Faraday::Env) && !env.is_a?(Faraday::Env) raise "expected Faraday::Env, got #{env.class}" end app.call(env) end end end shared_examples_for 'a successful redirection' do |status_code| it 'follows the redirection for a GET request' do expect(connection do |stub| stub.get('/permanent') { [status_code, { 'Location' => '/found' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.get('/permanent').body).to eq 'fin' end it 'follows the redirection for a HEAD request' do expect(connection do |stub| stub.head('/permanent') { [status_code, { 'Location' => '/found' }, ''] } stub.head('/found') { [200, { 'Content-Type' => 'text/plain' }, ''] } end.head('/permanent').status).to eq 200 end it 'follows the redirection for a OPTIONS request' do expect(connection do |stub| stub.new_stub(:options, '/permanent') { [status_code, { 'Location' => '/found' }, ''] } stub.new_stub(:options, '/found') { [200, { 'Content-Type' => 'text/plain' }, ''] } end.run_request(:options, '/permanent', nil, nil).status).to eq 200 end it 'tolerates invalid characters in redirect location' do unescaped_location = '/found?action_type_map=["og.likes!%20you"]' escaped_location = '/found?action_type_map=[%22og.likes!%20you%22]' expect(connection do |stub| stub.get('/') { [status_code, { 'Location' => unescaped_location }, ''] } stub.get(escaped_location) { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.get('/').body).to eq('fin') end end shared_examples_for 'a forced GET redirection' do |status_code| %i[put post delete patch].each do |method| it "a #{method.to_s.upcase} request is converted to a GET" do expect(connection do |stub| stub.new_stub(method, '/redirect') do [status_code, { 'Location' => '/found' }, 'elsewhere'] end stub.get('/found') do |env| (body = env[:body]) && body.empty? && (body = nil) [200, { 'Content-Type' => 'text/plain' }, body.inspect] end end.run_request(method, '/redirect', 'request data', nil).body).to eq('nil') end end end shared_examples_for 'a replayed redirection' do |status_code| it 'redirects with the original request headers' do conn = connection do |stub| stub.get('/redirect') do [status_code, { 'Location' => '/found' }, ''] end stub.get('/found') do |env| [200, { 'Content-Type' => 'text/plain' }, env[:request_headers]['X-Test-Value']] end end response = conn.get('/redirect') do |req| req.headers['X-Test-Value'] = 'success' end expect(response.body).to eq('success') end %i[put post delete patch].each do |method| it "replays a #{method.to_s.upcase} request" do expect(connection do |stub| stub.new_stub(method, '/redirect') { [status_code, { 'Location' => '/found' }, ''] } stub.new_stub(method, '/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.run_request(method, '/redirect', nil, nil).body).to eq 'fin' end end %i[put post patch].each do |method| it "forwards request body for a #{method.to_s.upcase} request" do conn = connection do |stub| stub.new_stub(method, '/redirect') do [status_code, { 'Location' => '/found' }, ''] end stub.new_stub(method, '/found') do |env| [200, { 'Content-Type' => 'text/plain' }, env[:body]] end end response = conn.run_request(method, '/redirect', 'original data', nil) expect(response.body).to eq('original data') end end end it 'returns non-redirect response results' do expect(connection do |stub| stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.get('/found').body).to eq 'fin' end it 'follows a single redirection' do expect(connection do |stub| stub.get('/') { [301, { 'Location' => '/found' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.get('/').body).to eq 'fin' end it 'follows many redirections' do expect(connection do |stub| stub.get('/') { [301, { 'Location' => '/redirect1' }, ''] } stub.get('/redirect1') { [301, { 'Location' => '/redirect2' }, ''] } stub.get('/redirect2') { [301, { 'Location' => '/found' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.get('/').body).to eq 'fin' end it 'raises a FaradayMiddleware::RedirectLimitReached after 3 redirections (by default)' do conn = connection do |stub| stub.get('/') { [301, { 'Location' => '/redirect1' }, ''] } stub.get('/redirect1') { [301, { 'Location' => '/redirect2' }, ''] } stub.get('/redirect2') { [301, { 'Location' => '/redirect3' }, ''] } stub.get('/redirect3') { [301, { 'Location' => '/found' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end expect { conn.get('/') }.to raise_error(FaradayMiddleware::RedirectLimitReached) end it 'raises a FaradayMiddleware::RedirectLimitReached after the initialized limit' do conn = connection(limit: 1) do |stub| stub.get('/') { [301, { 'Location' => '/redirect1' }, ''] } stub.get('/redirect1') { [301, { 'Location' => '/found' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end expect { conn.get('/') }.to raise_error(FaradayMiddleware::RedirectLimitReached) end it 'ignore fragments in the Location header' do expect(connection do |stub| stub.get('/') { [301, { 'Location' => '/found#fragment' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end.get('/').body).to eq 'fin' end described_class::REDIRECT_CODES.each do |code| context "for an HTTP #{code} response" do it 'raises a FaradayMiddleware::RedirectLimitReached when Location header is missing' do conn = connection do |stub| stub.get('/') { [code, {}, ''] } end expect { conn.get('/') }.to raise_error(FaradayMiddleware::RedirectLimitReached) end end end context 'when clear_authorization_header option' do context 'is false' do it 'redirects with the original authorization headers' do conn = connection(clear_authorization_header: false) do |stub| stub.get('/redirect') do [301, { 'Location' => '/found' }, ''] end stub.get('/found') do |env| [200, { 'Content-Type' => 'text/plain' }, env[:request_headers]['Authorization']] end end response = conn.get('/redirect') do |req| req.headers['Authorization'] = 'success' end expect(response.body).to eq 'success' end end context 'is true' do context 'redirect to same host' do it 'redirects with the original authorization headers' do conn = connection do |stub| stub.get('http://localhost/redirect') do [301, { 'Location' => '/found' }, ''] end stub.get('http://localhost/found') do |env| [200, {}, env.request_headers['Authorization']] end end response = conn.get('http://localhost/redirect') do |req| req.headers['Authorization'] = 'success' end expect(response.body).to eq 'success' end end context 'redirect to same host with explicitly port' do it 'redirects with the original authorization headers' do conn = connection do |stub| stub.get('http://localhost/redirect') do [301, { 'Location' => 'http://localhost:80/found' }, ''] end stub.get('http://localhost/found') do |env| [200, {}, env.request_headers['Authorization']] end end response = conn.get('http://localhost/redirect') do |req| req.headers['Authorization'] = 'success' end expect(response.body).to eq 'success' end end context 'redirect to different scheme' do it 'redirects without original authorization headers' do conn = connection do |stub| stub.get('http://localhost/redirect') do [301, { 'Location' => 'https://localhost2/found' }, ''] end stub.get('https://localhost2/found') do |env| [200, {}, env.request_headers['Authorization']] end end response = conn.get('http://localhost/redirect') do |req| req.headers['Authorization'] = 'failed' end expect(response.body).to eq nil end end context 'redirect to different host' do it 'redirects without original authorization headers' do conn = connection do |stub| stub.get('http://localhost/redirect') do [301, { 'Location' => 'http://localhost2/found' }, ''] end stub.get('https://localhost2/found') do |env| [200, {}, env.request_headers['Authorization']] end end response = conn.get('http://localhost/redirect') do |req| req.headers['Authorization'] = 'failed' end expect(response.body).to eq nil end end context 'redirect to different port' do it 'redirects without original authorization headers' do conn = connection do |stub| stub.get('http://localhost:9090/redirect') do [301, { 'Location' => 'http://localhost:9091/found' }, ''] end stub.get('http://localhost:9091/found') do |env| [200, {}, env.request_headers['Authorization']] end end response = conn.get('http://localhost:9090/redirect') do |req| req.headers['Authorization'] = 'failed' end expect(response.body).to eq nil end end end end [301, 302].each do |code| context "for an HTTP #{code} response" do it_behaves_like 'a successful redirection', code context 'by default' do it_behaves_like 'a forced GET redirection', code end context 'with standards compliancy enabled' do let(:middleware_options) { { standards_compliant: true } } it_behaves_like 'a replayed redirection', code end end end context 'for an HTTP 303 response' do context 'by default' do it_behaves_like 'a successful redirection', 303 it_behaves_like 'a forced GET redirection', 303 end context 'with standards compliancy enabled' do let(:middleware_options) { { standards_compliant: true } } it_behaves_like 'a successful redirection', 303 it_behaves_like 'a forced GET redirection', 303 end end context 'for an HTTP 307 response' do context 'by default' do it_behaves_like 'a successful redirection', 307 it_behaves_like 'a replayed redirection', 307 end context 'with standards compliancy enabled' do let(:middleware_options) { { standards_compliant: true } } it_behaves_like 'a successful redirection', 307 it_behaves_like 'a replayed redirection', 307 end end context 'for an HTTP 308 response' do context 'by default' do it_behaves_like 'a successful redirection', 308 it_behaves_like 'a replayed redirection', 308 end context 'with standards compliancy enabled' do let(:middleware_options) { { standards_compliant: true } } it_behaves_like 'a successful redirection', 308 it_behaves_like 'a replayed redirection', 308 end end context 'with a callback' do it 'calls the callback' do from = nil to = nil callback = lambda { |old, new| from = old[:url].path to = new[:url].path } conn = connection(callback: callback) do |stub| stub.get('/redirect') { [301, { 'Location' => '/found' }, ''] } stub.get('/found') { [200, { 'Content-Type' => 'text/plain' }, 'fin'] } end conn.get('/redirect') expect([from, to]).to eq ['/redirect', '/found'] end end private def connection(options = middleware_options) Faraday.new do |c| c.use described_class, options c.use lint_middleware c.adapter :test do |stub| yield(stub) if block_given? end end end end faraday_middleware-1.0.0/spec/unit/gzip_spec.rb000066400000000000000000000064061364007245400215500ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::Gzip, type: :response do require 'brotli' let(:middleware) do described_class.new(lambda { |env| Faraday::Response.new(env) }) end context 'request' do it 'sets the Accept-Encoding request header' do env = process('').env expect(env[:request_headers][:accept_encoding]).to eq('gzip,deflate,br') end it 'doesnt overwrite existing Accept-Encoding request header' do env = process('') do |e| e[:request_headers][:accept_encoding] = 'zopfli' end.env expect(env[:request_headers][:accept_encoding]).to eq('zopfli') end end context 'response' do let(:uncompressed_body) do 'RspecHello, spec!' end let(:gzipped_body) do io = StringIO.new gz = Zlib::GzipWriter.new(io) gz.write(uncompressed_body) gz.close res = io.string res.force_encoding('BINARY') res end let(:deflated_body) do Zlib::Deflate.deflate(uncompressed_body) end let(:raw_deflated_body) do z = Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -Zlib::MAX_WBITS) compressed_body = z.deflate(uncompressed_body, Zlib::FINISH) z.close compressed_body end let(:brotlied_body) do Brotli.deflate(uncompressed_body) end let(:empty_body) do '' end shared_examples 'compressed response' do it 'uncompresses the body' do expect(process(body).body).to eq(uncompressed_body) end it 'sets the Content-Length' do expect(process(body).headers['Content-Length']).to eq(uncompressed_body.length) end it 'removes the Content-Encoding' do expect(process(body).headers['Content-Encoding']).to be_nil end end context 'gzipped response' do let(:body) { gzipped_body } let(:headers) { { 'Content-Encoding' => 'gzip', 'Content-Length' => body.length } } it_behaves_like 'compressed response' end context 'deflated response' do let(:body) { deflated_body } let(:headers) { { 'Content-Encoding' => 'deflate', 'Content-Length' => body.length } } it_behaves_like 'compressed response' end context 'raw deflated response' do let(:body) { raw_deflated_body } let(:headers) { { 'Content-Encoding' => 'deflate', 'Content-Length' => body.length } } it_behaves_like 'compressed response' end context 'brotlied response' do let(:body) { brotlied_body } let(:headers) { { 'Content-Encoding' => 'br', 'Content-Length' => body.length } } it_behaves_like 'compressed response' end context 'empty response' do let(:body) { empty_body } let(:headers) { { 'Content-Encoding' => 'gzip', 'Content-Length' => body.length } } it 'sets the Content-Length' do expect(process(body).headers['Content-Length']).to eq(empty_body.length) end it 'removes the Content-Encoding' do expect(process(body).headers['Content-Encoding']).to be_nil end end context 'identity response' do let(:body) { uncompressed_body } it 'does not modify the body' do expect(process(body).body).to eq(uncompressed_body) end end end end faraday_middleware-1.0.0/spec/unit/mashify_spec.rb000066400000000000000000000037251364007245400222400ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::Mashify do MyMash = Struct.new(:body) context 'when used', type: :response do it 'creates a Hashie::Mash from the body' do body = { 'name' => 'Erik Michaels-Ober', 'username' => 'sferik' } me = process(body).body expect(me.name).to eq('Erik Michaels-Ober') expect(me.username).to eq('sferik') end it 'handles strings' do body = 'Most amazing string EVER' me = process(body).body expect(me).to eq('Most amazing string EVER') end it 'handles arrays' do body = [123, 456] values = process(body).body expect(values).to eq([123, 456]) end it 'handles arrays of hashes' do body = [{ 'username' => 'sferik' }, { 'username' => 'pengwynn' }] us = process(body).body expect(us.first.username).to eq('sferik') expect(us.last.username).to eq('pengwynn') end it 'handles nested arrays of hashes' do body = [[{ 'username' => 'sferik' }, { 'username' => 'pengwynn' }]] us = process(body).body.first expect(us.first.username).to eq('sferik') expect(us.last.username).to eq('pengwynn') end it 'handles mixed arrays' do body = [123, { 'username' => 'sferik' }, 456] values = process(body).body expect(values.first).to eq(123) expect(values.last).to eq(456) expect(values[1].username).to eq('sferik') end it 'allows for use of custom Mash subclasses at the class level' do original_class = described_class.mash_class described_class.mash_class = MyMash begin me = process({}).body expect(me).to be_instance_of(MyMash) ensure described_class.mash_class = original_class end end end context 'custom mash subclass', type: :response do let(:options) { { mash_class: MyMash } } it 'instance level' do me = process({}).body expect(me).to be_instance_of(MyMash) end end end faraday_middleware-1.0.0/spec/unit/method_override_spec.rb000066400000000000000000000042751364007245400237600ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::MethodOverride do let(:middleware) { described_class.new(->(env) { env }, *options) } let(:env) { middleware.call faraday_env(request_env(request_method)) } def request_env(method) { method: method, request_headers: Faraday::Utils::Headers.new } end shared_examples 'overrides method' do |method| it 'sets physical method to POST' do expect(env[:method]).to eq(:post) end it "sets header to #{method}" do expect(env[:request_headers]['X-Http-Method-Override']).to eq(method) end end shared_examples "doesn't override method" do |method| it 'keeps original method' do expect(env[:method]).to eq(method) end it "doesn't set header value" do expect(env[:request_headers]).not_to have_key('X-Http-Method-Override') end end context 'with default options' do let(:options) { nil } context 'GET' do let(:request_method) { :get } include_examples "doesn't override method", :get end context 'POST' do let(:request_method) { :post } include_examples "doesn't override method", :post end context 'PUT' do let(:request_method) { :put } include_examples 'overrides method', 'PUT' end end context 'configured to rewrite [:patch, :delete]' do let(:options) { [{ rewrite: %i[patch delete] }] } context 'PUT' do let(:request_method) { :put } include_examples "doesn't override method", :put end context 'PATCH' do let(:request_method) { :patch } include_examples 'overrides method', 'PATCH' end context 'DELETE' do let(:request_method) { :delete } include_examples 'overrides method', 'DELETE' end end context "configured to rewrite ['PATCH']" do let(:options) { [{ rewrite: %w[PATCH] }] } context 'PATCH' do let(:request_method) { :patch } include_examples 'overrides method', 'PATCH' end end context 'with invalid option' do let(:options) { [{ hello: 'world' }] } let(:request_method) { :get } it 'raises key error' do expect { env }.to raise_error(IndexError, /key [\s\w]*not found/) end end end faraday_middleware-1.0.0/spec/unit/oauth2_spec.rb000066400000000000000000000111151364007245400217720ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::OAuth2 do def query_params(env) Faraday::Utils.parse_query env[:url].query end def auth_header(env) env[:request_headers]['Authorization'] end def perform(params = {}, headers = {}) env = { url: URI('http://example.com/?' + Faraday::Utils.build_query(params)), request_headers: Faraday::Utils::Headers.new.update(headers) } app = make_app app.call(faraday_env(env)) end def make_app described_class.new(->(env) { env }, *Array(options)) end context 'no token configured' do let(:options) { [nil, { token_type: :param }] } it "doesn't add params" do request = perform(q: 'hello') expect(query_params(request)).to eq('q' => 'hello') end it "doesn't add headers" do expect(auth_header(perform)).to be_nil end it 'creates header for explicit token' do request = perform(q: 'hello', access_token: 'abc123') expect(query_params(request)).to eq('q' => 'hello', 'access_token' => 'abc123') expect(auth_header(request)).to eq(%(Token token="abc123")) end context 'bearer token type configured' do let(:options) { [nil, { token_type: :bearer }] } it "doesn't add headers" do expect(auth_header(perform)).to be_nil end it 'adds header for explicit token' do request = perform(q: 'hello', access_token: 'abc123') expect(auth_header(request)).to eq(%(Bearer abc123)) end end end context 'default token configured' do let(:options) { ['XYZ', { token_type: :param }] } it 'adds token param' do expect(query_params(perform(q: 'hello'))).to eq('q' => 'hello', 'access_token' => 'XYZ') end it 'adds token header' do expect(auth_header(perform)).to eq(%(Token token="XYZ")) end it 'overrides default with explicit token' do request = perform(q: 'hello', access_token: 'abc123') expect(query_params(request)).to eq('q' => 'hello', 'access_token' => 'abc123') expect(auth_header(request)).to eq(%(Token token="abc123")) end it 'clears default with empty explicit token' do request = perform(q: 'hello', access_token: nil) expect(query_params(request).fetch('access_token')).to_not eq('XYZ') expect(auth_header(request)).to be_nil end context 'bearer token type configured' do let(:options) { ['XYZ', { token_type: :bearer }] } it 'adds token header' do expect(auth_header(perform)).to eq(%(Bearer XYZ)) end it 'overrides default with explicit token' do request = perform(q: 'hello', access_token: 'abc123') expect(auth_header(request)).to eq(%(Bearer abc123)) end it 'clears default with empty explicit token' do request = perform(q: 'hello', access_token: nil) expect(auth_header(request)).to be_nil end end end context 'existing Authorization header' do let(:options) { ['XYZ', { token_type: :param }] } subject { perform({ q: 'hello' }, 'Authorization' => 'custom') } it 'adds token param' do expect(query_params(subject)).to eq('q' => 'hello', 'access_token' => 'XYZ') end it "doesn't override existing header" do expect(auth_header(subject)).to eq('custom') end context 'bearer token type configured' do let(:options) { ['XYZ', { token_type: :bearer }] } subject { perform({ q: 'hello' }, 'Authorization' => 'custom') } it "doesn't override existing header" do expect(auth_header(subject)).to eq('custom') end end end context 'custom param name configured' do let(:options) { ['XYZ', { token_type: :param, param_name: :oauth }] } it 'adds token param' do expect(query_params(perform)).to eq('oauth' => 'XYZ') end it 'overrides default with explicit token' do request = perform(oauth: 'abc123') expect(query_params(request)).to eq('oauth' => 'abc123') expect(auth_header(request)).to eq(%(Token token="abc123")) end end context 'options without token configuration' do let(:options) { [{ token_type: :param, param_name: :oauth }] } it "doesn't add param" do expect(query_params(perform)).to be_empty end it 'overrides default with explicit token' do expect(query_params(perform(oauth: 'abc123'))).to eq('oauth' => 'abc123') end end context 'invalid param name configured' do let(:options) { ['XYZ', { token_type: :param, param_name: nil }] } it 'raises error' do expect { make_app }.to raise_error(ArgumentError, ":param_name can't be blank") end end end faraday_middleware-1.0.0/spec/unit/oauth_spec.rb000066400000000000000000000107711364007245400217170ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::OAuth do def auth_header(env) env[:request_headers]['Authorization'] end def auth_values(env) auth = auth_header(env) return unless auth raise "invalid header: #{auth.inspect}" unless auth.sub!('OAuth ', '') Hash[*auth.split(/, |=/)] end def perform(oauth_options = {}, headers = {}, params = {}) env = { url: URI('http://example.com/'), request_headers: Faraday::Utils::Headers.new.update(headers), request: {}, body: params } unless oauth_options.is_a?(Hash) && oauth_options.empty? env[:request][:oauth] = oauth_options end app = make_app app.call(faraday_env(env)) end def make_app described_class.new(->(env) { env }, *Array(options)) end context 'invalid options' do let(:options) { nil } it 'errors out' do expect { make_app }.to raise_error(ArgumentError) end end context 'empty options' do let(:options) { [{}] } it 'signs request' do auth = auth_values(perform) expected_keys = %w[ oauth_nonce oauth_signature oauth_signature_method oauth_timestamp oauth_version ] expect(auth.keys).to match_array expected_keys end end context 'configured with consumer and token' do let(:options) do [{ consumer_key: 'CKEY', consumer_secret: 'CSECRET', token: 'TOKEN', token_secret: 'TSECRET' }] end it 'adds auth info to the header' do auth = auth_values(perform) expected_keys = %w[ oauth_consumer_key oauth_nonce oauth_signature oauth_signature_method oauth_timestamp oauth_token oauth_version ] expect(auth.keys).to match_array expected_keys expect(auth['oauth_version']).to eq(%("1.0")) expect(auth['oauth_signature_method']).to eq(%("HMAC-SHA1")) expect(auth['oauth_consumer_key']).to eq(%("CKEY")) expect(auth['oauth_token']).to eq(%("TOKEN")) end it "doesn't override existing header" do request = perform({}, 'Authorization' => 'iz me!') expect(auth_header(request)).to eq('iz me!') end it 'can override oauth options per-request' do auth = auth_values(perform(consumer_key: 'CKEY2')) expect(auth['oauth_consumer_key']).to eq(%("CKEY2")) expect(auth['oauth_token']).to eq(%("TOKEN")) end it 'can turn off oauth signing per-request' do expect(auth_header(perform(false))).to be_nil end end context 'configured without token' do let(:options) { [{ consumer_key: 'CKEY', consumer_secret: 'CSECRET' }] } it 'adds auth info to the header' do auth = auth_values(perform) expect(auth).to include('oauth_consumer_key') expect(auth).not_to include('oauth_token') end end context 'handling body parameters' do let(:options) do [{ consumer_key: 'CKEY', consumer_secret: 'CSECRET', nonce: '547fed103e122eecf84c080843eedfe6', timestamp: '1286830180' }] end let(:value) { { 'foo' => 'bar' } } let(:type_json) { { 'Content-Type' => 'application/json' } } let(:type_form) { { 'Content-Type' => 'application/x-www-form-urlencoded' } } extend Forwardable def_delegator :'Faraday::Utils', :build_nested_query it 'does not include the body for JSON' do auth_header_with = auth_header(perform({}, type_json, '{"foo":"bar"}')) auth_header_without = auth_header(perform({}, type_json, {})) expect(auth_header_with).to eq(auth_header_without) end it 'includes the body parameters with form Content-Type' do auth_header_with = auth_header(perform({}, type_form, {})) auth_header_without = auth_header(perform({}, type_form, value)) expect(auth_header_with).not_to eq(auth_header_without) end it 'includes the body parameters with an unspecified Content-Type' do auth_header_with = auth_header(perform({}, {}, value)) auth_header_without = auth_header(perform({}, type_form, value)) expect(auth_header_with).to eq(auth_header_without) end it 'includes the body parameters for form type with string body' do # simulates the behavior of Faraday::MiddleWare::UrlEncoded value = { 'foo' => %w[bar baz wat] } auth_header_hash = auth_header(perform({}, type_form, value)) auth_header_string = auth_header(perform({}, type_form, build_nested_query(value))) expect(auth_header_string).to eq(auth_header_hash) end end end faraday_middleware-1.0.0/spec/unit/parse_dates_spec.rb000066400000000000000000000030561364007245400230670ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::ParseDates, type: :response do let(:parsed) { '2012-02-01 13:14:15 UTC' } it 'parses dates' do expect(process('x' => '2012-02-01T13:14:15Z').body['x'].to_s).to eq(parsed) expect(process('x' => '2012-02-01T15:14:15+02:00').body['x'].utc.to_s).to eq(parsed) expect(process('x' => '2012-02-01T11:14:15-0200').body['x'].utc.to_s).to eq(parsed) end it 'parses dates with milliseconds' do date_str = '2012-02-01T13:14:15.123Z' non_zulu_date_str = '2012-02-01T11:14:15.123-02:00' expect(process('x' => date_str).body['x']).to eq(Time.parse(date_str)) expect(process('x' => non_zulu_date_str).body['x']).to eq(Time.parse(date_str)) end it 'parses nested dates in hash' do expect(process('x' => { 'y' => '2012-02-01T13:14:15Z' }).body['x']['y'].to_s).to eq(parsed) end it 'parses nested dates in arrays' do expect(process('x' => [{ 'y' => '2012-02-01T13:14:15Z' }]).body['x'][0]['y'].to_s).to eq(parsed) end it 'returns nil when body is empty' do expect(process(nil).body).to eq(nil) end it 'leaves arrays with ids alone' do expect(process('x' => [1, 2, 3]).body).to eq('x' => [1, 2, 3]) end it 'does not parse date-like things' do expect(process('x' => '2012-02-01T13:14:15Z bla').body['x'].to_s).to eq '2012-02-01T13:14:15Z bla' expect(process('x' => '12012-02-01T13:14:15Z').body['x'].to_s).to eq '12012-02-01T13:14:15Z' expect(process('x' => "2012-02-01T13:14:15Z\nfoo").body['x'].to_s).to eq "2012-02-01T13:14:15Z\nfoo" end end faraday_middleware-1.0.0/spec/unit/parse_json_spec.rb000066400000000000000000000075671364007245400227530ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::ParseJson, type: :response do context 'no type matching' do it "doesn't change nil body" do expect(process(nil).body).to be_nil end it 'nullifies empty body' do expect(process('').body).to be_nil end it 'parses json body' do response = process('{"a":1}') expect(response.body).to eq('a' => 1) expect(response.env[:raw_body]).to be_nil end end context 'with preserving raw' do let(:options) { { preserve_raw: true } } it 'parses json body' do response = process('{"a":1}') expect(response.body).to eq('a' => 1) expect(response.env[:raw_body]).to eq('{"a":1}') end it 'can opt out of preserving raw' do response = process('{"a":1}', nil, preserve_raw: false) expect(response.env[:raw_body]).to be_nil end end context 'with regexp type matching' do let(:options) { { content_type: /\bjson$/ } } it 'parses json body of correct type' do response = process('{"a":1}', 'application/x-json') expect(response.body).to eq('a' => 1) end it 'ignores json body of incorrect type' do response = process('{"a":1}', 'text/json-xml') expect(response.body).to eq('{"a":1}') end end context 'with array type matching' do let(:options) { { content_type: %w[a/b c/d] } } it 'parses json body of correct type' do expect(process('{"a":1}', 'a/b').body).to be_a(Hash) expect(process('{"a":1}', 'c/d').body).to be_a(Hash) end it 'ignores json body of incorrect type' do expect(process('{"a":1}', 'a/d').body).not_to be_a(Hash) end end it 'chokes on invalid json' do expect { process('{!') }.to raise_error(Faraday::ParsingError) end it 'includes the response on the ParsingError instance' do begin process('{') { |env| env[:response] = Faraday::Response.new } raise 'Parsing should have failed.' rescue Faraday::ParsingError => e expect(e.response).to be_a(Faraday::Response) end end context 'with mime type fix' do let(:middleware) do app = described_class::MimeTypeFix.new(lambda { |env| Faraday::Response.new(env) }, content_type: %r{^text/}) described_class.new(app, content_type: 'application/json') end it 'ignores completely incompatible type' do response = process('{"a":1}', 'application/xml') expect(response.body).to eq('{"a":1}') end it 'ignores compatible type with bad data' do response = process('var a = 1', 'text/javascript') expect(response.body).to eq('var a = 1') expect(response['content-type']).to eq('text/javascript') end it 'corrects compatible type and data' do response = process('{"a":1}', 'text/javascript') expect(response.body).to be_a(Hash) expect(response['content-type']).to eq('application/json') end it 'corrects compatible type even when data starts with whitespace' do response = process(%( \r\n\t{"a":1}), 'text/javascript') expect(response.body).to be_a(Hash) expect(response['content-type']).to eq('application/json') end end context 'HEAD responses' do it "nullifies the body if it's only one space" do response = process(' ') expect(response.body).to be_nil end it "nullifies the body if it's two spaces" do response = process(' ') expect(response.body).to be_nil end end context 'JSON options' do let(:body) { '{"a": 1}' } let(:result) { { a: 1 } } let(:options) do { parser_options: { symbolize_names: true } } end it 'passes relevant options to JSON parse' do expect(::JSON).to receive(:parse) .with(body, options[:parser_options]) .and_return(result) response = process(body) expect(response.body).to eq(result) end end end faraday_middleware-1.0.0/spec/unit/parse_marshal_spec.rb000066400000000000000000000006141364007245400234130ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::ParseMarshal, type: :response do it 'restores a marshaled dump' do expect(process(Marshal.dump(a: 1)).body).to be_eql(a: 1) end it 'nulifies blank response' do expect(process('').body).to be_nil end it 'chokes on invalid content' do expect { process('abc') }.to raise_error(Faraday::ParsingError) end end faraday_middleware-1.0.0/spec/unit/parse_xml_spec.rb000066400000000000000000000045201364007245400225640ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::ParseXml, type: :response do let(:xml) { 'Erik Michaels-Obersferik' } let(:user) { { 'user' => { 'name' => 'Erik Michaels-Ober', 'screen_name' => 'sferik' } } } context 'no type matching' do it "doesn't change nil body" do expect(process(nil).body).to be_nil end it 'turns empty body into empty hash' do expect(process('').body).to be_eql({}) end it 'parses xml body' do response = process(xml) expect(response.body).to eq(user) expect(response.env[:raw_body]).to be_nil end end context 'with preserving raw' do let(:options) { { preserve_raw: true } } it 'parses xml body' do response = process(xml) expect(response.body).to eq(user) expect(response.env[:raw_body]).to eq(xml) end it 'can opt out of preserving raw' do response = process(xml, nil, preserve_raw: false) expect(response.env[:raw_body]).to be_nil end end context 'with regexp type matching' do let(:options) { { content_type: /\bxml$/ } } it 'parses xml body of correct type' do response = process(xml, 'application/xml') expect(response.body).to eq(user) end it 'ignores xml body of incorrect type' do response = process(xml, 'text/html') expect(response.body).to eq(xml) end end context 'with array type matching' do let(:options) { { content_type: %w[a/b c/d] } } it 'parses xml body of correct type' do expect(process(xml, 'a/b').body).to be_a(Hash) expect(process(xml, 'c/d').body).to be_a(Hash) end it 'ignores xml body of incorrect type' do expect(process(xml, 'a/d').body).not_to be_a(Hash) end end it 'chokes on invalid xml' do ['{!', '"a"', 'true', 'null', '1'].each do |data| expect { process(data) }.to raise_error(Faraday::ParsingError) end end context 'MultiXml options' do let(:options) do { parser_options: { symbolize_names: true } } end it 'passes relevant options to MultiXml parse' do expect(::MultiXml).to receive(:parse) .with(xml, options[:parser_options]) .and_return(user) response = process(xml) expect(response.body).to be(user) end end end faraday_middleware-1.0.0/spec/unit/parse_yaml_spec.rb000066400000000000000000000035101364007245400227240ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::ParseYaml, type: :response do context 'no type matching' do it "doesn't change nil body" do expect(process(nil).body).to be_nil end it 'returns false for empty body' do expect(process('').body).to be false end it 'parses yaml body' do response = process('a: 1') expect(response.body).to eq('a' => 1) expect(response.env[:raw_body]).to be_nil end end context 'with preserving raw' do let(:options) { { preserve_raw: true } } it 'parses yaml body' do response = process('a: 1') expect(response.body).to eq('a' => 1) expect(response.env[:raw_body]).to eq('a: 1') end it 'can opt out of preserving raw' do response = process('a: 1', nil, preserve_raw: false) expect(response.env[:raw_body]).to be_nil end end context 'with regexp type matching' do let(:options) { { content_type: /\byaml$/ } } it 'parses json body of correct type' do response = process('a: 1', 'application/x-yaml') expect(response.body).to eq('a' => 1) end it 'ignores json body of incorrect type' do response = process('a: 1', 'text/yaml-xml') expect(response.body).to eq('a: 1') end end it 'chokes on invalid yaml' do expect { process('{!') }.to raise_error(Faraday::ParsingError) end context 'SafeYAML options' do let(:body) { 'a: 1' } let(:result) { { a: 1 } } let(:options) do { parser_options: { symbolize_names: true } } end it 'passes relevant options to SafeYAML load' do expect(::SafeYAML).to receive(:load) .with(body, nil, options[:parser_options]) .and_return(result) response = process(body) expect(response.body).to be(result) end end end faraday_middleware-1.0.0/spec/unit/rashify_spec.rb000066400000000000000000000026171364007245400222440ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::Rashify do context 'when used', type: :response do it 'creates a Hashie::Rash from the body' do body = { 'name' => 'Erik Michaels-Ober', 'username' => 'sferik' } me = process(body).body expect(me.name).to eq('Erik Michaels-Ober') expect(me.username).to eq('sferik') end it 'handles strings' do body = 'Most amazing string EVER' me = process(body).body expect(me).to eq('Most amazing string EVER') end it 'handles hashes and decamelcase the keys' do body = { 'name' => 'Erik Michaels-Ober', 'userName' => 'sferik' } me = process(body).body expect(me.name).to eq('Erik Michaels-Ober') expect(me.user_name).to eq('sferik') end it 'handles arrays' do body = [123, 456] values = process(body).body expect(values).to eq([123, 456]) end it 'handles arrays of hashes' do body = [{ 'username' => 'sferik' }, { 'username' => 'pengwynn' }] us = process(body).body expect(us.first.username).to eq('sferik') expect(us.last.username).to eq('pengwynn') end it 'handles mixed arrays' do body = [123, { 'username' => 'sferik' }, 456] values = process(body).body expect(values.first).to eq(123) expect(values.last).to eq(456) expect(values[1].username).to eq('sferik') end end end faraday_middleware-1.0.0/spec/unit/response_middleware_spec.rb000066400000000000000000000010751364007245400246270ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe FaradayMiddleware::ResponseMiddleware do describe '.define_parser' do it 'raises error when missing parser and block' do expect do described_class.define_parser end.to raise_error(ArgumentError) end it 'raises no error when given block' do expect do described_class.define_parser {} end.not_to raise_error end it 'raises no error when given a parser' do expect do described_class.define_parser(double) end.not_to raise_error end end end