Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add raw_request #1217

Merged
merged 21 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
pakrym-stripe marked this conversation as resolved.
Show resolved Hide resolved
# 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 = {}
pakrym-stripe marked this conversation as resolved.
Show resolved Hide resolved
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
52 changes: 36 additions & 16 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,19 +459,20 @@ 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.
query = query_params ? Util.encode_parameters(query_params) : nil

# Encoding body parameters is a little more complex because we may have
# encoding body parameters is a little more complex because we may have
pakrym-stripe marked this conversation as resolved.
Show resolved Hide resolved
# to send a multipart-encoded body. `body_log` is produced separately as
# 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"
pakrym-stripe marked this conversation as resolved.
Show resolved Hide resolved
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