Skip to content

Commit

Permalink
refactor(farady): split up middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
ksol committed Feb 11, 2024
1 parent 6f3bbc7 commit ce094af
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 14 deletions.
12 changes: 8 additions & 4 deletions lib/scalingo/api/client.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "scalingo/token_holder"
require "scalingo/faraday/response"
require "scalingo/faraday/unpack_middleware"
require "scalingo/faraday/extract_meta"
require "scalingo/faraday/extract_root_value"
require "active_support/core_ext/hash"

module Scalingo
Expand Down Expand Up @@ -87,7 +88,8 @@ def connection(fallback_to_guest: false)

def unauthenticated_connection
@unauthenticated_conn ||= Faraday.new(connection_options) { |conn|
conn.response :unpack
conn.response :extract_root_value
conn.response :extract_meta
conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
conn.request :json

Expand All @@ -108,7 +110,8 @@ def authenticated_connection
end

@connection = Faraday.new(connection_options) { |conn|
conn.response :unpack
conn.response :extract_root_value
conn.response :extract_meta
conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
conn.request :json
conn.request :authorization, "Bearer", -> { token_holder.token&.value }
Expand All @@ -122,7 +125,8 @@ def database_connection(database_id)

@database_connections ||= {}
@database_connections[database_id] ||= Faraday.new(connection_options) { |conn|
conn.response :unpack
conn.response :extract_root_value
conn.response :extract_meta
conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
conn.request :json
conn.request :authorization, "Bearer", -> { token_holder.database_tokens[database_id]&.value }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
require "faraday"

module Scalingo
class UnpackMiddleware < Faraday::Middleware
class ExtractMeta < Faraday::Middleware
def on_complete(env)
# We only want to unpack the response for successful responses
return unless env.response.success?

# Only hash-like objects are relevant to "unpack"
# Only hash-like objects are relevant
return unless env.body.is_a?(Hash)

# Extract meta from response
Expand All @@ -23,11 +20,8 @@ def on_complete(env)
}.compact
}
end

# Dig the root key if it's the only remaining key in the body
env.body = env.body.values.first if env.body.size == 1
end
end
end

Faraday::Response.register_middleware(unpack: Scalingo::UnpackMiddleware)
Faraday::Response.register_middleware(extract_meta: Scalingo::ExtractMeta)
18 changes: 18 additions & 0 deletions lib/scalingo/faraday/extract_root_value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "faraday"

module Scalingo
class ExtractRootValue < Faraday::Middleware
def on_complete(env)
# We only want to unpack the response for successful responses
return unless env.response.success?

# Only hash-like objects are relevant to "unpack"
return unless env.body.is_a?(Hash)

# Dig the root key if it's the only remaining key in the body
env.body = env.body.values.first if env.body.size == 1
end
end
end

Faraday::Response.register_middleware(extract_root_value: Scalingo::ExtractRootValue)
3 changes: 2 additions & 1 deletion lib/scalingo/faraday/response.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
require "faraday"
require "faraday/response"

# Some additional methods for Faraday::Response in order to enhance expressiveness
# Note: the file is scalingo/faraday/response but we're reopning Faraday::Response:
# we define additional methods for Faraday::Response in order to enhance expressiveness
module Faraday
class Response
def client_error?
Expand Down
101 changes: 101 additions & 0 deletions spec/scalingo/faraday/extract_meta_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
require "spec_helper"
require "scalingo/faraday/extract_meta"

RSpec.shared_examples "no meta extraction" do
it "does not extract meta from response" do
expect(subject.meta).to eq(nil)
expect(subject.meta?).to eq(false)
expect(subject.pagination).to eq(nil)
expect(subject.paginated?).to eq(false)
expect(subject.cursor_pagination).to eq(nil)
expect(subject.cursor_paginated?).to eq(false)
end
end

RSpec.describe Scalingo::ExtractMeta do
let(:headers) { {"Content-Type" => content_type}.compact }
let(:content_type) { "application/json" }
let(:body) { nil }

let(:client) do
Faraday.new do |b|
b.response :extract_meta
b.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
b.adapter :test do |stub|
stub.get("/") { [nil, headers, body] }
end
end
end

subject { client.get("/") }

context "with a non-json response" do
let(:content_type) { "text/html" }
let(:body) { "string".to_json }

include_examples "no meta extraction"
end

context "without a body" do
include_examples "no meta extraction"
end

context "with a non indexable body" do
let(:body) { "string".to_json }

include_examples "no meta extraction"
end

context "with a non hash indexable body" do
let(:body) { [1, 2, 3].to_json }

include_examples "no meta extraction"
end

context "with a hash" do
context "without a meta key" do
let(:body) { {a: 1, b: 2}.to_json }

include_examples "no meta extraction"
end

context "with a generic meta key" do
let(:body) { {meta: {a: 1}}.to_json }

it "extracts the meta" do
expect(subject.meta).to eq({a: 1})
expect(subject.meta?).to eq(true)
expect(subject.pagination).to eq(nil)
expect(subject.paginated?).to eq(false)
expect(subject.cursor_pagination).to eq(nil)
expect(subject.cursor_paginated?).to eq(false)
end
end

context "with a pagination meta key" do
let(:body) { {meta: {pagination: {page: 1}}}.to_json }

it "extracts the meta" do
expect(subject.meta).to eq({pagination: {page: 1}})
expect(subject.meta?).to eq(true)
expect(subject.pagination).to eq({page: 1})
expect(subject.paginated?).to eq(true)
expect(subject.cursor_pagination).to eq(nil)
expect(subject.cursor_paginated?).to eq(false)
end
end

context "with a cursor pagination" do
let(:body) { {next_cursor: 1, has_more: true}.to_json }

it "extracts the meta" do
expect(subject.meta).to eq({cursor_pagination: {next_cursor: 1, has_more: true}})
expect(subject.meta?).to eq(true)
expect(subject.pagination).to eq(nil)
expect(subject.paginated?).to eq(false)
expect(subject.cursor_pagination).to eq({next_cursor: 1, has_more: true})
expect(subject.cursor_paginated?).to eq(true)
end
end
end
end
48 changes: 48 additions & 0 deletions spec/scalingo/faraday/extract_root_value_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require "spec_helper"
require "scalingo/faraday/extract_root_value"

RSpec.describe Scalingo::ExtractRootValue do
let(:status) { 200 }
let(:body) { {a: 1} }

let(:client) do
Faraday.new do |b|
b.response :extract_root_value
b.adapter :test do |stub|
stub.get("/") { [status, {}, body] }
end
end
end

subject { client.get("/") }

context "with a non-successful response" do
let(:status) { 300 }

it "does not change the response body" do
expect(subject.body).to eq({a: 1})
end
end

context "with a non-hash response" do
let(:body) { [{a: 1}] }

it "does not change the response body" do
expect(subject.body).to eq([{a: 1}])
end
end

context "with a multi keys hash" do
let(:body) { {a: 1, b: 2} }

it "does not change the response body" do
expect(subject.body).to eq({a: 1, b: 2})
end
end

context "with a single key hash" do
it "extracts the response body" do
expect(subject.body).to eq(1)
end
end
end

0 comments on commit ce094af

Please sign in to comment.