Skip to content

Commit

Permalink
Add raw_request (#1217)
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym-stripe authored May 18, 2023
1 parent adc0ccf commit 23cbd8f
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 15 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Metrics/MethodLength:
Metrics/ModuleLength:
Enabled: false

Metrics/ParameterLists:
# There's 2 methods in `StripeClient` that have long parameter lists.
Max: 8

Style/AccessModifierDeclarations:
EnforcedStyle: inline

Expand Down
43 changes: 43 additions & 0 deletions lib/stripe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,49 @@ def self.set_app_info(name, partner_id: nil, url: nil, version: nil)
version: version,
}
end

class Preview
def self._get_default_opts(opts)
{ api_mode: :preview }.merge(opts)
end

def self.get(url, opts = {})
Stripe.raw_request(:get, url, {}, _get_default_opts(opts))
end

def self.post(url, params = {}, opts = {})
Stripe.raw_request(:post, url, params, _get_default_opts(opts))
end

def self.delete(url, opts = {})
Stripe.raw_request(:delete, url, {}, _get_default_opts(opts))
end
end

class RawRequest
include Stripe::APIOperations::Request

def initialize
@opts = {}
end

def execute(method, url, params = {}, opts = {})
resp, = execute_resource_request(method, url, params, opts)

resp
end
end

# Sends a request to Stripe REST API
def self.raw_request(method, url, params = {}, opts = {})
req = RawRequest.new
req.execute(method, url, params, opts)
end

def self.deserialize(data)
data = JSON.parse(data) if data.is_a?(String)
Util.convert_to_stripe_object(data, {})
end
end

Stripe.log_level = ENV["STRIPE_LOG"] unless ENV["STRIPE_LOG"].nil?
2 changes: 2 additions & 0 deletions lib/stripe/api_operations/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ def execute_resource_request_stream(method, url,
api_key = headers.delete(:api_key)
api_base = headers.delete(:api_base)
client = headers.delete(:client)
api_mode = headers.delete(:api_mode)
# Assume all remaining opts must be headers

resp, opts[:api_key] = client.send(
client_request_method_sym,
method, url,
api_base: api_base, api_key: api_key,
headers: headers, params: params,
api_mode: api_mode,
&read_body_chunk_block
)

Expand Down
1 change: 1 addition & 0 deletions lib/stripe/api_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
module Stripe
module ApiVersion
CURRENT = "2022-11-15"
PREVIEW = "20230509T165653"
end
end
50 changes: 35 additions & 15 deletions lib/stripe/stripe_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,10 @@ def request
end

def execute_request(method, path,
api_base: nil, api_key: nil, headers: {}, params: {})
api_base: nil, api_key: nil,
headers: {}, params: {}, api_mode: nil)
http_resp, api_key = execute_request_internal(
method, path, api_base, api_key, headers, params
method, path, api_base, api_key, headers, params, api_mode
)

begin
Expand Down Expand Up @@ -245,14 +246,16 @@ def execute_request(method, path,
def execute_request_stream(method, path,
api_base: nil, api_key: nil,
headers: {}, params: {},
api_mode: nil,
&read_body_chunk_block)
unless block_given?
raise ArgumentError,
"execute_request_stream requires a read_body_chunk_block"
end

http_resp, api_key = execute_request_internal(
method, path, api_base, api_key, headers, params, &read_body_chunk_block
method, path, api_base, api_key,
headers, params, api_mode, &read_body_chunk_block
)

# When the read_body_chunk_block is given, we no longer have access to the
Expand Down Expand Up @@ -432,7 +435,7 @@ def self.maybe_gc_connection_managers

private def execute_request_internal(method, path,
api_base, api_key, headers, params,
&read_body_chunk_block)
api_mode, &read_body_chunk_block)
raise ArgumentError, "method should be a symbol" \
unless method.is_a?(Symbol)
raise ArgumentError, "path should be a string" \
Expand All @@ -456,8 +459,9 @@ def self.maybe_gc_connection_managers

query_params, path = merge_query_params(query_params, path)

headers = request_headers(api_key, method)
headers = request_headers(api_key, method, api_mode)
.update(Util.normalize_headers(headers))

url = api_url(path, api_base)

# Merge given query parameters with any already encoded in the path.
Expand All @@ -468,7 +472,7 @@ def self.maybe_gc_connection_managers
# a log-friendly variant of the encoded form. File objects are displayed
# as such instead of as their file contents.
body, body_log =
body_params ? encode_body(body_params, headers) : [nil, nil]
body_params ? encode_body(body_params, headers, api_mode) : [nil, nil]

authenticator.authenticate(method, headers, body) unless api_key

Expand Down Expand Up @@ -544,7 +548,7 @@ def self.maybe_gc_connection_managers
# Encodes a set of body parameters using multipart if `Content-Type` is set
# for that, or standard form-encoding otherwise. Returns the encoded body
# and a version of the encoded body that's safe to be logged.
private def encode_body(body_params, headers)
private def encode_body(body_params, headers, api_mode)
body = nil
flattened_params = Util.flatten_params(body_params)

Expand All @@ -560,15 +564,22 @@ def self.maybe_gc_connection_managers
flattened_params =
flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h

elsif api_mode == :preview
body = JSON.generate(body_params)
headers["Content-Type"] = "application/json"
else
body = Util.encode_parameters(body_params)
end

# We don't use `Util.encode_parameters` partly as an optimization (to not
# redo work we've already done), and partly because the encoded forms of
# certain characters introduce a lot of visual noise and it's nice to
# have a clearer format for logs.
body_log = flattened_params.map { |k, v| "#{k}=#{v}" }.join("&")
if api_mode == :preview
body_log = body
else
# We don't use `Util.encode_parameters` partly as an optimization (to
# not redo work we've already done), and partly because the encoded
# forms of certain characters introduce a lot of visual noise and it's
# nice to have a clearer format for logs.
body_log = flattened_params.map { |k, v| "#{k}=#{v}" }.join("&")
end

[body, body_log]
end
Expand Down Expand Up @@ -868,7 +879,7 @@ def self.maybe_gc_connection_managers
message + "\n\n(Network error: #{error.message})"
end

private def request_headers(api_key, method)
private def request_headers(api_key, method, api_mode)
user_agent = "Stripe/v1 RubyBindings/#{Stripe::VERSION}"
unless Stripe.app_info.nil?
user_agent += " " + format_app_info(Stripe.app_info)
Expand All @@ -877,9 +888,13 @@ def self.maybe_gc_connection_managers
headers = {
"User-Agent" => user_agent,
"Authorization" => "Bearer #{api_key}",
"Content-Type" => "application/x-www-form-urlencoded",
}

if api_mode != :preview
# TODO: (major) don't set Content-Type if method is not post
headers["Content-Type"] = "application/x-www-form-urlencoded"
end

if config.enable_telemetry? && !@last_request_metrics.nil?
headers["X-Stripe-Client-Telemetry"] = JSON.generate(
last_request_metrics: @last_request_metrics.payload
Expand All @@ -892,7 +907,12 @@ def self.maybe_gc_connection_managers
headers["Idempotency-Key"] ||= SecureRandom.uuid
end

headers["Stripe-Version"] = config.api_version if config.api_version
if api_mode == :preview
headers["Stripe-Version"] = ApiVersion::PREVIEW
elsif config.api_version
headers["Stripe-Version"] = config.api_version
end

headers["Stripe-Account"] = config.stripe_account if config.stripe_account

user_agent = @system_profiler.user_agent
Expand Down
84 changes: 84 additions & 0 deletions test/stripe/preview_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

require ::File.expand_path("../test_helper", __dir__)

class PreviewTest < Test::Unit::TestCase
context "preview raw requests" do
should "send preview get request with correct default options" do
expected_body = "{\"id\": \"acc_123\"}"
req = nil

stub_request(:get, "#{Stripe.api_base}/v2/accounts/acc_123")
.with { |request| req = request }
.to_return(body: expected_body)

resp = Stripe::Preview.get("/v2/accounts/acc_123")

assert_equal nil, req.headers["Content-Type"]
assert_equal Stripe::ApiVersion::PREVIEW, req.headers["Stripe-Version"]
assert_equal expected_body, resp.http_body
end

should "send preview post request with correct default options" do
expected_body = "{\"id\": \"acc_123\"}"
req = nil

stub_request(:post, "#{Stripe.api_base}/v2/accounts")
.with { |request| req = request }
.to_return(body: expected_body)

resp = Stripe::Preview.post("/v2/accounts", { p1: 1, p2: "string" })

assert_equal "application/json", req.headers["Content-Type"]
assert_equal Stripe::ApiVersion::PREVIEW, req.headers["Stripe-Version"]
assert_equal "{\"p1\":1,\"p2\":\"string\"}", req.body
assert_equal expected_body, resp.http_body
end

should "send preview delete request with correct default options" do
expected_body = "{\"id\": \"acc_123\"}"
req = nil

stub_request(:delete, "#{Stripe.api_base}/v2/accounts/acc_123")
.with { |request| req = request }
.to_return(body: expected_body)

resp = Stripe::Preview.delete("/v2/accounts/acc_123")

assert_equal nil, req.headers["Content-Type"]
assert_equal Stripe::ApiVersion::PREVIEW, req.headers["Stripe-Version"]
assert_equal expected_body, resp.http_body
end

should "allow overriding default options for preview requests" do
expected_body = "{\"id\": \"acc_123\"}"
stripe_version_override = "2022-11-15"
req = nil

stub_request(:post, "#{Stripe.api_base}/v2/accounts")
.with { |request| req = request }
.to_return(body: expected_body)

resp = Stripe::Preview.post("/v2/accounts", {}, { stripe_version: stripe_version_override })

assert_equal "application/json", req.headers["Content-Type"]
assert_equal stripe_version_override, req.headers["Stripe-Version"]
assert_equal expected_body, resp.http_body
end

should "allow setting stripe_context for preview requests" do
expected_body = "{\"id\": \"acc_123\"}"
stripe_context = "acc_123"
req = nil

stub_request(:post, "#{Stripe.api_base}/v2/accounts")
.with { |request| req = request }
.to_return(body: expected_body)

Stripe::Preview.post("/v2/accounts", {}, { stripe_context: stripe_context })

assert_equal "application/json", req.headers["Content-Type"]
assert_equal stripe_context, req.headers["Stripe-Context"]
end
end
end
Loading

0 comments on commit 23cbd8f

Please sign in to comment.