From c664f2fb5019cabca07ce026410d6951f9412d69 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 02:46:47 +0000 Subject: [PATCH 01/13] chore(internal): version bump --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8e35716f..1dd82a82 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GIT PATH remote: . specs: - orb-billing (0.1.2) + orb-billing (0.1.3) connection_pool GEM From 2b99ae2ea108ad4e6227bd5ab7e9a7dd064a8ded Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 13 Apr 2025 07:00:21 +0000 Subject: [PATCH 02/13] chore(internal): codegen related update --- lib/orb/internal.rb | 6 ++- lib/orb/internal/page.rb | 13 +++++-- lib/orb/internal/transport/base_client.rb | 2 + lib/orb/internal/type/array_of.rb | 15 +++++++- lib/orb/internal/type/base_model.rb | 38 ++++++++++++++++--- lib/orb/internal/type/base_page.rb | 1 + lib/orb/internal/type/boolean.rb | 2 + lib/orb/internal/type/converter.rb | 24 ++++++++++++ lib/orb/internal/type/enum.rb | 13 +++++++ lib/orb/internal/type/hash_of.rb | 15 +++++++- lib/orb/internal/type/io_like.rb | 2 + lib/orb/internal/type/union.rb | 13 +++++++ lib/orb/internal/type/unknown.rb | 2 + rbi/lib/orb/internal.rbi | 2 +- rbi/lib/orb/internal/page.rbi | 1 + .../orb/internal/transport/base_client.rbi | 1 + rbi/lib/orb/internal/type/array_of.rbi | 4 ++ rbi/lib/orb/internal/type/base_model.rbi | 7 ++++ rbi/lib/orb/internal/type/converter.rbi | 8 ++++ rbi/lib/orb/internal/type/enum.rbi | 4 ++ rbi/lib/orb/internal/type/hash_of.rbi | 4 ++ rbi/lib/orb/internal/type/union.rbi | 4 ++ sig/orb/internal.rbs | 2 +- sig/orb/internal/type/array_of.rbs | 2 + sig/orb/internal/type/base_model.rbs | 2 + sig/orb/internal/type/converter.rbs | 4 ++ sig/orb/internal/type/enum.rbs | 2 + sig/orb/internal/type/hash_of.rbs | 2 + sig/orb/internal/type/union.rbs | 2 + 29 files changed, 183 insertions(+), 14 deletions(-) diff --git a/lib/orb/internal.rb b/lib/orb/internal.rb index 0b2b7bbb..b67eff4e 100644 --- a/lib/orb/internal.rb +++ b/lib/orb/internal.rb @@ -3,6 +3,10 @@ module Orb # @api private module Internal - OMIT = Object.new.freeze + OMIT = + Object.new.tap do + _1.define_singleton_method(:inspect) { "#<#{Orb::Internal}::OMIT>" } + end + .freeze end end diff --git a/lib/orb/internal/page.rb b/lib/orb/internal/page.rb index 8d7c49fa..df354031 100644 --- a/lib/orb/internal/page.rb +++ b/lib/orb/internal/page.rb @@ -30,11 +30,10 @@ class Page # @param page_data [Hash{Symbol=>Object}] def initialize(client:, req:, headers:, page_data:) super - model = req.fetch(:model) case page_data in {data: Array | nil => data} - @data = data&.map { Orb::Internal::Type::Converter.coerce(model, _1) } + @data = data&.map { Orb::Internal::Type::Converter.coerce(@model, _1) } else end @@ -73,18 +72,24 @@ def auto_paging_each(&blk) unless block_given? raise ArgumentError.new("A block must be given to ##{__method__}") end + page = self loop do - page.data&.each { blk.call(_1) } + page.data&.each(&blk) + break unless page.next_page? page = page.next_page end end + # @api private + # # @return [String] def inspect # rubocop:disable Layout/LineLength - "#<#{self.class}:0x#{object_id.to_s(16)} data=#{data.inspect} pagination_metadata=#{pagination_metadata.inspect}>" + model = Orb::Internal::Type::Converter.inspect(@model, depth: 1) + + "#<#{self.class}[#{model}]:0x#{object_id.to_s(16)} pagination_metadata=#{pagination_metadata.inspect}>" # rubocop:enable Layout/LineLength end diff --git a/lib/orb/internal/transport/base_client.rb b/lib/orb/internal/transport/base_client.rb index 83d779fc..e7dde9e9 100644 --- a/lib/orb/internal/transport/base_client.rb +++ b/lib/orb/internal/transport/base_client.rb @@ -460,6 +460,8 @@ def request(req) end end + # @api private + # # @return [String] def inspect # rubocop:disable Layout/LineLength diff --git a/lib/orb/internal/type/array_of.rb b/lib/orb/internal/type/array_of.rb index bcbf948e..8d3fc097 100644 --- a/lib/orb/internal/type/array_of.rb +++ b/lib/orb/internal/type/array_of.rb @@ -13,6 +13,8 @@ module Type class ArrayOf include Orb::Internal::Type::Converter + private_class_method :new + # @param type_info [Hash{Symbol=>Object}, Proc, Orb::Internal::Type::Converter, Class] # # @param spec [Hash{Symbol=>Object}] . @@ -120,7 +122,18 @@ def dump(value, state:) # @option spec [Boolean] :"nil?" def initialize(type_info, spec = {}) @item_type_fn = Orb::Internal::Type::Converter.type_info(type_info || spec) - @nilable = spec[:nil?] + @nilable = spec.fetch(:nil?, false) + end + + # @api private + # + # @param depth [Integer] + # + # @return [String] + def inspect(depth: 0) + # rubocop:disable Layout/LineLength + "#{self.class}[#{[Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ), nilable? ? 'nil' : nil].compact.join(' | ')}]" + # rubocop:enable Layout/LineLength end end end diff --git a/lib/orb/internal/type/base_model.rb b/lib/orb/internal/type/base_model.rb index 41cba58c..ed167976 100644 --- a/lib/orb/internal/type/base_model.rb +++ b/lib/orb/internal/type/base_model.rb @@ -63,7 +63,7 @@ def fields setter = "#{name_sym}=" api_name = info.fetch(:api_name, name_sym) - nilable = info[:nil?] + nilable = info.fetch(:nil?, false) const = required && !nilable ? info.fetch(:const, Orb::Internal::OMIT) : Orb::Internal::OMIT [name_sym, setter].each { undef_method(_1) } if known_fields.key?(name_sym) @@ -361,14 +361,42 @@ def initialize(data = {}) end end + class << self + # @api private + # + # @param depth [Integer] + # + # @return [String] + def inspect(depth: 0) + return super() if depth.positive? + + depth = depth.succ + deferred = fields.transform_values do |field| + type, required, nilable = field.fetch_values(:type, :required, :nilable) + -> do + [ + Orb::Internal::Type::Converter.inspect(type, depth: depth), + !required || nilable ? "nil" : nil + ].compact.join(" | ") + end + .tap { _1.define_singleton_method(:inspect) { call } } + end + + "#{name}[#{deferred.inspect}]" + end + end + + # @api private + # # @return [String] def inspect - rows = self.class.known_fields.keys.map do - "#{_1}=#{@data.key?(_1) ? public_send(_1) : ''}" + rows = @data.map do + "#{_1}=#{self.class.known_fields.key?(_1) ? public_send(_1).inspect : ''}" rescue Orb::Errors::ConversionError - "#{_1}=#{@data.fetch(_1)}" + "#{_1}=#{_2.inspect}" end - "#<#{self.class.name}:0x#{object_id.to_s(16)} #{rows.join(' ')}>" + + "#<#{self.class}:0x#{object_id.to_s(16)} #{rows.join(' ')}>" end end end diff --git a/lib/orb/internal/type/base_page.rb b/lib/orb/internal/type/base_page.rb index ad092fd2..009ecf00 100644 --- a/lib/orb/internal/type/base_page.rb +++ b/lib/orb/internal/type/base_page.rb @@ -36,6 +36,7 @@ def to_enum = super(:auto_paging_each) def initialize(client:, req:, headers:, page_data:) @client = client @req = req + @model = req.fetch(:model) super() end diff --git a/lib/orb/internal/type/boolean.rb b/lib/orb/internal/type/boolean.rb index d1a6b900..c05da5d9 100644 --- a/lib/orb/internal/type/boolean.rb +++ b/lib/orb/internal/type/boolean.rb @@ -11,6 +11,8 @@ module Type class Boolean extend Orb::Internal::Type::Converter + private_class_method :new + # @param other [Object] # # @return [Boolean] diff --git a/lib/orb/internal/type/converter.rb b/lib/orb/internal/type/converter.rb index 04896cad..3675e242 100644 --- a/lib/orb/internal/type/converter.rb +++ b/lib/orb/internal/type/converter.rb @@ -49,6 +49,15 @@ def dump(value, state:) end end + # @api private + # + # @param depth [Integer] + # + # @return [String] + def inspect(depth: 0) + super() + end + # rubocop:enable Lint/UnusedMethodArgument class << self @@ -240,6 +249,21 @@ def dump(target, value, state: {can_retry: true}) Orb::Internal::Type::Unknown.dump(value, state: state) end end + + # @api private + # + # @param target [Object] + # @param depth [Integer] + # + # @return [String] + def inspect(target, depth:) + case target + in Orb::Internal::Type::Converter + target.inspect(depth: depth.succ) + else + target.inspect + end + end end end end diff --git a/lib/orb/internal/type/enum.rb b/lib/orb/internal/type/enum.rb index bd9592d0..c62c42cc 100644 --- a/lib/orb/internal/type/enum.rb +++ b/lib/orb/internal/type/enum.rb @@ -103,6 +103,19 @@ def coerce(value, state:) # # # # @return [Symbol, Object] # def dump(value, state:) = super + + # @api private + # + # @param depth [Integer] + # + # @return [String] + def inspect(depth: 0) + # rubocop:disable Layout/LineLength + return super() if depth.positive? + + "#{name}[#{values.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) }.join(' | ')}]" + # rubocop:enable Layout/LineLength + end end end end diff --git a/lib/orb/internal/type/hash_of.rb b/lib/orb/internal/type/hash_of.rb index 44173458..8699ebba 100644 --- a/lib/orb/internal/type/hash_of.rb +++ b/lib/orb/internal/type/hash_of.rb @@ -13,6 +13,8 @@ module Type class HashOf include Orb::Internal::Type::Converter + private_class_method :new + # @param type_info [Hash{Symbol=>Object}, Proc, Orb::Internal::Type::Converter, Class] # # @param spec [Hash{Symbol=>Object}] . @@ -140,7 +142,18 @@ def dump(value, state:) # @option spec [Boolean] :"nil?" def initialize(type_info, spec = {}) @item_type_fn = Orb::Internal::Type::Converter.type_info(type_info || spec) - @nilable = spec[:nil?] + @nilable = spec.fetch(:nil?, false) + end + + # @api private + # + # @param depth [Integer] + # + # @return [String] + def inspect(depth: 0) + # rubocop:disable Layout/LineLength + "#{self.class}[#{[Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ), nilable? ? 'nil' : nil].compact.join(' | ')}]" + # rubocop:enable Layout/LineLength end end end diff --git a/lib/orb/internal/type/io_like.rb b/lib/orb/internal/type/io_like.rb index fe821f55..7c5c56c7 100644 --- a/lib/orb/internal/type/io_like.rb +++ b/lib/orb/internal/type/io_like.rb @@ -11,6 +11,8 @@ module Type class IOLike extend Orb::Internal::Type::Converter + private_class_method :new + # @param other [Object] # # @return [Boolean] diff --git a/lib/orb/internal/type/union.rb b/lib/orb/internal/type/union.rb index 36474e0c..a3672a6d 100644 --- a/lib/orb/internal/type/union.rb +++ b/lib/orb/internal/type/union.rb @@ -225,6 +225,19 @@ def dump(value, state:) # rubocop:enable Style/CaseEquality # rubocop:enable Style/HashEachMethods + + # @api private + # + # @param depth [Integer] + # + # @return [String] + def inspect(depth: 0) + # rubocop:disable Layout/LineLength + return super() if depth.positive? + + "#{name}[#{variants.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) }.join(' | ')}]" + # rubocop:enable Layout/LineLength + end end end end diff --git a/lib/orb/internal/type/unknown.rb b/lib/orb/internal/type/unknown.rb index 36966379..0110bf0a 100644 --- a/lib/orb/internal/type/unknown.rb +++ b/lib/orb/internal/type/unknown.rb @@ -13,6 +13,8 @@ class Unknown # rubocop:disable Lint/UnusedMethodArgument + private_class_method :new + # @param other [Object] # # @return [Boolean] diff --git a/rbi/lib/orb/internal.rbi b/rbi/lib/orb/internal.rbi index 3041df91..02b59bf4 100644 --- a/rbi/lib/orb/internal.rbi +++ b/rbi/lib/orb/internal.rbi @@ -7,6 +7,6 @@ module Orb # this alias might be refined in the future. AnyHash = T.type_alias { T::Hash[Symbol, T.anything] } - OMIT = T.let(T.anything, T.anything) + OMIT = T.let(Object.new.freeze, T.anything) end end diff --git a/rbi/lib/orb/internal/page.rbi b/rbi/lib/orb/internal/page.rbi index 8309312f..34463855 100644 --- a/rbi/lib/orb/internal/page.rbi +++ b/rbi/lib/orb/internal/page.rbi @@ -13,6 +13,7 @@ module Orb sig { returns(PaginationMetadata) } attr_accessor :pagination_metadata + # @api private sig { returns(String) } def inspect; end diff --git a/rbi/lib/orb/internal/transport/base_client.rbi b/rbi/lib/orb/internal/transport/base_client.rbi index cbcd1c95..3b8a885b 100644 --- a/rbi/lib/orb/internal/transport/base_client.rbi +++ b/rbi/lib/orb/internal/transport/base_client.rbi @@ -187,6 +187,7 @@ module Orb model: Orb::Internal::Type::Unknown, options: {} ); end + # @api private sig { returns(String) } def inspect; end end diff --git a/rbi/lib/orb/internal/type/array_of.rbi b/rbi/lib/orb/internal/type/array_of.rbi index d9ef3137..eba67eb4 100644 --- a/rbi/lib/orb/internal/type/array_of.rbi +++ b/rbi/lib/orb/internal/type/array_of.rbi @@ -78,6 +78,10 @@ module Orb .void end def initialize(type_info, spec = {}); end + + # @api private + sig(:final) { params(depth: Integer).returns(String) } + def inspect(depth: 0); end end end end diff --git a/rbi/lib/orb/internal/type/base_model.rbi b/rbi/lib/orb/internal/type/base_model.rbi index f525170c..70d21a00 100644 --- a/rbi/lib/orb/internal/type/base_model.rbi +++ b/rbi/lib/orb/internal/type/base_model.rbi @@ -185,6 +185,13 @@ module Orb sig { params(data: T.any(T::Hash[Symbol, T.anything], T.self_type)).returns(T.attached_class) } def self.new(data = {}); end + class << self + # @api private + sig { params(depth: Integer).returns(String) } + def inspect(depth: 0); end + end + + # @api private sig { returns(String) } def inspect; end end diff --git a/rbi/lib/orb/internal/type/converter.rbi b/rbi/lib/orb/internal/type/converter.rbi index e134df41..1e434053 100644 --- a/rbi/lib/orb/internal/type/converter.rbi +++ b/rbi/lib/orb/internal/type/converter.rbi @@ -35,6 +35,10 @@ module Orb end def dump(value, state:); end + # @api private + sig { params(depth: Integer).returns(String) } + def inspect(depth: 0); end + class << self # @api private sig do @@ -106,6 +110,10 @@ module Orb .returns(T.anything) end def self.dump(target, value, state: {can_retry: true}); end + + # @api private + sig { params(target: T.anything, depth: Integer).returns(String) } + def self.inspect(target, depth:); end end end end diff --git a/rbi/lib/orb/internal/type/enum.rbi b/rbi/lib/orb/internal/type/enum.rbi index b8e5c81d..6eb41366 100644 --- a/rbi/lib/orb/internal/type/enum.rbi +++ b/rbi/lib/orb/internal/type/enum.rbi @@ -57,6 +57,10 @@ module Orb .returns(T.any(Symbol, T.anything)) end def dump(value, state:); end + + # @api private + sig { params(depth: Integer).returns(String) } + def inspect(depth: 0); end end end end diff --git a/rbi/lib/orb/internal/type/hash_of.rbi b/rbi/lib/orb/internal/type/hash_of.rbi index 6c482004..57516bdd 100644 --- a/rbi/lib/orb/internal/type/hash_of.rbi +++ b/rbi/lib/orb/internal/type/hash_of.rbi @@ -76,6 +76,10 @@ module Orb .void end def initialize(type_info, spec = {}); end + + # @api private + sig(:final) { params(depth: Integer).returns(String) } + def inspect(depth: 0); end end end end diff --git a/rbi/lib/orb/internal/type/union.rbi b/rbi/lib/orb/internal/type/union.rbi index 18ce6879..2675dc8b 100644 --- a/rbi/lib/orb/internal/type/union.rbi +++ b/rbi/lib/orb/internal/type/union.rbi @@ -62,6 +62,10 @@ module Orb ).returns(T.anything) end def dump(value, state:); end + + # @api private + sig { params(depth: Integer).returns(String) } + def inspect(depth: 0); end end end end diff --git a/sig/orb/internal.rbs b/sig/orb/internal.rbs index e6e6fd6f..4ebb487b 100644 --- a/sig/orb/internal.rbs +++ b/sig/orb/internal.rbs @@ -1,5 +1,5 @@ module Orb module Internal - OMIT: top + OMIT: Object end end diff --git a/sig/orb/internal/type/array_of.rbs b/sig/orb/internal/type/array_of.rbs index f37088f6..20dac6aa 100644 --- a/sig/orb/internal/type/array_of.rbs +++ b/sig/orb/internal/type/array_of.rbs @@ -35,6 +35,8 @@ module Orb | Orb::Internal::Type::Converter::input type_info, ?::Hash[Symbol, top] spec ) -> void + + def inspect: (?depth: Integer) -> String end end end diff --git a/sig/orb/internal/type/base_model.rbs b/sig/orb/internal/type/base_model.rbs index bae69564..23bf32b7 100644 --- a/sig/orb/internal/type/base_model.rbs +++ b/sig/orb/internal/type/base_model.rbs @@ -75,6 +75,8 @@ module Orb def initialize: (?::Hash[Symbol, top] | self data) -> void + def self.inspect: (?depth: Integer) -> String + def inspect: -> String end end diff --git a/sig/orb/internal/type/converter.rbs b/sig/orb/internal/type/converter.rbs index e3fb8663..018efc59 100644 --- a/sig/orb/internal/type/converter.rbs +++ b/sig/orb/internal/type/converter.rbs @@ -23,6 +23,8 @@ module Orb state: Orb::Internal::Type::Converter::dump_state ) -> top + def inspect: (?depth: Integer) -> String + def self.type_info: ( { const: (nil | bool | Integer | Float | Symbol)?, @@ -44,6 +46,8 @@ module Orb top value, ?state: Orb::Internal::Type::Converter::dump_state ) -> top + + def self.inspect: (top target, depth: Integer) -> String end end end diff --git a/sig/orb/internal/type/enum.rbs b/sig/orb/internal/type/enum.rbs index 61541787..f87e8c1d 100644 --- a/sig/orb/internal/type/enum.rbs +++ b/sig/orb/internal/type/enum.rbs @@ -21,6 +21,8 @@ module Orb Symbol | top value, state: Orb::Internal::Type::Converter::dump_state ) -> (Symbol | top) + + def inspect: (?depth: Integer) -> String end end end diff --git a/sig/orb/internal/type/hash_of.rbs b/sig/orb/internal/type/hash_of.rbs index 54691d6e..014ffc68 100644 --- a/sig/orb/internal/type/hash_of.rbs +++ b/sig/orb/internal/type/hash_of.rbs @@ -35,6 +35,8 @@ module Orb | Orb::Internal::Type::Converter::input type_info, ?::Hash[Symbol, top] spec ) -> void + + def inspect: (?depth: Integer) -> String end end end diff --git a/sig/orb/internal/type/union.rbs b/sig/orb/internal/type/union.rbs index e7c8de0c..e907f7a9 100644 --- a/sig/orb/internal/type/union.rbs +++ b/sig/orb/internal/type/union.rbs @@ -39,6 +39,8 @@ module Orb top value, state: Orb::Internal::Type::Converter::dump_state ) -> top + + def inspect: (?depth: Integer) -> String end end end From 9297be8eaa459f14cb0b4118066ecd59877686ab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 13 Apr 2025 09:10:15 +0000 Subject: [PATCH 03/13] chore(internal): minor touch ups on sdk internals --- lib/orb/internal/page.rb | 55 +++++++++++------------ lib/orb/internal/transport/base_client.rb | 14 ++++-- lib/orb/internal/type/array_of.rb | 10 +++-- lib/orb/internal/type/enum.rb | 6 +-- lib/orb/internal/type/hash_of.rb | 10 +++-- lib/orb/internal/type/union.rb | 6 +-- 6 files changed, 55 insertions(+), 46 deletions(-) diff --git a/lib/orb/internal/page.rb b/lib/orb/internal/page.rb index df354031..d57a2ad9 100644 --- a/lib/orb/internal/page.rb +++ b/lib/orb/internal/page.rb @@ -22,32 +22,6 @@ class Page # @return [PaginationMetadata] attr_accessor :pagination_metadata - # @api private - # - # @param client [Orb::Internal::Transport::BaseClient] - # @param req [Hash{Symbol=>Object}] - # @param headers [Hash{String=>String}, Net::HTTPHeader] - # @param page_data [Hash{Symbol=>Object}] - def initialize(client:, req:, headers:, page_data:) - super - - case page_data - in {data: Array | nil => data} - @data = data&.map { Orb::Internal::Type::Converter.coerce(@model, _1) } - else - end - - case page_data - in {pagination_metadata: Hash | nil => pagination_metadata} - @pagination_metadata = - Orb::Internal::Type::Converter.coerce( - Orb::Internal::Page::PaginationMetadata, - pagination_metadata - ) - else - end - end - # @return [Boolean] def next_page? !pagination_metadata&.next_cursor.nil? @@ -82,15 +56,38 @@ def auto_paging_each(&blk) end end + # @api private + # + # @param client [Orb::Internal::Transport::BaseClient] + # @param req [Hash{Symbol=>Object}] + # @param headers [Hash{String=>String}, Net::HTTPHeader] + # @param page_data [Hash{Symbol=>Object}] + def initialize(client:, req:, headers:, page_data:) + super + + case page_data + in {data: Array | nil => data} + @data = data&.map { Orb::Internal::Type::Converter.coerce(@model, _1) } + else + end + case page_data + in {pagination_metadata: Hash | nil => pagination_metadata} + @pagination_metadata = + Orb::Internal::Type::Converter.coerce( + Orb::Internal::Page::PaginationMetadata, + pagination_metadata + ) + else + end + end + # @api private # # @return [String] def inspect - # rubocop:disable Layout/LineLength model = Orb::Internal::Type::Converter.inspect(@model, depth: 1) - "#<#{self.class}[#{model}]:0x#{object_id.to_s(16)} pagination_metadata=#{pagination_metadata.inspect}>" - # rubocop:enable Layout/LineLength + "#<#{self.class}[#{model}]:0x#{object_id.to_s(16)}>" end class PaginationMetadata < Orb::Internal::Type::BaseModel diff --git a/lib/orb/internal/transport/base_client.rb b/lib/orb/internal/transport/base_client.rb index e7dde9e9..49aea075 100644 --- a/lib/orb/internal/transport/base_client.rb +++ b/lib/orb/internal/transport/base_client.rb @@ -93,7 +93,11 @@ def follow_redirect(request, status:, response_headers:) URI.join(url, response_headers["location"]) rescue ArgumentError message = "Server responded with status #{status} but no valid location header." - raise Orb::Errors::APIConnectionError.new(url: url, message: message) + raise Orb::Errors::APIConnectionError.new( + url: url, + response: response_headers, + message: message + ) end request = {**request, url: location} @@ -101,7 +105,11 @@ def follow_redirect(request, status:, response_headers:) case [url.scheme, location.scheme] in ["https", "http"] message = "Tried to redirect to a insecure URL" - raise Orb::Errors::APIConnectionError.new(url: url, message: message) + raise Orb::Errors::APIConnectionError.new( + url: url, + response: response_headers, + message: message + ) else nil end @@ -350,7 +358,7 @@ def initialize( self.class.reap_connection!(status, stream: stream) message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects." - raise Orb::Errors::APIConnectionError.new(url: url, message: message) + raise Orb::Errors::APIConnectionError.new(url: url, response: response, message: message) in 300..399 self.class.reap_connection!(status, stream: stream) diff --git a/lib/orb/internal/type/array_of.rb b/lib/orb/internal/type/array_of.rb index 8d3fc097..d7dc22bd 100644 --- a/lib/orb/internal/type/array_of.rb +++ b/lib/orb/internal/type/array_of.rb @@ -15,6 +15,8 @@ class ArrayOf private_class_method :new + # @overload [](type_info, spec = {}) + # # @param type_info [Hash{Symbol=>Object}, Proc, Orb::Internal::Type::Converter, Class] # # @param spec [Hash{Symbol=>Object}] . @@ -26,7 +28,7 @@ class ArrayOf # @option spec [Proc] :union # # @option spec [Boolean] :"nil?" - def self.[](type_info, spec = {}) = new(type_info, spec) + def self.[](...) = new(...) # @param other [Object] # @@ -131,9 +133,9 @@ def initialize(type_info, spec = {}) # # @return [String] def inspect(depth: 0) - # rubocop:disable Layout/LineLength - "#{self.class}[#{[Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ), nilable? ? 'nil' : nil].compact.join(' | ')}]" - # rubocop:enable Layout/LineLength + items = Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ) + + "#{self.class}[#{[items, nilable? ? 'nil' : nil].compact.join(' | ')}]" end end end diff --git a/lib/orb/internal/type/enum.rb b/lib/orb/internal/type/enum.rb index c62c42cc..9ecc03f6 100644 --- a/lib/orb/internal/type/enum.rb +++ b/lib/orb/internal/type/enum.rb @@ -110,11 +110,11 @@ def coerce(value, state:) # # @return [String] def inspect(depth: 0) - # rubocop:disable Layout/LineLength return super() if depth.positive? - "#{name}[#{values.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) }.join(' | ')}]" - # rubocop:enable Layout/LineLength + members = values.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) } + + "#{name}[#{members.join(' | ')}]" end end end diff --git a/lib/orb/internal/type/hash_of.rb b/lib/orb/internal/type/hash_of.rb index 8699ebba..56073fbc 100644 --- a/lib/orb/internal/type/hash_of.rb +++ b/lib/orb/internal/type/hash_of.rb @@ -15,6 +15,8 @@ class HashOf private_class_method :new + # @overload [](type_info, spec = {}) + # # @param type_info [Hash{Symbol=>Object}, Proc, Orb::Internal::Type::Converter, Class] # # @param spec [Hash{Symbol=>Object}] . @@ -26,7 +28,7 @@ class HashOf # @option spec [Proc] :union # # @option spec [Boolean] :"nil?" - def self.[](type_info, spec = {}) = new(type_info, spec) + def self.[](...) = new(...) # @param other [Object] # @@ -151,9 +153,9 @@ def initialize(type_info, spec = {}) # # @return [String] def inspect(depth: 0) - # rubocop:disable Layout/LineLength - "#{self.class}[#{[Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ), nilable? ? 'nil' : nil].compact.join(' | ')}]" - # rubocop:enable Layout/LineLength + items = Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ) + + "#{self.class}[#{[items, nilable? ? 'nil' : nil].compact.join(' | ')}]" end end end diff --git a/lib/orb/internal/type/union.rb b/lib/orb/internal/type/union.rb index a3672a6d..021c4ec3 100644 --- a/lib/orb/internal/type/union.rb +++ b/lib/orb/internal/type/union.rb @@ -232,11 +232,11 @@ def dump(value, state:) # # @return [String] def inspect(depth: 0) - # rubocop:disable Layout/LineLength return super() if depth.positive? - "#{name}[#{variants.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) }.join(' | ')}]" - # rubocop:enable Layout/LineLength + members = variants.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) } + + "#{name}[#{members.join(' | ')}]" end end end From 9dc6b52d1bfa57e3fab328bf8673872522ab7f25 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 13:10:03 +0000 Subject: [PATCH 04/13] chore(internal): loosen internal type restrictions --- lib/orb/internal/type/enum.rb | 6 +-- lib/orb/internal/type/union.rb | 4 +- rbi/lib/orb/internal/type/array_of.rbi | 19 ++++---- rbi/lib/orb/internal/type/boolean.rbi | 9 ++-- rbi/lib/orb/internal/type/hash_of.rbi | 19 ++++---- rbi/lib/orb/internal/type/io_like.rbi | 9 ++-- rbi/lib/orb/internal/type/unknown.rbi | 9 ++-- test/orb/internal/type/base_model_test.rb | 54 ++++++++++++++++++++++- 8 files changed, 86 insertions(+), 43 deletions(-) diff --git a/lib/orb/internal/type/enum.rb b/lib/orb/internal/type/enum.rb index 9ecc03f6..2e386e16 100644 --- a/lib/orb/internal/type/enum.rb +++ b/lib/orb/internal/type/enum.rb @@ -58,9 +58,9 @@ def ===(other) = values.include?(other) # # @return [Boolean] def ==(other) - # rubocop:disable Layout/LineLength - other.is_a?(Module) && other.singleton_class <= Orb::Internal::Type::Enum && other.values.to_set == values.to_set - # rubocop:enable Layout/LineLength + # rubocop:disable Style/CaseEquality + Orb::Internal::Type::Enum === other && other.values.to_set == values.to_set + # rubocop:enable Style/CaseEquality end # @api private diff --git a/lib/orb/internal/type/union.rb b/lib/orb/internal/type/union.rb index 021c4ec3..7d9357a3 100644 --- a/lib/orb/internal/type/union.rb +++ b/lib/orb/internal/type/union.rb @@ -140,9 +140,7 @@ def ===(other) # # @return [Boolean] def ==(other) - # rubocop:disable Layout/LineLength - other.is_a?(Module) && other.singleton_class <= Orb::Internal::Type::Union && other.derefed_variants == derefed_variants - # rubocop:enable Layout/LineLength + Orb::Internal::Type::Union === other && other.derefed_variants == derefed_variants end # @api private diff --git a/rbi/lib/orb/internal/type/array_of.rbi b/rbi/lib/orb/internal/type/array_of.rbi index eba67eb4..1f8fd4a7 100644 --- a/rbi/lib/orb/internal/type/array_of.rbi +++ b/rbi/lib/orb/internal/type/array_of.rbi @@ -10,11 +10,10 @@ module Orb include Orb::Internal::Type::Converter abstract! - final! Elem = type_member(:out) - sig(:final) do + sig do params( type_info: T.any( Orb::Internal::AnyHash, @@ -27,14 +26,14 @@ module Orb end def self.[](type_info, spec = {}); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def ===(other); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def ==(other); end # @api private - sig(:final) do + sig do override .params(value: T.any( T::Array[T.anything], @@ -46,7 +45,7 @@ module Orb def coerce(value, state:); end # @api private - sig(:final) do + sig do override .params(value: T.any( T::Array[T.anything], @@ -58,15 +57,15 @@ module Orb def dump(value, state:); end # @api private - sig(:final) { returns(Elem) } + sig { returns(Elem) } protected def item_type; end # @api private - sig(:final) { returns(T::Boolean) } + sig { returns(T::Boolean) } protected def nilable?; end # @api private - sig(:final) do + sig do params( type_info: T.any( Orb::Internal::AnyHash, @@ -80,7 +79,7 @@ module Orb def initialize(type_info, spec = {}); end # @api private - sig(:final) { params(depth: Integer).returns(String) } + sig { params(depth: Integer).returns(String) } def inspect(depth: 0); end end end diff --git a/rbi/lib/orb/internal/type/boolean.rbi b/rbi/lib/orb/internal/type/boolean.rbi index 51da0ac4..cc88e526 100644 --- a/rbi/lib/orb/internal/type/boolean.rbi +++ b/rbi/lib/orb/internal/type/boolean.rbi @@ -10,17 +10,16 @@ module Orb extend Orb::Internal::Type::Converter abstract! - final! - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def self.===(other); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def self.==(other); end class << self # @api private - sig(:final) do + sig do override .params(value: T.any( T::Boolean, @@ -32,7 +31,7 @@ module Orb def coerce(value, state:); end # @api private - sig(:final) do + sig do override .params(value: T.any(T::Boolean, T.anything), state: Orb::Internal::Type::Converter::DumpState) .returns(T.any(T::Boolean, T.anything)) diff --git a/rbi/lib/orb/internal/type/hash_of.rbi b/rbi/lib/orb/internal/type/hash_of.rbi index 57516bdd..8072f046 100644 --- a/rbi/lib/orb/internal/type/hash_of.rbi +++ b/rbi/lib/orb/internal/type/hash_of.rbi @@ -10,11 +10,10 @@ module Orb include Orb::Internal::Type::Converter abstract! - final! Elem = type_member(:out) - sig(:final) do + sig do params( type_info: T.any( Orb::Internal::AnyHash, @@ -27,14 +26,14 @@ module Orb end def self.[](type_info, spec = {}); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def ===(other); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def ==(other); end # @api private - sig(:final) do + sig do override .params( value: T.any(T::Hash[T.anything, T.anything], T.anything), @@ -45,7 +44,7 @@ module Orb def coerce(value, state:); end # @api private - sig(:final) do + sig do override .params( value: T.any(T::Hash[T.anything, T.anything], T.anything), @@ -56,15 +55,15 @@ module Orb def dump(value, state:); end # @api private - sig(:final) { returns(Elem) } + sig { returns(Elem) } protected def item_type; end # @api private - sig(:final) { returns(T::Boolean) } + sig { returns(T::Boolean) } protected def nilable?; end # @api private - sig(:final) do + sig do params( type_info: T.any( Orb::Internal::AnyHash, @@ -78,7 +77,7 @@ module Orb def initialize(type_info, spec = {}); end # @api private - sig(:final) { params(depth: Integer).returns(String) } + sig { params(depth: Integer).returns(String) } def inspect(depth: 0); end end end diff --git a/rbi/lib/orb/internal/type/io_like.rbi b/rbi/lib/orb/internal/type/io_like.rbi index 06ff47b0..47798fbe 100644 --- a/rbi/lib/orb/internal/type/io_like.rbi +++ b/rbi/lib/orb/internal/type/io_like.rbi @@ -10,17 +10,16 @@ module Orb extend Orb::Internal::Type::Converter abstract! - final! - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def self.===(other); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def self.==(other); end class << self # @api private - sig(:final) do + sig do override .params(value: T.any( StringIO, @@ -33,7 +32,7 @@ module Orb def coerce(value, state:); end # @api private - sig(:final) do + sig do override .params( value: T.any(Pathname, StringIO, IO, String, T.anything), diff --git a/rbi/lib/orb/internal/type/unknown.rbi b/rbi/lib/orb/internal/type/unknown.rbi index 2bd89240..93bd4287 100644 --- a/rbi/lib/orb/internal/type/unknown.rbi +++ b/rbi/lib/orb/internal/type/unknown.rbi @@ -10,17 +10,16 @@ module Orb extend Orb::Internal::Type::Converter abstract! - final! - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def self.===(other); end - sig(:final) { params(other: T.anything).returns(T::Boolean) } + sig { params(other: T.anything).returns(T::Boolean) } def self.==(other); end class << self # @api private - sig(:final) do + sig do override.params( value: T.anything, state: Orb::Internal::Type::Converter::CoerceState @@ -29,7 +28,7 @@ module Orb def coerce(value, state:); end # @api private - sig(:final) do + sig do override.params( value: T.anything, state: Orb::Internal::Type::Converter::DumpState diff --git a/test/orb/internal/type/base_model_test.rb b/test/orb/internal/type/base_model_test.rb index 138d5e6b..d33e2616 100644 --- a/test/orb/internal/type/base_model_test.rb +++ b/test/orb/internal/type/base_model_test.rb @@ -154,6 +154,12 @@ def test_dump_retry end class Orb::Test::EnumModelTest < Minitest::Test + class E0 + include Orb::Internal::Type::Enum + + def initialize(*values) = (@values = values) + end + module E1 extend Orb::Internal::Type::Enum @@ -183,6 +189,10 @@ module E4 def test_coerce cases = { + [E0.new, "one"] => [{no: 1}, "one"], + [E0.new(:one), "one"] => [{yes: 1}, :one], + [E0.new(:two), "one"] => [{maybe: 1}, "one"], + # rubocop:disable Lint/BooleanSymbol [E1, true] => [{yes: 1}, true], [E1, false] => [{no: 1}, false], @@ -432,8 +442,10 @@ def test_accessors end class Orb::Test::UnionTest < Minitest::Test - module U0 - extend Orb::Internal::Type::Union + class U0 + include Orb::Internal::Type::Union + + def initialize(*variants) = variants.each { variant(_1) } end module U1 @@ -519,6 +531,11 @@ def test_coerce cases = { [U0, :""] => [{no: 1}, 0, :""], + [U0.new(Integer, Float), "one"] => [{no: 1}, 2, "one"], + [U0.new(Integer, Float), 1.0] => [{yes: 1}, 2, 1.0], + [U0.new({const: :a}), "a"] => [{yes: 1}, 1, :a], + [U0.new({const: :a}), "2"] => [{maybe: 1}, 1, "2"], + [U1, "a"] => [{yes: 1}, 1, :a], [U1, "2"] => [{maybe: 1}, 2, "2"], [U1, :b] => [{maybe: 1}, 2, :b], @@ -556,6 +573,12 @@ def test_coerce end class Orb::Test::BaseModelQoLTest < Minitest::Test + class E0 + include Orb::Internal::Type::Enum + + def initialize(*values) = (@values = values) + end + module E1 extend Orb::Internal::Type::Enum @@ -575,6 +598,26 @@ module E3 B = 3 end + class U0 + include Orb::Internal::Type::Union + + def initialize(*variants) = variants.each { variant(_1) } + end + + module U1 + extend Orb::Internal::Type::Union + + variant String + variant Integer + end + + module U2 + extend Orb::Internal::Type::Union + + variant String + variant Integer + end + class M1 < Orb::Internal::Type::BaseModel required :a, Integer end @@ -592,8 +635,15 @@ def test_equality [Orb::Internal::Type::Unknown, Orb::Internal::Type::Unknown] => true, [Orb::Internal::Type::Boolean, Orb::Internal::Type::Boolean] => true, [Orb::Internal::Type::Unknown, Orb::Internal::Type::Boolean] => false, + [E0.new(:a, :b), E0.new(:a, :b)] => true, + [E0.new(:a, :b), E0.new(:b, :a)] => true, + [E0.new(:a, :b), E0.new(:b, :c)] => false, [E1, E2] => true, [E1, E3] => false, + [U0.new(String, Integer), U0.new(String, Integer)] => true, + [U0.new(String, Integer), U0.new(Integer, String)] => false, + [U0.new(String, Float), U0.new(String, Integer)] => false, + [U1, U2] => true, [M1, M2] => false, [M1, M3] => true } From 202fff413ad9e9be16b67a07270dd4ac2c4f14e4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:07:55 +0000 Subject: [PATCH 05/13] chore(internal): codegen related update --- README.md | 2 +- lib/orb/internal/type/enum.rb | 3 ++- lib/orb/internal/type/union.rb | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 375bca97..fd23c7d2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The Orb Ruby library provides convenient access to the Orb REST API from any Rub ## Documentation -Documentation for released of this gem can be found [on RubyDoc](https://gemdocs.org/gems/orb-billing). +Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/orb-billing). The underlying REST API documentation can be found on [docs.withorb.com](https://docs.withorb.com/reference/api-reference). diff --git a/lib/orb/internal/type/enum.rb b/lib/orb/internal/type/enum.rb index 2e386e16..e7bb80f1 100644 --- a/lib/orb/internal/type/enum.rb +++ b/lib/orb/internal/type/enum.rb @@ -113,8 +113,9 @@ def inspect(depth: 0) return super() if depth.positive? members = values.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) } + prefix = is_a?(Module) ? name : self.class.name - "#{name}[#{members.join(' | ')}]" + "#{prefix}[#{members.join(' | ')}]" end end end diff --git a/lib/orb/internal/type/union.rb b/lib/orb/internal/type/union.rb index 7d9357a3..d907202e 100644 --- a/lib/orb/internal/type/union.rb +++ b/lib/orb/internal/type/union.rb @@ -233,8 +233,9 @@ def inspect(depth: 0) return super() if depth.positive? members = variants.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) } + prefix = is_a?(Module) ? name : self.class.name - "#{name}[#{members.join(' | ')}]" + "#{prefix}[#{members.join(' | ')}]" end end end From a0bfe42c34e09819e4948069670cfdf895cb51af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:16:43 +0000 Subject: [PATCH 06/13] docs: update documentation links to be more uniform --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd23c7d2..f9836f71 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Orb Ruby library provides convenient access to the Orb REST API from any Rub Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/orb-billing). -The underlying REST API documentation can be found on [docs.withorb.com](https://docs.withorb.com/reference/api-reference). +The REST API documentation can be found on [docs.withorb.com](https://docs.withorb.com/reference/api-reference). ## Installation From 9df6d6382e67f6f28f1cedff4ee1c949ffd70237 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:55:39 +0000 Subject: [PATCH 07/13] chore(internal): always run post-processing when formatting when syntax_tree --- .rubocop.yml | 1 + Rakefile | 9 ++++++++- lib/orb/internal/type/enum.rb | 4 +++- lib/orb/internal/type/union.rb | 4 +++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index e924c263..4af3d69a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -244,6 +244,7 @@ Style/SafeNavigation: Style/SignalException: Exclude: - Rakefile + - "**/*.rake" # We use these sparingly, where we anticipate future branches for the # inner conditional. diff --git a/Rakefile b/Rakefile index 4efefa43..8bc487ce 100644 --- a/Rakefile +++ b/Rakefile @@ -59,12 +59,19 @@ multitask(:syntax_tree) do # 2. at label `l1`, join previously annotated line with `class | module` information. pst = sed + [subst, "--"] + success = false + # transform class aliases to type aliases, which syntax tree has no trouble with sh("#{find.shelljoin} | #{pre.shelljoin}") # run syntax tree to format `*.rbs` files - sh("#{find.shelljoin} | #{fmt.shelljoin}") + sh("#{find.shelljoin} | #{fmt.shelljoin}") do + success = _1 + end # transform type aliases back to class aliases sh("#{find.shelljoin} | #{pst.shelljoin}") + + # always run post-processing to remove comment marker + fail unless success end multitask(format: [:ruboformat, :syntax_tree]) diff --git a/lib/orb/internal/type/enum.rb b/lib/orb/internal/type/enum.rb index e7bb80f1..e3ddcc6a 100644 --- a/lib/orb/internal/type/enum.rb +++ b/lib/orb/internal/type/enum.rb @@ -110,7 +110,9 @@ def coerce(value, state:) # # @return [String] def inspect(depth: 0) - return super() if depth.positive? + if depth.positive? + return is_a?(Module) ? super() : self.class.name + end members = values.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) } prefix = is_a?(Module) ? name : self.class.name diff --git a/lib/orb/internal/type/union.rb b/lib/orb/internal/type/union.rb index d907202e..98cd9b31 100644 --- a/lib/orb/internal/type/union.rb +++ b/lib/orb/internal/type/union.rb @@ -230,7 +230,9 @@ def dump(value, state:) # # @return [String] def inspect(depth: 0) - return super() if depth.positive? + if depth.positive? + return is_a?(Module) ? super() : self.class.name + end members = variants.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) } prefix = is_a?(Module) ? name : self.class.name From bb2243a19fbd077687be4b2a4a001e4b7381c54d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 17:32:04 +0000 Subject: [PATCH 08/13] chore(internal): protect SSE parsing pipeline from broken UTF-8 characters --- .../transport/pooled_net_requester.rb | 2 +- lib/orb/internal/util.rb | 45 +++++++++++++++---- rbi/lib/orb/internal/util.rbi | 15 +++++++ sig/orb/internal/util.rbs | 2 + test/orb/internal/util_test.rb | 22 ++++++++- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/lib/orb/internal/transport/pooled_net_requester.rb b/lib/orb/internal/transport/pooled_net_requester.rb index 7db786fb..d9e8ee83 100644 --- a/lib/orb/internal/transport/pooled_net_requester.rb +++ b/lib/orb/internal/transport/pooled_net_requester.rb @@ -149,7 +149,7 @@ def execute(request) break if finished rsp.read_body do |bytes| - y << bytes + y << bytes.force_encoding(Encoding::BINARY) break if finished self.class.calibrate_socket_timeout(conn, deadline) diff --git a/lib/orb/internal/util.rb b/lib/orb/internal/util.rb index 62195f31..6290e5f4 100644 --- a/lib/orb/internal/util.rb +++ b/lib/orb/internal/util.rb @@ -448,7 +448,7 @@ def initialize(src, &blk) else src end - @buf = String.new.b + @buf = String.new @blk = blk end end @@ -460,7 +460,7 @@ class << self # @return [Enumerable] def writable_enum(&blk) Enumerator.new do |y| - buf = String.new.b + buf = String.new y.define_singleton_method(:write) do self << buf.replace(_1) buf.bytesize @@ -582,6 +582,27 @@ def encode_content(headers, body) # @api private # + # https://www.iana.org/assignments/character-sets/character-sets.xhtml + # + # @param content_type [String] + # @param text [String] + def force_charset!(content_type, text:) + charset = /charset=([^;\s]+)/.match(content_type)&.captures&.first + + return unless charset + + begin + encoding = Encoding.find(charset) + text.force_encoding(encoding) + rescue ArgumentError + nil + end + end + + # @api private + # + # Assumes each chunk in stream has `Encoding::BINARY`. + # # @param headers [Hash{String=>String}, Net::HTTPHeader] # @param stream [Enumerable] # @param suppress_error [Boolean] @@ -589,7 +610,7 @@ def encode_content(headers, body) # @raise [JSON::ParserError] # @return [Object] def decode_content(headers, stream:, suppress_error: false) - case headers["content-type"] + case (content_type = headers["content-type"]) in %r{^application/(?:vnd\.api\+)?json} json = stream.to_a.join begin @@ -606,11 +627,10 @@ def decode_content(headers, stream:, suppress_error: false) in %r{^text/event-stream} lines = decode_lines(stream) decode_sse(lines) - in %r{^text/} - stream.to_a.join else - # TODO: parsing other response types - StringIO.new(stream.to_a.join) + text = stream.to_a.join + force_charset!(content_type, text: text) + StringIO.new(text) end end end @@ -675,12 +695,17 @@ def chain_fused(enum, &blk) class << self # @api private # + # Assumes Strings have been forced into having `Encoding::BINARY`. + # + # This decoder is responsible for reassembling lines split across multiple + # fragments. + # # @param enum [Enumerable] # # @return [Enumerable] def decode_lines(enum) re = /(\r\n|\r|\n)/ - buffer = String.new.b + buffer = String.new cr_seen = nil chain_fused(enum) do |y| @@ -711,6 +736,8 @@ def decode_lines(enum) # # https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream # + # Assumes that `lines` has been decoded with `#decode_lines`. + # # @param lines [Enumerable] # # @return [EnumerableObject}>] @@ -734,7 +761,7 @@ def decode_sse(lines) in "event" current.merge!(event: value) in "data" - (current[:data] ||= String.new.b) << (value << "\n") + (current[:data] ||= String.new) << (value << "\n") in "id" unless value.include?("\0") current.merge!(id: value) in "retry" if /^\d+$/ =~ value diff --git a/rbi/lib/orb/internal/util.rbi b/rbi/lib/orb/internal/util.rbi index 02bad0ea..a8284783 100644 --- a/rbi/lib/orb/internal/util.rbi +++ b/rbi/lib/orb/internal/util.rbi @@ -215,6 +215,14 @@ module Orb def encode_content(headers, body); end # @api private + # + # https://www.iana.org/assignments/character-sets/character-sets.xhtml + sig { params(content_type: String, text: String).void } + def force_charset!(content_type, text:); end + + # @api private + # + # Assumes each chunk in stream has `Encoding::BINARY`. sig do params( headers: T.any(T::Hash[String, String], Net::HTTPHeader), @@ -263,12 +271,19 @@ module Orb class << self # @api private + # + # Assumes Strings have been forced into having `Encoding::BINARY`. + # + # This decoder is responsible for reassembling lines split across multiple + # fragments. sig { params(enum: T::Enumerable[String]).returns(T::Enumerable[String]) } def decode_lines(enum); end # @api private # # https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream + # + # Assumes that `lines` has been decoded with `#decode_lines`. sig do params(lines: T::Enumerable[String]).returns(T::Enumerable[Orb::Internal::Util::ServerSentEvent]) end diff --git a/sig/orb/internal/util.rbs b/sig/orb/internal/util.rbs index e0d7f2c4..1488744e 100644 --- a/sig/orb/internal/util.rbs +++ b/sig/orb/internal/util.rbs @@ -120,6 +120,8 @@ module Orb top body ) -> top + def self?.force_charset!: (String content_type, text: String) -> void + def self?.decode_content: ( ::Hash[String, String] headers, stream: Enumerable[String], diff --git a/test/orb/internal/util_test.rb b/test/orb/internal/util_test.rb index c5144373..5c8a2a37 100644 --- a/test/orb/internal/util_test.rb +++ b/test/orb/internal/util_test.rb @@ -368,6 +368,24 @@ def test_close_fused_sse_chain end end +class Orb::Test::UtilContentDecodingTest < Minitest::Test + def test_charset + cases = { + "application/json" => Encoding::BINARY, + "application/json; charset=utf-8" => Encoding::UTF_8, + "charset=uTf-8 application/json; " => Encoding::UTF_8, + "charset=UTF-8; application/json; " => Encoding::UTF_8, + "charset=ISO-8859-1 ;application/json; " => Encoding::ISO_8859_1, + "charset=EUC-KR ;application/json; " => Encoding::EUC_KR + } + text = String.new.force_encoding(Encoding::BINARY) + cases.each do |content_type, encoding| + Orb::Internal::Util.force_charset!(content_type, text: text) + assert_equal(encoding, text.encoding) + end + end +end + class Orb::Test::UtilSseTest < Minitest::Test def test_decode_lines cases = { @@ -381,7 +399,9 @@ def test_decode_lines %W[\na b\n\n] => %W[\n ab\n \n], %W[\na b] => %W[\n ab], %W[\u1F62E\u200D\u1F4A8] => %W[\u1F62E\u200D\u1F4A8], - %W[\u1F62E \u200D \u1F4A8] => %W[\u1F62E\u200D\u1F4A8] + %W[\u1F62E \u200D \u1F4A8] => %W[\u1F62E\u200D\u1F4A8], + ["\xf0\x9f".b, "\xa5\xba".b] => ["\xf0\x9f\xa5\xba".b], + ["\xf0".b, "\x9f".b, "\xa5".b, "\xba".b] => ["\xf0\x9f\xa5\xba".b] } eols = %W[\n \r \r\n] cases.each do |enum, expected| From a472f4cdee0d969b7e240a9519acccd3877f3de6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 19:10:59 +0000 Subject: [PATCH 09/13] feat(client): enable setting base URL from environment variable --- lib/orb/client.rb | 5 +++-- rbi/lib/orb/client.rbi | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/orb/client.rb b/lib/orb/client.rb index 318b4994..7c9504dc 100644 --- a/lib/orb/client.rb +++ b/lib/orb/client.rb @@ -76,7 +76,8 @@ class Client < Orb::Internal::Transport::BaseClient # # @param api_key [String, nil] Defaults to `ENV["ORB_API_KEY"]` # - # @param base_url [String, nil] Override the default base URL for the API, e.g., `"https://api.example.com/v2/"` + # @param base_url [String, nil] Override the default base URL for the API, e.g., + # `"https://api.example.com/v2/"`. Defaults to `ENV["ORB_BASE_URL"]` # # @param max_retries [Integer] Max number of retries to attempt after a failed retryable request. # @@ -89,7 +90,7 @@ class Client < Orb::Internal::Transport::BaseClient # @param idempotency_header [String] def initialize( api_key: ENV["ORB_API_KEY"], - base_url: nil, + base_url: ENV["ORB_BASE_URL"], max_retries: DEFAULT_MAX_RETRIES, timeout: DEFAULT_TIMEOUT_IN_SECONDS, initial_retry_delay: DEFAULT_INITIAL_RETRY_DELAY, diff --git a/rbi/lib/orb/client.rbi b/rbi/lib/orb/client.rbi index 9a65af61..f3b58afe 100644 --- a/rbi/lib/orb/client.rbi +++ b/rbi/lib/orb/client.rbi @@ -78,8 +78,9 @@ module Orb def self.new( # Defaults to `ENV["ORB_API_KEY"]` api_key: ENV["ORB_API_KEY"], - # Override the default base URL for the API, e.g., `"https://api.example.com/v2/"` - base_url: nil, + # Override the default base URL for the API, e.g., + # `"https://api.example.com/v2/"`. Defaults to `ENV["ORB_BASE_URL"]` + base_url: ENV["ORB_BASE_URL"], # Max number of retries to attempt after a failed retryable request. max_retries: DEFAULT_MAX_RETRIES, timeout: DEFAULT_TIMEOUT_IN_SECONDS, From cb204de43549ee6aa9ead2d1e071ec5df0c58cb2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:10:30 +0000 Subject: [PATCH 10/13] chore(internal): contribute.md and contributor QoL improvements --- .gitignore | 6 +- .ruby-version | 1 + CONTRIBUTING.md | 132 ++++++++++++++++++++++++++++++++++++++++++ Rakefile | 67 ++++++++++++++++----- orb.gemspec | 17 ++++-- scripts/bootstrap | 4 +- scripts/format | 1 + scripts/lint | 2 + scripts/test | 2 +- sorbet/rbi/.gitignore | 2 + 10 files changed, 210 insertions(+), 24 deletions(-) create mode 100644 .ruby-version create mode 100644 CONTRIBUTING.md create mode 100644 sorbet/rbi/.gitignore diff --git a/.gitignore b/.gitignore index 8b1228a8..3d26ceed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ *.gem .idea/ +.ignore .prism.log .ruby-lsp/ .yardoc/ -Brewfile.lock.json bin/tapioca +Brewfile.lock.json doc/ -sorbet/* -!/sorbet/config +sorbet/tapioca/* diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..fd2a0186 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2058a521 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,132 @@ +## Setting up the environment + +This repository contains a `.ruby-version` file, which should work with either [rbenv](https://github.com/rbenv/rbenv) or [asdf](https://github.com/asdf-vm/asdf) with the [ruby plugin](https://github.com/asdf-vm/asdf-ruby). + +Please follow the instructions for your preferred version manager to install the Ruby version specified in the `.ruby-version` file. + +To set up the repository, run: + +```bash +$ ./scripts/bootstrap +``` + +This will install all the required dependencies. + +## Modifying/Adding code + +Most of the SDK is generated code. Modifications to code will be persisted between generations, but may result in merge conflicts between manual patches and changes from the generator. The generator will never modify the contents `examples/` directory. + +## Adding and running examples + +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. + +```ruby +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../lib/orb" + +# ... +``` + +```bash +$ chmod +x './examples/.rb' + +# run the example against your api +$ ruby './examples/.rb' +``` + +## Using the repository from source + +If you’d like to use the repository from source, you can either install from git or reference a cloned repository: + +To install via git in your `Gemfile`: + +```ruby +gem "orb-billing", git: "https://www.github.com/orbcorp/orb-ruby" +``` + +Alternatively, reference local copy of the repo: + +```bash +$ git clone -- 'https://www.github.com/orbcorp/orb-ruby' '' +``` + +```ruby +gem "orb-billing", path: "" +``` + +## Running commands + +Running `rake` by itself will show all runnable commands. + +```bash +$ bundle exec rake +``` + +## Running tests + +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. + +```bash +$ npx prism mock path/to/your/openapi.yml +``` + +```bash +$ bundle exec rake test +``` + +## Linting and formatting + +This repository uses [rubocop](https://github.com/rubocop/rubocop) for linting and formatting of `*.rb` and `*.rbi` files. [syntax_tree](https://github.com/ruby-syntax-tree/syntax_tree) is used for formatting `*.rbs` files. + +There are two separate type checkers supported by this library: [sorbet](https://github.com/sorbet/sorbet) and [steep](https://github.com/soutaro/steep) are used for verifying `*.rbi` and `*.rbs` files respectively. + +To lint and typecheck: + +```bash +$ bundle exec rake lint +``` + +To format and fix all lint issues automatically: + +```bash +$ bundle exec rake format +``` + +## Editor Support + +### Solargraph + +This library includes [Solargraph](https://solargraph.org) support for both auto-completion and go to definition. + +```ruby +gem "solargraph", group: :development +``` + +Note: if you had installed the gem locally using `git: "..."` or `path: "..."`, you must update your [`.solargraph.yml`](https://solargraph.org/guides/configuration) to include the path to where the gem is located: + +```yaml +include: + - '/lib/**/*.rb' +``` + +### Sorbet + +[Sorbet](https://sorbet.org) should mostly work out of the box when editing this library directly. However, there are a some caveats due to the colocation of `*.rb` and `*.rbi` files in the same project. These issues should not otherwise manifest when this library is used as a dependency. + +1. For go to definition usages, sorbet might get confused and may not always navigate to the correct location. + +2. For each generic type in `*.rbi` files, a spurious "Duplicate type member" error is present. + +### Ruby LSP + +The Ruby LSP has [best effort support](https://shopify.github.io/ruby-lsp/#guessed-types) for inferring type information from Ruby code, and as such it may not always be able to provide accurate type information. + +## Documentation Preview + +To preview the documentation, run: + +```bash +$ bundle exec rake docs:preview [PORT=8808] +``` diff --git a/Rakefile b/Rakefile index 8bc487ce..433bf6f4 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "pathname" require "securerandom" require "shellwords" @@ -7,10 +8,23 @@ require "minitest/test_task" require "rake/clean" require "rubocop/rake_task" -CLEAN.push(*%w[.idea/ .ruby-lsp/ .yardoc/]) +tapioca = "sorbet/tapioca" +ignore_file = ".ignore" -multitask(default: [:test]) +CLEAN.push(*%w[.idea/ .ruby-lsp/ .yardoc/ doc/], *FileList["*.gem"], ignore_file) +CLOBBER.push(*%w[sorbet/rbi/annotations/ sorbet/rbi/gems/], tapioca) + +multitask(:default) do + sh(*%w[rake --tasks]) +end + +desc("Preview docs; use `PORT=` to change the port") +multitask(:"docs:preview") do + sh(*%w[yard server --bind [::] --reload --quiet --port], ENV.fetch("PORT", "8808")) +end + +desc("Run test suites; use `TEST=path/to/test.rb` to run a specific test file") multitask(:test) do rb = FileList[ENV.fetch("TEST", "./test/**/*_test.rb")] @@ -23,17 +37,20 @@ end rubo_find = %w[find ./lib ./test ./rbi -type f -and ( -name *.rb -or -name *.rbi ) -print0] xargs = %w[xargs --no-run-if-empty --null --max-procs=0 --max-args=300 --] -multitask(:rubocop) do +desc("Lint `*.rb(i)`") +multitask(:"lint:rubocop") do lint = xargs + %w[rubocop --fail-level E] + (ENV.key?("CI") ? %w[--format github] : []) sh("#{rubo_find.shelljoin} | #{lint.shelljoin}") end -multitask(:ruboformat) do +desc("Format `*.rb(i)`") +multitask(:"format:rubocop") do fmt = xargs + %w[rubocop --fail-level F --autocorrect --format simple --] sh("#{rubo_find.shelljoin} | #{fmt.shelljoin}") end -multitask(:syntax_tree) do +desc("Format `*.rbs`") +multitask(:"format:syntax_tree") do find = %w[find ./sig -type f -name *.rbs -print0] inplace = /darwin|bsd/ =~ RUBY_PLATFORM ? %w[-i''] : %w[-i] uuid = SecureRandom.uuid @@ -74,27 +91,49 @@ multitask(:syntax_tree) do fail unless success end -multitask(format: [:ruboformat, :syntax_tree]) +desc("Format everything") +multitask(format: [:"format:rubocop", :"format:syntax_tree"]) -multitask(:steep) do +desc("Typecheck `*.rbs`") +multitask(:"typecheck:steep") do sh(*%w[steep check]) end -multitask(:sorbet) do +desc("Typecheck `*.rbi`") +multitask(:"typecheck:sorbet") do sh(*%w[srb typecheck]) end -file("sorbet/tapioca") do +file(tapioca) do sh(*%w[tapioca init]) end -multitask(typecheck: [:steep, :sorbet]) -multitask(lint: [:rubocop, :typecheck]) +desc("Typecheck everything") +multitask(typecheck: [:"typecheck:steep", :"typecheck:sorbet"]) + +desc("Lint everything") +multitask(lint: [:"lint:rubocop", :typecheck]) + +desc("Build yard docs") +multitask(:"build:docs") do + sh(*%w[yard]) +end -multitask(:build) do - sh(*%w[gem build -- orb.gemspec]) +desc("Build ruby gem") +multitask(:"build:gem") do + # optimizing for grepping through the gem bundle: many tools honour `.ignore` files, including VSCode + # + # both `rbi` and `sig` directories are navigable by their respective tool chains and therefore can be ignored by tools such as `rg` + Pathname(ignore_file).write(<<~GLOB) + rbi/* + sig/* + GLOB + + sh(*%w[gem build -- openai.gemspec]) + rm_rf(ignore_file) end -multitask(release: [:build]) do +desc("Release ruby gem") +multitask(release: [:"build:gem"]) do sh(*%w[gem push], *FileList["orb-*.gem"]) end diff --git a/orb.gemspec b/orb.gemspec index 83e9b7e7..6e3ca867 100644 --- a/orb.gemspec +++ b/orb.gemspec @@ -8,12 +8,21 @@ Gem::Specification.new do |s| s.summary = "Ruby library to access the Orb API" s.authors = ["Orb"] s.email = "team@withorb.com" - s.files = Dir["lib/**/*.rb", "rbi/**/*.rbi", "sig/**/*.rbs", "manifest.yaml", "CHANGELOG.md", "SECURITY.md"] - s.extra_rdoc_files = ["README.md"] - s.required_ruby_version = ">= 3.0.0" - s.add_dependency "connection_pool" s.homepage = "https://gemdocs.org/gems/orb-billing" s.metadata["homepage_uri"] = s.homepage s.metadata["source_code_uri"] = "https://github.com/orbcorp/orb-ruby" s.metadata["rubygems_mfa_required"] = false.to_s + s.required_ruby_version = ">= 3.0.0" + + s.files = Dir[ + "lib/**/*.rb", + "rbi/**/*.rbi", + "sig/**/*.rbs", + "manifest.yaml", + "SECURITY.md", + "CHANGELOG.md", + ".ignore" + ] + s.extra_rdoc_files = ["README.md"] + s.add_dependency "connection_pool" end diff --git a/scripts/bootstrap b/scripts/bootstrap index 88566757..cc31aa85 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -2,7 +2,7 @@ set -e -cd "$(dirname "$0")/.." +cd -- "$(dirname -- "$0")/.." if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then brew bundle check >/dev/null 2>&1 || { @@ -13,4 +13,4 @@ fi echo "==> Installing Ruby dependencies…" -bundle install +exec -- bundle install "$@" diff --git a/scripts/format b/scripts/format index 67b400de..177d1e63 100755 --- a/scripts/format +++ b/scripts/format @@ -5,4 +5,5 @@ set -e cd -- "$(dirname -- "$0")/.." echo "==> Running formatters" + exec -- bundle exec rake format "$@" diff --git a/scripts/lint b/scripts/lint index 39581dc1..08b0dbeb 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,4 +4,6 @@ set -e cd -- "$(dirname -- "$0")/.." +echo "==> Running linters" + exec -- bundle exec rake lint "$@" diff --git a/scripts/test b/scripts/test index 2e1fe093..8e5d35cd 100755 --- a/scripts/test +++ b/scripts/test @@ -2,7 +2,7 @@ set -e -cd "$(dirname "$0")/.." +cd -- "$(dirname -- "$0")/.." RED='\033[0;31m' GREEN='\033[0;32m' diff --git a/sorbet/rbi/.gitignore b/sorbet/rbi/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/sorbet/rbi/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From f570e93e8fe1a2f6d6df495021ea1152aeaf1e58 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:19:05 +0000 Subject: [PATCH 11/13] fix: always send idempotency header when specified as a request option --- lib/orb/internal/transport/base_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/orb/internal/transport/base_client.rb b/lib/orb/internal/transport/base_client.rb index 49aea075..400340c5 100644 --- a/lib/orb/internal/transport/base_client.rb +++ b/lib/orb/internal/transport/base_client.rb @@ -253,7 +253,7 @@ def initialize( if @idempotency_header && !headers.key?(@idempotency_header) && - !Net::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase) + (!Net::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase) || opts.key?(:idempotency_key)) headers[@idempotency_header] = opts.fetch(:idempotency_key) { generate_idempotency_key } end From 86f8280e1126e5bd4013f4c1e5ddd09190044fa3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:10:56 +0000 Subject: [PATCH 12/13] chore: refine `#inspect` and `#to_s` for model classes --- lib/orb/internal/type/base_model.rb | 46 ++++++++++++++++-------- rbi/lib/orb/internal/type/base_model.rbi | 9 +++++ sig/orb/internal/type/base_model.rbs | 6 ++++ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/lib/orb/internal/type/base_model.rb b/lib/orb/internal/type/base_model.rb index ed167976..eb73b89c 100644 --- a/lib/orb/internal/type/base_model.rb +++ b/lib/orb/internal/type/base_model.rb @@ -338,6 +338,27 @@ def deconstruct_keys(keys) .to_h end + class << self + # @param model [Orb::Internal::Type::BaseModel] + # + # @return [Hash{Symbol=>Object}] + def walk(model) + walk = ->(x) do + case x + in Orb::Internal::Type::BaseModel + walk.call(x.to_h) + in Hash + x.transform_values(&walk) + in Array + x.map(&walk) + else + x + end + end + walk.call(model) + end + end + # @param a [Object] # # @return [String] @@ -373,13 +394,11 @@ def inspect(depth: 0) depth = depth.succ deferred = fields.transform_values do |field| type, required, nilable = field.fetch_values(:type, :required, :nilable) - -> do - [ - Orb::Internal::Type::Converter.inspect(type, depth: depth), - !required || nilable ? "nil" : nil - ].compact.join(" | ") - end - .tap { _1.define_singleton_method(:inspect) { call } } + inspected = [ + Orb::Internal::Type::Converter.inspect(type, depth: depth), + !required || nilable ? "nil" : nil + ].compact.join(" | ") + -> { inspected }.tap { _1.define_singleton_method(:inspect) { call } } end "#{name}[#{deferred.inspect}]" @@ -389,15 +408,12 @@ def inspect(depth: 0) # @api private # # @return [String] - def inspect - rows = @data.map do - "#{_1}=#{self.class.known_fields.key?(_1) ? public_send(_1).inspect : ''}" - rescue Orb::Errors::ConversionError - "#{_1}=#{_2.inspect}" - end + def to_s = self.class.walk(@data).to_s - "#<#{self.class}:0x#{object_id.to_s(16)} #{rows.join(' ')}>" - end + # @api private + # + # @return [String] + def inspect = "#<#{self.class}:0x#{object_id.to_s(16)} #{self}>" end end end diff --git a/rbi/lib/orb/internal/type/base_model.rbi b/rbi/lib/orb/internal/type/base_model.rbi index 70d21a00..fadb4e30 100644 --- a/rbi/lib/orb/internal/type/base_model.rbi +++ b/rbi/lib/orb/internal/type/base_model.rbi @@ -175,6 +175,11 @@ module Orb sig { params(keys: T.nilable(T::Array[Symbol])).returns(Orb::Internal::AnyHash) } def deconstruct_keys(keys); end + class << self + sig { params(model: Orb::Internal::Type::BaseModel).returns(Orb::Internal::AnyHash) } + def walk(model); end + end + sig { params(a: T.anything).returns(String) } def to_json(*a); end @@ -191,6 +196,10 @@ module Orb def inspect(depth: 0); end end + # @api private + sig { returns(String) } + def to_s; end + # @api private sig { returns(String) } def inspect; end diff --git a/sig/orb/internal/type/base_model.rbs b/sig/orb/internal/type/base_model.rbs index 23bf32b7..709d39bb 100644 --- a/sig/orb/internal/type/base_model.rbs +++ b/sig/orb/internal/type/base_model.rbs @@ -69,6 +69,10 @@ module Orb def deconstruct_keys: (::Array[Symbol]? keys) -> ::Hash[Symbol, top] + def self.walk: ( + Orb::Internal::Type::BaseModel model + ) -> ::Hash[Symbol, top] + def to_json: (*top a) -> String def to_yaml: (*top a) -> String @@ -77,6 +81,8 @@ module Orb def self.inspect: (?depth: Integer) -> String + def to_s: -> String + def inspect: -> String end end From feefacd8dfba9e1033bc6ca1618e792e66e998f7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:11:17 +0000 Subject: [PATCH 13/13] release: 0.2.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ README.md | 2 +- lib/orb/version.rb | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4c5a1a01..10f30916 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.3" + ".": "0.2.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a4b80c0b..29068b4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 0.2.0 (2025-04-17) + +Full Changelog: [v0.1.3...v0.2.0](https://github.com/orbcorp/orb-ruby/compare/v0.1.3...v0.2.0) + +### Features + +* **client:** enable setting base URL from environment variable ([a472f4c](https://github.com/orbcorp/orb-ruby/commit/a472f4cdee0d969b7e240a9519acccd3877f3de6)) + + +### Bug Fixes + +* always send idempotency header when specified as a request option ([f570e93](https://github.com/orbcorp/orb-ruby/commit/f570e93e8fe1a2f6d6df495021ea1152aeaf1e58)) + + +### Chores + +* **internal:** always run post-processing when formatting when syntax_tree ([9df6d63](https://github.com/orbcorp/orb-ruby/commit/9df6d6382e67f6f28f1cedff4ee1c949ffd70237)) +* **internal:** codegen related update ([202fff4](https://github.com/orbcorp/orb-ruby/commit/202fff413ad9e9be16b67a07270dd4ac2c4f14e4)) +* **internal:** codegen related update ([2b99ae2](https://github.com/orbcorp/orb-ruby/commit/2b99ae2ea108ad4e6227bd5ab7e9a7dd064a8ded)) +* **internal:** contribute.md and contributor QoL improvements ([cb204de](https://github.com/orbcorp/orb-ruby/commit/cb204de43549ee6aa9ead2d1e071ec5df0c58cb2)) +* **internal:** loosen internal type restrictions ([9dc6b52](https://github.com/orbcorp/orb-ruby/commit/9dc6b52d1bfa57e3fab328bf8673872522ab7f25)) +* **internal:** minor touch ups on sdk internals ([9297be8](https://github.com/orbcorp/orb-ruby/commit/9297be8eaa459f14cb0b4118066ecd59877686ab)) +* **internal:** protect SSE parsing pipeline from broken UTF-8 characters ([bb2243a](https://github.com/orbcorp/orb-ruby/commit/bb2243a19fbd077687be4b2a4a001e4b7381c54d)) +* **internal:** version bump ([c664f2f](https://github.com/orbcorp/orb-ruby/commit/c664f2fb5019cabca07ce026410d6951f9412d69)) +* refine `#inspect` and `#to_s` for model classes ([86f8280](https://github.com/orbcorp/orb-ruby/commit/86f8280e1126e5bd4013f4c1e5ddd09190044fa3)) + + +### Documentation + +* update documentation links to be more uniform ([a0bfe42](https://github.com/orbcorp/orb-ruby/commit/a0bfe42c34e09819e4948069670cfdf895cb51af)) + ## 0.1.3 (2025-04-11) Full Changelog: [v0.1.2...v0.1.3](https://github.com/orbcorp/orb-ruby/compare/v0.1.2...v0.1.3) diff --git a/README.md b/README.md index f9836f71..27555d12 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application ```ruby -gem "orb-billing", "~> 0.1.3" +gem "orb-billing", "~> 0.2.0" ``` diff --git a/lib/orb/version.rb b/lib/orb/version.rb index 902cbc99..4fa2a003 100644 --- a/lib/orb/version.rb +++ b/lib/orb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Orb - VERSION = "0.1.3" + VERSION = "0.2.0" end