diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c373724d..46b9b6b2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.8" + ".": "0.1.0-alpha.9" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f930c863..d6ac9060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.1.0-alpha.9 (2025-02-18) + +Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/orbcorp/orb-ruby/compare/v0.1.0-alpha.8...v0.1.0-alpha.9) + +### Features + +* support overlapping HTTP requests in same Fiber ([#43](https://github.com/orbcorp/orb-ruby/issues/43)) ([35549e0](https://github.com/orbcorp/orb-ruby/commit/35549e0240188802f66cab3d0c45573631f8baa7)) + + +### Bug Fixes + +* ssl timeout not required when TCP socket open timeout specified ([#44](https://github.com/orbcorp/orb-ruby/issues/44)) ([2734175](https://github.com/orbcorp/orb-ruby/commit/2734175c6c31340d8f6d2d32b41be08dd30249a4)) + + +### Chores + +* enable full pagination tests ([#41](https://github.com/orbcorp/orb-ruby/issues/41)) ([e70e98b](https://github.com/orbcorp/orb-ruby/commit/e70e98b41b346edcee0076741f11f8b8a186c33e)) +* **internal:** codegen related update ([#42](https://github.com/orbcorp/orb-ruby/issues/42)) ([f34e03e](https://github.com/orbcorp/orb-ruby/commit/f34e03eb511117f21282716401ba8c129d0b15e4)) +* **internal:** version bump ([#39](https://github.com/orbcorp/orb-ruby/issues/39)) ([21d40f8](https://github.com/orbcorp/orb-ruby/commit/21d40f8d2177b1ab3e5df755911e39576e4ef11d)) + ## 0.1.0-alpha.8 (2025-02-15) Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/orbcorp/orb-ruby/compare/v0.1.0-alpha.7...v0.1.0-alpha.8) diff --git a/Gemfile.lock b/Gemfile.lock index e9e0698d..1823b0ac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GIT PATH remote: . specs: - orb (0.1.0.pre.alpha.8) + orb (0.1.0.pre.alpha.9) connection_pool GEM @@ -87,13 +87,13 @@ GEM parser (>= 3.3.1.0) ruby-progressbar (1.13.0) securerandom (0.4.1) - sorbet (0.5.11829) - sorbet-static (= 0.5.11829) - sorbet-runtime (0.5.11829) - sorbet-static (0.5.11829-x86_64-linux) - sorbet-static-and-runtime (0.5.11829) - sorbet (= 0.5.11829) - sorbet-runtime (= 0.5.11829) + sorbet (0.5.11834) + sorbet-static (= 0.5.11834) + sorbet-runtime (0.5.11834) + sorbet-static (0.5.11834-x86_64-linux) + sorbet-static-and-runtime (0.5.11834) + sorbet (= 0.5.11834) + sorbet-runtime (= 0.5.11834) spoom (1.1.16) sorbet (>= 0.5.10187) sorbet-runtime (>= 0.5.9204) diff --git a/lib/orb/base_client.rb b/lib/orb/base_client.rb index 679c3fc8..1bacb335 100644 --- a/lib/orb/base_client.rb +++ b/lib/orb/base_client.rb @@ -126,10 +126,16 @@ def initialize( path = Orb::Util.interpolate_path(uninterpolated_path) + query = Orb::Util.deep_merge( + req[:query].to_h, + opts[:extra_query].to_h + ) + headers = Orb::Util.normalized_headers( @headers, auth_headers, - *[req[:headers], opts[:extra_headers]].compact + req[:headers].to_h, + opts[:extra_headers].to_h ) if @idempotency_header && @@ -157,7 +163,7 @@ def initialize( Orb::Util.deep_merge(*[req[:body], opts[:extra_body]].compact) end - url = Orb::Util.join_parsed_uri(@base_url, {**req, path: path}) + url = Orb::Util.join_parsed_uri(@base_url, {**req, path: path, query: query}) headers, encoded = Orb::Util.encode_content(headers, body) max_retries = opts.fetch(:max_retries, @max_retries) {method: method, url: url, headers: headers, body: encoded, max_retries: max_retries, timeout: timeout} @@ -387,12 +393,10 @@ def initialize( parsed = Orb::Util.decode_content(response) unwrapped = Orb::Util.dig(parsed, req[:unwrap]) - page = req[:page] - model = req.fetch(:model, Orb::Unknown) - case [page, model] - in [Class, Class | Orb::Converter | nil] + case [req[:page], req.fetch(:model, Orb::Unknown)] + in [Class => page, _] page.new(client: self, req: req, headers: response, unwrapped: unwrapped) - in [nil, Class | Orb::Converter] + in [nil, Class | Orb::Converter => model] Orb::Converter.coerce(model, unwrapped) in [nil, nil] unwrapped diff --git a/lib/orb/client.rb b/lib/orb/client.rb index cd1a421f..6a9bcb55 100644 --- a/lib/orb/client.rb +++ b/lib/orb/client.rb @@ -66,6 +66,8 @@ class Client < Orb::BaseClient # @return [Hash{String=>String}] # private def auth_headers + return {} if @api_key.nil? + {"Authorization" => "Bearer #{@api_key}"} end diff --git a/lib/orb/pooled_net_requester.rb b/lib/orb/pooled_net_requester.rb index 74fd1d47..4e9583a6 100644 --- a/lib/orb/pooled_net_requester.rb +++ b/lib/orb/pooled_net_requester.rb @@ -12,29 +12,58 @@ def initialize # @private # # @param url [URL::Generic] - # @param timeout [Float] + # @param blk [Proc] # # @return [ConnectionPool] # - private def get_pool(url) + private def with_pool(url, &blk) origin = Orb::Util.uri_origin(url) - @mutex.synchronize do - @pools[origin] ||= ConnectionPool.new(size: Etc.nprocessors) do - port = - case [url.port, url.scheme] - in [Integer, _] - url.port - in [nil, "http" | "ws"] - Net::HTTP.http_default_port - in [nil, "https" | "wss"] - Net::HTTP.https_default_port - end + key = :"#{self.class.name}-connection_in_use_for_#{origin}" + + return blk.call(make_conn(url)) if Thread.current[key] + + pool = + @mutex.synchronize do + @pools[origin] ||= ConnectionPool.new(size: Etc.nprocessors) do + make_conn(url) + end + end + + pool.with do |conn| + Thread.current[key] = true + + blk.call(conn) + # rubocop:disable Lint/RescueException + rescue Exception => e + # rubocop:enable Lint/RescueException + # should close connection on all errors to ensure no invalid state persists + conn.finish if conn.started? + raise e + ensure + Thread.current[key] = nil + end + end - session = Net::HTTP.new(url.host, port) - session.use_ssl = %w[https wss].include?(url.scheme) - session.max_retries = 0 - session + # @private + # + # @param url [URI::Generic] + # + # @return [Net::HTTP] + # + private def make_conn(url) + port = + case [url.port, url.scheme] + in [Integer, _] + url.port + in [nil, "http" | "ws"] + Net::HTTP.http_default_port + in [nil, "https" | "wss"] + Net::HTTP.https_default_port end + + Net::HTTP.new(url.host, port).tap do + _1.use_ssl = %w[https wss].include?(url.scheme) + _1.max_retries = 0 end end @@ -44,7 +73,7 @@ def initialize # @option req [Symbol] :method # @option req [URI::Generic] :url # @option req [Hash{String => String}] :headers - # @option req [String, Hash] :body + # @option req [String, Hash, IO, StringIO] :body # @option req [Float] :timeout # # @return [Net::HTTPResponse] @@ -55,7 +84,7 @@ def execute(req) request = Net::HTTPGenericRequest.new( method.to_s.upcase, !body.nil?, - ![:head, :options].include?(method), + method != :head, url.to_s ) @@ -67,28 +96,30 @@ def execute(req) request.body_stream = body end - # This timeout is for acquiring a connection from the pool - # The default 5 seconds seems too short, lets just have a nearly unbounded queue for now - # - # TODO: revisit this around granular timeout / concurrency control - get_pool(url).with(timeout: 600) do |conn| + with_pool(url) do |conn| + make_request(conn, request, timeout) + end + end + + # @private + # + # @param conn [Net::HTTP] + # @param request [Net::HTTPGenericRequest] + # @param timeout [Float] + # + # @return [Net::HTTPResponse] + # + private def make_request(conn, request, timeout) + unless conn.started? conn.open_timeout = timeout - conn.read_timeout = timeout - conn.write_timeout = timeout - conn.continue_timeout = timeout + conn.start + end - conn.start unless conn.started? + conn.read_timeout = timeout + conn.write_timeout = timeout + conn.continue_timeout = timeout - conn.request(request) - # rubocop:disable Lint/RescueException - rescue Exception => e - # rubocop:enable Lint/RescueException - # should close connection on all errors to ensure no invalid state persists - conn.finish if conn.started? - raise e - end - rescue ConnectionPool::TimeoutError - raise Orb::APITimeoutError.new(url: url) + conn.request(request) end end end diff --git a/lib/orb/util.rb b/lib/orb/util.rb index 8b95e3c5..893860d5 100644 --- a/lib/orb/util.rb +++ b/lib/orb/util.rb @@ -292,8 +292,6 @@ def self.unparse_uri(parsed) # # @option rhs [Hash{String=>Array}] :query # - # @option rhs [Hash{String=>Array}] :extra_query - # # @return [URI::Generic] # def self.join_parsed_uri(lhs, rhs) @@ -307,7 +305,7 @@ def self.join_parsed_uri(lhs, rhs) query = deep_merge( joined.path == base_path ? base_query : {}, parsed_query, - *rhs.values_at(:query, :extra_query).compact, + rhs[:query].to_h, concat: true ) @@ -327,12 +325,12 @@ def self.decode_query(query) # @private # - # @param query [Hash{String=>Array, String, nil}] + # @param query [Hash{String=>Array, String, nil}, nil] # # @return [String, nil] # def self.encode_query(query) - query.empty? ? nil : URI.encode_www_form(query) + query.to_h.empty? ? nil : URI.encode_www_form(query) end # @private diff --git a/lib/orb/version.rb b/lib/orb/version.rb index 9622e37e..4ca5cfb9 100644 --- a/lib/orb/version.rb +++ b/lib/orb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Orb - VERSION = "0.1.0-alpha.8" + VERSION = "0.1.0-alpha.9" end diff --git a/rbi/lib/orb/client.rbi b/rbi/lib/orb/client.rbi index cdfc8989..2b6b5098 100644 --- a/rbi/lib/orb/client.rbi +++ b/rbi/lib/orb/client.rbi @@ -70,7 +70,7 @@ module Orb def dimensional_price_groups end - sig { returns(T::Hash[String, String]) } + sig { override.returns(T::Hash[String, String]) } private def auth_headers end diff --git a/rbi/lib/orb/util.rbi b/rbi/lib/orb/util.rbi index 5d0c0b88..a9e92304 100644 --- a/rbi/lib/orb/util.rbi +++ b/rbi/lib/orb/util.rbi @@ -94,7 +94,12 @@ module Orb end sig do - params(query: T::Hash[String, T.nilable(T.any(T::Array[String], String))]).returns(T.nilable(String)) + params( + query: T.nilable( + T::Hash[String, + T.nilable(T.any(T::Array[String], String))] + ) + ).returns(T.nilable(String)) end def self.encode_query(query) end diff --git a/rbi/lib/orb/version.rbi b/rbi/lib/orb/version.rbi index 5c234b7a..f11bbaa1 100644 --- a/rbi/lib/orb/version.rbi +++ b/rbi/lib/orb/version.rbi @@ -1,5 +1,5 @@ # typed: strong module Orb - VERSION = "0.1.0-alpha.8" + VERSION = "0.1.0-alpha.9" end diff --git a/sig/orb/util.rbs b/sig/orb/util.rbs index 16584274..0034fca6 100644 --- a/sig/orb/util.rbs +++ b/sig/orb/util.rbs @@ -60,7 +60,7 @@ module Orb def self?.decode_query: (String? query) -> ::Hash[String, ::Array[String]] def self?.encode_query: ( - ::Hash[String, (::Array[String] | String)?] query + ::Hash[String, (::Array[String] | String)?]? query ) -> String? def self?.normalized_headers: ( diff --git a/sig/orb/version.rbs b/sig/orb/version.rbs index 69d63108..d0f7f695 100644 --- a/sig/orb/version.rbs +++ b/sig/orb/version.rbs @@ -1,3 +1,3 @@ module Orb - VERSION: "0.1.0-alpha.7" + VERSION: "0.1.0-alpha.8" end diff --git a/test/orb/util_test.rb b/test/orb/util_test.rb index 6404abe2..091b7c88 100644 --- a/test/orb/util_test.rb +++ b/test/orb/util_test.rb @@ -114,15 +114,12 @@ def test_joining_uris Orb::Util.parse_uri("h://a.b/c?d=e") ], [ - "h://a.b/c?d=e&f=g", + "h://a.b/c?d=e", "h://nope", { host: "a.b", path: "/c", - query: {"d" => ["e"]}, - extra_query: { - "f" => ["g"] - } + query: {"d" => ["e"]} } ] ]