orb-billing 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.ignore +2 -0
  3. data/CHANGELOG.md +31 -0
  4. data/README.md +3 -3
  5. data/lib/orb/client.rb +3 -2
  6. data/lib/orb/internal/page.rb +33 -31
  7. data/lib/orb/internal/transport/base_client.rb +14 -4
  8. data/lib/orb/internal/transport/pooled_net_requester.rb +1 -1
  9. data/lib/orb/internal/type/array_of.rb +17 -2
  10. data/lib/orb/internal/type/base_model.rb +52 -8
  11. data/lib/orb/internal/type/base_page.rb +1 -0
  12. data/lib/orb/internal/type/boolean.rb +2 -0
  13. data/lib/orb/internal/type/converter.rb +24 -0
  14. data/lib/orb/internal/type/enum.rb +19 -3
  15. data/lib/orb/internal/type/hash_of.rb +17 -2
  16. data/lib/orb/internal/type/io_like.rb +2 -0
  17. data/lib/orb/internal/type/union.rb +17 -3
  18. data/lib/orb/internal/type/unknown.rb +2 -0
  19. data/lib/orb/internal/util.rb +36 -9
  20. data/lib/orb/internal.rb +5 -1
  21. data/lib/orb/version.rb +1 -1
  22. data/rbi/lib/orb/client.rbi +3 -2
  23. data/rbi/lib/orb/internal/page.rbi +1 -0
  24. data/rbi/lib/orb/internal/transport/base_client.rbi +1 -0
  25. data/rbi/lib/orb/internal/type/array_of.rbi +12 -9
  26. data/rbi/lib/orb/internal/type/base_model.rbi +16 -0
  27. data/rbi/lib/orb/internal/type/boolean.rbi +4 -5
  28. data/rbi/lib/orb/internal/type/converter.rbi +8 -0
  29. data/rbi/lib/orb/internal/type/enum.rbi +4 -0
  30. data/rbi/lib/orb/internal/type/hash_of.rbi +12 -9
  31. data/rbi/lib/orb/internal/type/io_like.rbi +4 -5
  32. data/rbi/lib/orb/internal/type/union.rbi +4 -0
  33. data/rbi/lib/orb/internal/type/unknown.rbi +4 -5
  34. data/rbi/lib/orb/internal/util.rbi +15 -0
  35. data/rbi/lib/orb/internal.rbi +1 -1
  36. data/sig/orb/internal/type/array_of.rbs +2 -0
  37. data/sig/orb/internal/type/base_model.rbs +8 -0
  38. data/sig/orb/internal/type/converter.rbs +4 -0
  39. data/sig/orb/internal/type/enum.rbs +2 -0
  40. data/sig/orb/internal/type/hash_of.rbs +2 -0
  41. data/sig/orb/internal/type/union.rbs +2 -0
  42. data/sig/orb/internal/util.rbs +2 -0
  43. data/sig/orb/internal.rbs +1 -1
  44. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2f2a150f3bbeffc4968f28b279e5c7c43abec6157af25c8e611fd831e284c28
4
- data.tar.gz: a16f35197738dd432d005af4c9712424d72b6321a40dd2bb8a8610b1c6cf6759
3
+ metadata.gz: b20102d8611954dcd1c5682addb7eb5d7b54314f2a38a14ac662e61fdd3084d5
4
+ data.tar.gz: '0857f6afda9adda33b5370837b17c5ab08e2bead3d708a3554e0c23c4398c2de'
5
5
  SHA512:
6
- metadata.gz: 1e00b2405bef17513a4df12aee28e0960ae7d5e7cb84a58897bc38fac404f910a7137a3ba195fc9b097d4669179ca632b79def2b9235246f1946185717fc0c91
7
- data.tar.gz: abc4cf559259bf76454de1a583eb75accd6deddcc596730fff3ed4cadebf4a44c158ea2b78f2072a4ab953d1ca73fd623b93df3fee072ca8e6d429e03e60772a
6
+ metadata.gz: a2641bfb912356b7b5458ffa63da5c444a75c651dc14984a863badfb4b461e1b06da20ac25153dfa4d1e8176411268b541777f631cf75300b1704dfc0316a417
7
+ data.tar.gz: 51816a9538c1d9e475afd9bc3e027aa4ef09874850ebb0d12d0c3b662c63315ef1a61e28e3d271ad83e3c529d8dddb545eace95b2fafa4c6e6689edbd63cb2a2
data/.ignore ADDED
@@ -0,0 +1,2 @@
1
+ rbi/*
2
+ sig/*
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 (2025-04-17)
4
+
5
+ Full Changelog: [v0.1.3...v0.2.0](https://github.com/orbcorp/orb-ruby/compare/v0.1.3...v0.2.0)
6
+
7
+ ### Features
8
+
9
+ * **client:** enable setting base URL from environment variable ([a472f4c](https://github.com/orbcorp/orb-ruby/commit/a472f4cdee0d969b7e240a9519acccd3877f3de6))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * always send idempotency header when specified as a request option ([f570e93](https://github.com/orbcorp/orb-ruby/commit/f570e93e8fe1a2f6d6df495021ea1152aeaf1e58))
15
+
16
+
17
+ ### Chores
18
+
19
+ * **internal:** always run post-processing when formatting when syntax_tree ([9df6d63](https://github.com/orbcorp/orb-ruby/commit/9df6d6382e67f6f28f1cedff4ee1c949ffd70237))
20
+ * **internal:** codegen related update ([202fff4](https://github.com/orbcorp/orb-ruby/commit/202fff413ad9e9be16b67a07270dd4ac2c4f14e4))
21
+ * **internal:** codegen related update ([2b99ae2](https://github.com/orbcorp/orb-ruby/commit/2b99ae2ea108ad4e6227bd5ab7e9a7dd064a8ded))
22
+ * **internal:** contribute.md and contributor QoL improvements ([cb204de](https://github.com/orbcorp/orb-ruby/commit/cb204de43549ee6aa9ead2d1e071ec5df0c58cb2))
23
+ * **internal:** loosen internal type restrictions ([9dc6b52](https://github.com/orbcorp/orb-ruby/commit/9dc6b52d1bfa57e3fab328bf8673872522ab7f25))
24
+ * **internal:** minor touch ups on sdk internals ([9297be8](https://github.com/orbcorp/orb-ruby/commit/9297be8eaa459f14cb0b4118066ecd59877686ab))
25
+ * **internal:** protect SSE parsing pipeline from broken UTF-8 characters ([bb2243a](https://github.com/orbcorp/orb-ruby/commit/bb2243a19fbd077687be4b2a4a001e4b7381c54d))
26
+ * **internal:** version bump ([c664f2f](https://github.com/orbcorp/orb-ruby/commit/c664f2fb5019cabca07ce026410d6951f9412d69))
27
+ * refine `#inspect` and `#to_s` for model classes ([86f8280](https://github.com/orbcorp/orb-ruby/commit/86f8280e1126e5bd4013f4c1e5ddd09190044fa3))
28
+
29
+
30
+ ### Documentation
31
+
32
+ * update documentation links to be more uniform ([a0bfe42](https://github.com/orbcorp/orb-ruby/commit/a0bfe42c34e09819e4948069670cfdf895cb51af))
33
+
3
34
  ## 0.1.3 (2025-04-11)
4
35
 
5
36
  Full Changelog: [v0.1.2...v0.1.3](https://github.com/orbcorp/orb-ruby/compare/v0.1.2...v0.1.3)
data/README.md CHANGED
@@ -4,9 +4,9 @@ The Orb Ruby library provides convenient access to the Orb REST API from any Rub
4
4
 
5
5
  ## Documentation
6
6
 
7
- Documentation for released of this gem can be found [on RubyDoc](https://gemdocs.org/gems/orb-billing).
7
+ Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/orb-billing).
8
8
 
9
- The underlying REST API documentation can be found on [docs.withorb.com](https://docs.withorb.com/reference/api-reference).
9
+ The REST API documentation can be found on [docs.withorb.com](https://docs.withorb.com/reference/api-reference).
10
10
 
11
11
  ## Installation
12
12
 
@@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application
15
15
  <!-- x-release-please-start-version -->
16
16
 
17
17
  ```ruby
18
- gem "orb-billing", "~> 0.1.3"
18
+ gem "orb-billing", "~> 0.2.0"
19
19
  ```
20
20
 
21
21
  <!-- x-release-please-end -->
data/lib/orb/client.rb CHANGED
@@ -76,7 +76,8 @@ module Orb
76
76
  #
77
77
  # @param api_key [String, nil] Defaults to `ENV["ORB_API_KEY"]`
78
78
  #
79
- # @param base_url [String, nil] Override the default base URL for the API, e.g., `"https://api.example.com/v2/"`
79
+ # @param base_url [String, nil] Override the default base URL for the API, e.g.,
80
+ # `"https://api.example.com/v2/"`. Defaults to `ENV["ORB_BASE_URL"]`
80
81
  #
81
82
  # @param max_retries [Integer] Max number of retries to attempt after a failed retryable request.
82
83
  #
@@ -89,7 +90,7 @@ module Orb
89
90
  # @param idempotency_header [String]
90
91
  def initialize(
91
92
  api_key: ENV["ORB_API_KEY"],
92
- base_url: nil,
93
+ base_url: ENV["ORB_BASE_URL"],
93
94
  max_retries: DEFAULT_MAX_RETRIES,
94
95
  timeout: DEFAULT_TIMEOUT_IN_SECONDS,
95
96
  initial_retry_delay: DEFAULT_INITIAL_RETRY_DELAY,
@@ -22,33 +22,6 @@ module Orb
22
22
  # @return [PaginationMetadata]
23
23
  attr_accessor :pagination_metadata
24
24
 
25
- # @api private
26
- #
27
- # @param client [Orb::Internal::Transport::BaseClient]
28
- # @param req [Hash{Symbol=>Object}]
29
- # @param headers [Hash{String=>String}, Net::HTTPHeader]
30
- # @param page_data [Hash{Symbol=>Object}]
31
- def initialize(client:, req:, headers:, page_data:)
32
- super
33
- model = req.fetch(:model)
34
-
35
- case page_data
36
- in {data: Array | nil => data}
37
- @data = data&.map { Orb::Internal::Type::Converter.coerce(model, _1) }
38
- else
39
- end
40
-
41
- case page_data
42
- in {pagination_metadata: Hash | nil => pagination_metadata}
43
- @pagination_metadata =
44
- Orb::Internal::Type::Converter.coerce(
45
- Orb::Internal::Page::PaginationMetadata,
46
- pagination_metadata
47
- )
48
- else
49
- end
50
- end
51
-
52
25
  # @return [Boolean]
53
26
  def next_page?
54
27
  !pagination_metadata&.next_cursor.nil?
@@ -73,19 +46,48 @@ module Orb
73
46
  unless block_given?
74
47
  raise ArgumentError.new("A block must be given to ##{__method__}")
75
48
  end
49
+
76
50
  page = self
77
51
  loop do
78
- page.data&.each { blk.call(_1) }
52
+ page.data&.each(&blk)
53
+
79
54
  break unless page.next_page?
80
55
  page = page.next_page
81
56
  end
82
57
  end
83
58
 
59
+ # @api private
60
+ #
61
+ # @param client [Orb::Internal::Transport::BaseClient]
62
+ # @param req [Hash{Symbol=>Object}]
63
+ # @param headers [Hash{String=>String}, Net::HTTPHeader]
64
+ # @param page_data [Hash{Symbol=>Object}]
65
+ def initialize(client:, req:, headers:, page_data:)
66
+ super
67
+
68
+ case page_data
69
+ in {data: Array | nil => data}
70
+ @data = data&.map { Orb::Internal::Type::Converter.coerce(@model, _1) }
71
+ else
72
+ end
73
+ case page_data
74
+ in {pagination_metadata: Hash | nil => pagination_metadata}
75
+ @pagination_metadata =
76
+ Orb::Internal::Type::Converter.coerce(
77
+ Orb::Internal::Page::PaginationMetadata,
78
+ pagination_metadata
79
+ )
80
+ else
81
+ end
82
+ end
83
+
84
+ # @api private
85
+ #
84
86
  # @return [String]
85
87
  def inspect
86
- # rubocop:disable Layout/LineLength
87
- "#<#{self.class}:0x#{object_id.to_s(16)} data=#{data.inspect} pagination_metadata=#{pagination_metadata.inspect}>"
88
- # rubocop:enable Layout/LineLength
88
+ model = Orb::Internal::Type::Converter.inspect(@model, depth: 1)
89
+
90
+ "#<#{self.class}[#{model}]:0x#{object_id.to_s(16)}>"
89
91
  end
90
92
 
91
93
  class PaginationMetadata < Orb::Internal::Type::BaseModel
@@ -93,7 +93,11 @@ module Orb
93
93
  URI.join(url, response_headers["location"])
94
94
  rescue ArgumentError
95
95
  message = "Server responded with status #{status} but no valid location header."
96
- raise Orb::Errors::APIConnectionError.new(url: url, message: message)
96
+ raise Orb::Errors::APIConnectionError.new(
97
+ url: url,
98
+ response: response_headers,
99
+ message: message
100
+ )
97
101
  end
98
102
 
99
103
  request = {**request, url: location}
@@ -101,7 +105,11 @@ module Orb
101
105
  case [url.scheme, location.scheme]
102
106
  in ["https", "http"]
103
107
  message = "Tried to redirect to a insecure URL"
104
- raise Orb::Errors::APIConnectionError.new(url: url, message: message)
108
+ raise Orb::Errors::APIConnectionError.new(
109
+ url: url,
110
+ response: response_headers,
111
+ message: message
112
+ )
105
113
  else
106
114
  nil
107
115
  end
@@ -245,7 +253,7 @@ module Orb
245
253
 
246
254
  if @idempotency_header &&
247
255
  !headers.key?(@idempotency_header) &&
248
- !Net::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase)
256
+ (!Net::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase) || opts.key?(:idempotency_key))
249
257
  headers[@idempotency_header] = opts.fetch(:idempotency_key) { generate_idempotency_key }
250
258
  end
251
259
 
@@ -350,7 +358,7 @@ module Orb
350
358
  self.class.reap_connection!(status, stream: stream)
351
359
 
352
360
  message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
353
- raise Orb::Errors::APIConnectionError.new(url: url, message: message)
361
+ raise Orb::Errors::APIConnectionError.new(url: url, response: response, message: message)
354
362
  in 300..399
355
363
  self.class.reap_connection!(status, stream: stream)
356
364
 
@@ -460,6 +468,8 @@ module Orb
460
468
  end
461
469
  end
462
470
 
471
+ # @api private
472
+ #
463
473
  # @return [String]
464
474
  def inspect
465
475
  # rubocop:disable Layout/LineLength
@@ -149,7 +149,7 @@ module Orb
149
149
  break if finished
150
150
 
151
151
  rsp.read_body do |bytes|
152
- y << bytes
152
+ y << bytes.force_encoding(Encoding::BINARY)
153
153
  break if finished
154
154
 
155
155
  self.class.calibrate_socket_timeout(conn, deadline)
@@ -13,6 +13,10 @@ module Orb
13
13
  class ArrayOf
14
14
  include Orb::Internal::Type::Converter
15
15
 
16
+ private_class_method :new
17
+
18
+ # @overload [](type_info, spec = {})
19
+ #
16
20
  # @param type_info [Hash{Symbol=>Object}, Proc, Orb::Internal::Type::Converter, Class]
17
21
  #
18
22
  # @param spec [Hash{Symbol=>Object}] .
@@ -24,7 +28,7 @@ module Orb
24
28
  # @option spec [Proc] :union
25
29
  #
26
30
  # @option spec [Boolean] :"nil?"
27
- def self.[](type_info, spec = {}) = new(type_info, spec)
31
+ def self.[](...) = new(...)
28
32
 
29
33
  # @param other [Object]
30
34
  #
@@ -120,7 +124,18 @@ module Orb
120
124
  # @option spec [Boolean] :"nil?"
121
125
  def initialize(type_info, spec = {})
122
126
  @item_type_fn = Orb::Internal::Type::Converter.type_info(type_info || spec)
123
- @nilable = spec[:nil?]
127
+ @nilable = spec.fetch(:nil?, false)
128
+ end
129
+
130
+ # @api private
131
+ #
132
+ # @param depth [Integer]
133
+ #
134
+ # @return [String]
135
+ def inspect(depth: 0)
136
+ items = Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ)
137
+
138
+ "#{self.class}[#{[items, nilable? ? 'nil' : nil].compact.join(' | ')}]"
124
139
  end
125
140
  end
126
141
  end
@@ -63,7 +63,7 @@ module Orb
63
63
 
64
64
  setter = "#{name_sym}="
65
65
  api_name = info.fetch(:api_name, name_sym)
66
- nilable = info[:nil?]
66
+ nilable = info.fetch(:nil?, false)
67
67
  const = required && !nilable ? info.fetch(:const, Orb::Internal::OMIT) : Orb::Internal::OMIT
68
68
 
69
69
  [name_sym, setter].each { undef_method(_1) } if known_fields.key?(name_sym)
@@ -338,6 +338,27 @@ module Orb
338
338
  .to_h
339
339
  end
340
340
 
341
+ class << self
342
+ # @param model [Orb::Internal::Type::BaseModel]
343
+ #
344
+ # @return [Hash{Symbol=>Object}]
345
+ def walk(model)
346
+ walk = ->(x) do
347
+ case x
348
+ in Orb::Internal::Type::BaseModel
349
+ walk.call(x.to_h)
350
+ in Hash
351
+ x.transform_values(&walk)
352
+ in Array
353
+ x.map(&walk)
354
+ else
355
+ x
356
+ end
357
+ end
358
+ walk.call(model)
359
+ end
360
+ end
361
+
341
362
  # @param a [Object]
342
363
  #
343
364
  # @return [String]
@@ -361,15 +382,38 @@ module Orb
361
382
  end
362
383
  end
363
384
 
364
- # @return [String]
365
- def inspect
366
- rows = self.class.known_fields.keys.map do
367
- "#{_1}=#{@data.key?(_1) ? public_send(_1) : ''}"
368
- rescue Orb::Errors::ConversionError
369
- "#{_1}=#{@data.fetch(_1)}"
385
+ class << self
386
+ # @api private
387
+ #
388
+ # @param depth [Integer]
389
+ #
390
+ # @return [String]
391
+ def inspect(depth: 0)
392
+ return super() if depth.positive?
393
+
394
+ depth = depth.succ
395
+ deferred = fields.transform_values do |field|
396
+ type, required, nilable = field.fetch_values(:type, :required, :nilable)
397
+ inspected = [
398
+ Orb::Internal::Type::Converter.inspect(type, depth: depth),
399
+ !required || nilable ? "nil" : nil
400
+ ].compact.join(" | ")
401
+ -> { inspected }.tap { _1.define_singleton_method(:inspect) { call } }
402
+ end
403
+
404
+ "#{name}[#{deferred.inspect}]"
370
405
  end
371
- "#<#{self.class.name}:0x#{object_id.to_s(16)} #{rows.join(' ')}>"
372
406
  end
407
+
408
+ # @api private
409
+ #
410
+ # @return [String]
411
+ def to_s = self.class.walk(@data).to_s
412
+
413
+ # @api private
414
+ #
415
+ # @return [String]
416
+ def inspect = "#<#{self.class}:0x#{object_id.to_s(16)} #{self}>"
373
417
  end
374
418
  end
375
419
  end
@@ -36,6 +36,7 @@ module Orb
36
36
  def initialize(client:, req:, headers:, page_data:)
37
37
  @client = client
38
38
  @req = req
39
+ @model = req.fetch(:model)
39
40
  super()
40
41
  end
41
42
 
@@ -11,6 +11,8 @@ module Orb
11
11
  class Boolean
12
12
  extend Orb::Internal::Type::Converter
13
13
 
14
+ private_class_method :new
15
+
14
16
  # @param other [Object]
15
17
  #
16
18
  # @return [Boolean]
@@ -49,6 +49,15 @@ module Orb
49
49
  end
50
50
  end
51
51
 
52
+ # @api private
53
+ #
54
+ # @param depth [Integer]
55
+ #
56
+ # @return [String]
57
+ def inspect(depth: 0)
58
+ super()
59
+ end
60
+
52
61
  # rubocop:enable Lint/UnusedMethodArgument
53
62
 
54
63
  class << self
@@ -240,6 +249,21 @@ module Orb
240
249
  Orb::Internal::Type::Unknown.dump(value, state: state)
241
250
  end
242
251
  end
252
+
253
+ # @api private
254
+ #
255
+ # @param target [Object]
256
+ # @param depth [Integer]
257
+ #
258
+ # @return [String]
259
+ def inspect(target, depth:)
260
+ case target
261
+ in Orb::Internal::Type::Converter
262
+ target.inspect(depth: depth.succ)
263
+ else
264
+ target.inspect
265
+ end
266
+ end
243
267
  end
244
268
  end
245
269
  end
@@ -58,9 +58,9 @@ module Orb
58
58
  #
59
59
  # @return [Boolean]
60
60
  def ==(other)
61
- # rubocop:disable Layout/LineLength
62
- other.is_a?(Module) && other.singleton_class <= Orb::Internal::Type::Enum && other.values.to_set == values.to_set
63
- # rubocop:enable Layout/LineLength
61
+ # rubocop:disable Style/CaseEquality
62
+ Orb::Internal::Type::Enum === other && other.values.to_set == values.to_set
63
+ # rubocop:enable Style/CaseEquality
64
64
  end
65
65
 
66
66
  # @api private
@@ -103,6 +103,22 @@ module Orb
103
103
  # #
104
104
  # # @return [Symbol, Object]
105
105
  # def dump(value, state:) = super
106
+
107
+ # @api private
108
+ #
109
+ # @param depth [Integer]
110
+ #
111
+ # @return [String]
112
+ def inspect(depth: 0)
113
+ if depth.positive?
114
+ return is_a?(Module) ? super() : self.class.name
115
+ end
116
+
117
+ members = values.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) }
118
+ prefix = is_a?(Module) ? name : self.class.name
119
+
120
+ "#{prefix}[#{members.join(' | ')}]"
121
+ end
106
122
  end
107
123
  end
108
124
  end
@@ -13,6 +13,10 @@ module Orb
13
13
  class HashOf
14
14
  include Orb::Internal::Type::Converter
15
15
 
16
+ private_class_method :new
17
+
18
+ # @overload [](type_info, spec = {})
19
+ #
16
20
  # @param type_info [Hash{Symbol=>Object}, Proc, Orb::Internal::Type::Converter, Class]
17
21
  #
18
22
  # @param spec [Hash{Symbol=>Object}] .
@@ -24,7 +28,7 @@ module Orb
24
28
  # @option spec [Proc] :union
25
29
  #
26
30
  # @option spec [Boolean] :"nil?"
27
- def self.[](type_info, spec = {}) = new(type_info, spec)
31
+ def self.[](...) = new(...)
28
32
 
29
33
  # @param other [Object]
30
34
  #
@@ -140,7 +144,18 @@ module Orb
140
144
  # @option spec [Boolean] :"nil?"
141
145
  def initialize(type_info, spec = {})
142
146
  @item_type_fn = Orb::Internal::Type::Converter.type_info(type_info || spec)
143
- @nilable = spec[:nil?]
147
+ @nilable = spec.fetch(:nil?, false)
148
+ end
149
+
150
+ # @api private
151
+ #
152
+ # @param depth [Integer]
153
+ #
154
+ # @return [String]
155
+ def inspect(depth: 0)
156
+ items = Orb::Internal::Type::Converter.inspect(item_type, depth: depth.succ)
157
+
158
+ "#{self.class}[#{[items, nilable? ? 'nil' : nil].compact.join(' | ')}]"
144
159
  end
145
160
  end
146
161
  end
@@ -11,6 +11,8 @@ module Orb
11
11
  class IOLike
12
12
  extend Orb::Internal::Type::Converter
13
13
 
14
+ private_class_method :new
15
+
14
16
  # @param other [Object]
15
17
  #
16
18
  # @return [Boolean]
@@ -140,9 +140,7 @@ module Orb
140
140
  #
141
141
  # @return [Boolean]
142
142
  def ==(other)
143
- # rubocop:disable Layout/LineLength
144
- other.is_a?(Module) && other.singleton_class <= Orb::Internal::Type::Union && other.derefed_variants == derefed_variants
145
- # rubocop:enable Layout/LineLength
143
+ Orb::Internal::Type::Union === other && other.derefed_variants == derefed_variants
146
144
  end
147
145
 
148
146
  # @api private
@@ -225,6 +223,22 @@ module Orb
225
223
 
226
224
  # rubocop:enable Style/CaseEquality
227
225
  # rubocop:enable Style/HashEachMethods
226
+
227
+ # @api private
228
+ #
229
+ # @param depth [Integer]
230
+ #
231
+ # @return [String]
232
+ def inspect(depth: 0)
233
+ if depth.positive?
234
+ return is_a?(Module) ? super() : self.class.name
235
+ end
236
+
237
+ members = variants.map { Orb::Internal::Type::Converter.inspect(_1, depth: depth.succ) }
238
+ prefix = is_a?(Module) ? name : self.class.name
239
+
240
+ "#{prefix}[#{members.join(' | ')}]"
241
+ end
228
242
  end
229
243
  end
230
244
  end
@@ -13,6 +13,8 @@ module Orb
13
13
 
14
14
  # rubocop:disable Lint/UnusedMethodArgument
15
15
 
16
+ private_class_method :new
17
+
16
18
  # @param other [Object]
17
19
  #
18
20
  # @return [Boolean]
@@ -448,7 +448,7 @@ module Orb
448
448
  else
449
449
  src
450
450
  end
451
- @buf = String.new.b
451
+ @buf = String.new
452
452
  @blk = blk
453
453
  end
454
454
  end
@@ -460,7 +460,7 @@ module Orb
460
460
  # @return [Enumerable<String>]
461
461
  def writable_enum(&blk)
462
462
  Enumerator.new do |y|
463
- buf = String.new.b
463
+ buf = String.new
464
464
  y.define_singleton_method(:write) do
465
465
  self << buf.replace(_1)
466
466
  buf.bytesize
@@ -582,6 +582,27 @@ module Orb
582
582
 
583
583
  # @api private
584
584
  #
585
+ # https://www.iana.org/assignments/character-sets/character-sets.xhtml
586
+ #
587
+ # @param content_type [String]
588
+ # @param text [String]
589
+ def force_charset!(content_type, text:)
590
+ charset = /charset=([^;\s]+)/.match(content_type)&.captures&.first
591
+
592
+ return unless charset
593
+
594
+ begin
595
+ encoding = Encoding.find(charset)
596
+ text.force_encoding(encoding)
597
+ rescue ArgumentError
598
+ nil
599
+ end
600
+ end
601
+
602
+ # @api private
603
+ #
604
+ # Assumes each chunk in stream has `Encoding::BINARY`.
605
+ #
585
606
  # @param headers [Hash{String=>String}, Net::HTTPHeader]
586
607
  # @param stream [Enumerable<String>]
587
608
  # @param suppress_error [Boolean]
@@ -589,7 +610,7 @@ module Orb
589
610
  # @raise [JSON::ParserError]
590
611
  # @return [Object]
591
612
  def decode_content(headers, stream:, suppress_error: false)
592
- case headers["content-type"]
613
+ case (content_type = headers["content-type"])
593
614
  in %r{^application/(?:vnd\.api\+)?json}
594
615
  json = stream.to_a.join
595
616
  begin
@@ -606,11 +627,10 @@ module Orb
606
627
  in %r{^text/event-stream}
607
628
  lines = decode_lines(stream)
608
629
  decode_sse(lines)
609
- in %r{^text/}
610
- stream.to_a.join
611
630
  else
612
- # TODO: parsing other response types
613
- StringIO.new(stream.to_a.join)
631
+ text = stream.to_a.join
632
+ force_charset!(content_type, text: text)
633
+ StringIO.new(text)
614
634
  end
615
635
  end
616
636
  end
@@ -675,12 +695,17 @@ module Orb
675
695
  class << self
676
696
  # @api private
677
697
  #
698
+ # Assumes Strings have been forced into having `Encoding::BINARY`.
699
+ #
700
+ # This decoder is responsible for reassembling lines split across multiple
701
+ # fragments.
702
+ #
678
703
  # @param enum [Enumerable<String>]
679
704
  #
680
705
  # @return [Enumerable<String>]
681
706
  def decode_lines(enum)
682
707
  re = /(\r\n|\r|\n)/
683
- buffer = String.new.b
708
+ buffer = String.new
684
709
  cr_seen = nil
685
710
 
686
711
  chain_fused(enum) do |y|
@@ -711,6 +736,8 @@ module Orb
711
736
  #
712
737
  # https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
713
738
  #
739
+ # Assumes that `lines` has been decoded with `#decode_lines`.
740
+ #
714
741
  # @param lines [Enumerable<String>]
715
742
  #
716
743
  # @return [Enumerable<Hash{Symbol=>Object}>]
@@ -734,7 +761,7 @@ module Orb
734
761
  in "event"
735
762
  current.merge!(event: value)
736
763
  in "data"
737
- (current[:data] ||= String.new.b) << (value << "\n")
764
+ (current[:data] ||= String.new) << (value << "\n")
738
765
  in "id" unless value.include?("\0")
739
766
  current.merge!(id: value)
740
767
  in "retry" if /^\d+$/ =~ value
data/lib/orb/internal.rb CHANGED
@@ -3,6 +3,10 @@
3
3
  module Orb
4
4
  # @api private
5
5
  module Internal
6
- OMIT = Object.new.freeze
6
+ OMIT =
7
+ Object.new.tap do
8
+ _1.define_singleton_method(:inspect) { "#<#{Orb::Internal}::OMIT>" }
9
+ end
10
+ .freeze
7
11
  end
8
12
  end
data/lib/orb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Orb
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -78,8 +78,9 @@ module Orb
78
78
  def self.new(
79
79
  # Defaults to `ENV["ORB_API_KEY"]`
80
80
  api_key: ENV["ORB_API_KEY"],
81
- # Override the default base URL for the API, e.g., `"https://api.example.com/v2/"`
82
- base_url: nil,
81
+ # Override the default base URL for the API, e.g.,
82
+ # `"https://api.example.com/v2/"`. Defaults to `ENV["ORB_BASE_URL"]`
83
+ base_url: ENV["ORB_BASE_URL"],
83
84
  # Max number of retries to attempt after a failed retryable request.
84
85
  max_retries: DEFAULT_MAX_RETRIES,
85
86
  timeout: DEFAULT_TIMEOUT_IN_SECONDS,
@@ -13,6 +13,7 @@ module Orb
13
13
  sig { returns(PaginationMetadata) }
14
14
  attr_accessor :pagination_metadata
15
15
 
16
+ # @api private
16
17
  sig { returns(String) }
17
18
  def inspect; end
18
19
 
@@ -187,6 +187,7 @@ module Orb
187
187
  model: Orb::Internal::Type::Unknown,
188
188
  options: {}
189
189
  ); end
190
+ # @api private
190
191
  sig { returns(String) }
191
192
  def inspect; end
192
193
  end
@@ -10,11 +10,10 @@ module Orb
10
10
  include Orb::Internal::Type::Converter
11
11
 
12
12
  abstract!
13
- final!
14
13
 
15
14
  Elem = type_member(:out)
16
15
 
17
- sig(:final) do
16
+ sig do
18
17
  params(
19
18
  type_info: T.any(
20
19
  Orb::Internal::AnyHash,
@@ -27,14 +26,14 @@ module Orb
27
26
  end
28
27
  def self.[](type_info, spec = {}); end
29
28
 
30
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
29
+ sig { params(other: T.anything).returns(T::Boolean) }
31
30
  def ===(other); end
32
31
 
33
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
32
+ sig { params(other: T.anything).returns(T::Boolean) }
34
33
  def ==(other); end
35
34
 
36
35
  # @api private
37
- sig(:final) do
36
+ sig do
38
37
  override
39
38
  .params(value: T.any(
40
39
  T::Array[T.anything],
@@ -46,7 +45,7 @@ module Orb
46
45
  def coerce(value, state:); end
47
46
 
48
47
  # @api private
49
- sig(:final) do
48
+ sig do
50
49
  override
51
50
  .params(value: T.any(
52
51
  T::Array[T.anything],
@@ -58,15 +57,15 @@ module Orb
58
57
  def dump(value, state:); end
59
58
 
60
59
  # @api private
61
- sig(:final) { returns(Elem) }
60
+ sig { returns(Elem) }
62
61
  protected def item_type; end
63
62
 
64
63
  # @api private
65
- sig(:final) { returns(T::Boolean) }
64
+ sig { returns(T::Boolean) }
66
65
  protected def nilable?; end
67
66
 
68
67
  # @api private
69
- sig(:final) do
68
+ sig do
70
69
  params(
71
70
  type_info: T.any(
72
71
  Orb::Internal::AnyHash,
@@ -78,6 +77,10 @@ module Orb
78
77
  .void
79
78
  end
80
79
  def initialize(type_info, spec = {}); end
80
+
81
+ # @api private
82
+ sig { params(depth: Integer).returns(String) }
83
+ def inspect(depth: 0); end
81
84
  end
82
85
  end
83
86
  end
@@ -175,6 +175,11 @@ module Orb
175
175
  sig { params(keys: T.nilable(T::Array[Symbol])).returns(Orb::Internal::AnyHash) }
176
176
  def deconstruct_keys(keys); end
177
177
 
178
+ class << self
179
+ sig { params(model: Orb::Internal::Type::BaseModel).returns(Orb::Internal::AnyHash) }
180
+ def walk(model); end
181
+ end
182
+
178
183
  sig { params(a: T.anything).returns(String) }
179
184
  def to_json(*a); end
180
185
 
@@ -185,6 +190,17 @@ module Orb
185
190
  sig { params(data: T.any(T::Hash[Symbol, T.anything], T.self_type)).returns(T.attached_class) }
186
191
  def self.new(data = {}); end
187
192
 
193
+ class << self
194
+ # @api private
195
+ sig { params(depth: Integer).returns(String) }
196
+ def inspect(depth: 0); end
197
+ end
198
+
199
+ # @api private
200
+ sig { returns(String) }
201
+ def to_s; end
202
+
203
+ # @api private
188
204
  sig { returns(String) }
189
205
  def inspect; end
190
206
  end
@@ -10,17 +10,16 @@ module Orb
10
10
  extend Orb::Internal::Type::Converter
11
11
 
12
12
  abstract!
13
- final!
14
13
 
15
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
14
+ sig { params(other: T.anything).returns(T::Boolean) }
16
15
  def self.===(other); end
17
16
 
18
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
17
+ sig { params(other: T.anything).returns(T::Boolean) }
19
18
  def self.==(other); end
20
19
 
21
20
  class << self
22
21
  # @api private
23
- sig(:final) do
22
+ sig do
24
23
  override
25
24
  .params(value: T.any(
26
25
  T::Boolean,
@@ -32,7 +31,7 @@ module Orb
32
31
  def coerce(value, state:); end
33
32
 
34
33
  # @api private
35
- sig(:final) do
34
+ sig do
36
35
  override
37
36
  .params(value: T.any(T::Boolean, T.anything), state: Orb::Internal::Type::Converter::DumpState)
38
37
  .returns(T.any(T::Boolean, T.anything))
@@ -35,6 +35,10 @@ module Orb
35
35
  end
36
36
  def dump(value, state:); end
37
37
 
38
+ # @api private
39
+ sig { params(depth: Integer).returns(String) }
40
+ def inspect(depth: 0); end
41
+
38
42
  class << self
39
43
  # @api private
40
44
  sig do
@@ -106,6 +110,10 @@ module Orb
106
110
  .returns(T.anything)
107
111
  end
108
112
  def self.dump(target, value, state: {can_retry: true}); end
113
+
114
+ # @api private
115
+ sig { params(target: T.anything, depth: Integer).returns(String) }
116
+ def self.inspect(target, depth:); end
109
117
  end
110
118
  end
111
119
  end
@@ -57,6 +57,10 @@ module Orb
57
57
  .returns(T.any(Symbol, T.anything))
58
58
  end
59
59
  def dump(value, state:); end
60
+
61
+ # @api private
62
+ sig { params(depth: Integer).returns(String) }
63
+ def inspect(depth: 0); end
60
64
  end
61
65
  end
62
66
  end
@@ -10,11 +10,10 @@ module Orb
10
10
  include Orb::Internal::Type::Converter
11
11
 
12
12
  abstract!
13
- final!
14
13
 
15
14
  Elem = type_member(:out)
16
15
 
17
- sig(:final) do
16
+ sig do
18
17
  params(
19
18
  type_info: T.any(
20
19
  Orb::Internal::AnyHash,
@@ -27,14 +26,14 @@ module Orb
27
26
  end
28
27
  def self.[](type_info, spec = {}); end
29
28
 
30
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
29
+ sig { params(other: T.anything).returns(T::Boolean) }
31
30
  def ===(other); end
32
31
 
33
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
32
+ sig { params(other: T.anything).returns(T::Boolean) }
34
33
  def ==(other); end
35
34
 
36
35
  # @api private
37
- sig(:final) do
36
+ sig do
38
37
  override
39
38
  .params(
40
39
  value: T.any(T::Hash[T.anything, T.anything], T.anything),
@@ -45,7 +44,7 @@ module Orb
45
44
  def coerce(value, state:); end
46
45
 
47
46
  # @api private
48
- sig(:final) do
47
+ sig do
49
48
  override
50
49
  .params(
51
50
  value: T.any(T::Hash[T.anything, T.anything], T.anything),
@@ -56,15 +55,15 @@ module Orb
56
55
  def dump(value, state:); end
57
56
 
58
57
  # @api private
59
- sig(:final) { returns(Elem) }
58
+ sig { returns(Elem) }
60
59
  protected def item_type; end
61
60
 
62
61
  # @api private
63
- sig(:final) { returns(T::Boolean) }
62
+ sig { returns(T::Boolean) }
64
63
  protected def nilable?; end
65
64
 
66
65
  # @api private
67
- sig(:final) do
66
+ sig do
68
67
  params(
69
68
  type_info: T.any(
70
69
  Orb::Internal::AnyHash,
@@ -76,6 +75,10 @@ module Orb
76
75
  .void
77
76
  end
78
77
  def initialize(type_info, spec = {}); end
78
+
79
+ # @api private
80
+ sig { params(depth: Integer).returns(String) }
81
+ def inspect(depth: 0); end
79
82
  end
80
83
  end
81
84
  end
@@ -10,17 +10,16 @@ module Orb
10
10
  extend Orb::Internal::Type::Converter
11
11
 
12
12
  abstract!
13
- final!
14
13
 
15
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
14
+ sig { params(other: T.anything).returns(T::Boolean) }
16
15
  def self.===(other); end
17
16
 
18
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
17
+ sig { params(other: T.anything).returns(T::Boolean) }
19
18
  def self.==(other); end
20
19
 
21
20
  class << self
22
21
  # @api private
23
- sig(:final) do
22
+ sig do
24
23
  override
25
24
  .params(value: T.any(
26
25
  StringIO,
@@ -33,7 +32,7 @@ module Orb
33
32
  def coerce(value, state:); end
34
33
 
35
34
  # @api private
36
- sig(:final) do
35
+ sig do
37
36
  override
38
37
  .params(
39
38
  value: T.any(Pathname, StringIO, IO, String, T.anything),
@@ -62,6 +62,10 @@ module Orb
62
62
  ).returns(T.anything)
63
63
  end
64
64
  def dump(value, state:); end
65
+
66
+ # @api private
67
+ sig { params(depth: Integer).returns(String) }
68
+ def inspect(depth: 0); end
65
69
  end
66
70
  end
67
71
  end
@@ -10,17 +10,16 @@ module Orb
10
10
  extend Orb::Internal::Type::Converter
11
11
 
12
12
  abstract!
13
- final!
14
13
 
15
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
14
+ sig { params(other: T.anything).returns(T::Boolean) }
16
15
  def self.===(other); end
17
16
 
18
- sig(:final) { params(other: T.anything).returns(T::Boolean) }
17
+ sig { params(other: T.anything).returns(T::Boolean) }
19
18
  def self.==(other); end
20
19
 
21
20
  class << self
22
21
  # @api private
23
- sig(:final) do
22
+ sig do
24
23
  override.params(
25
24
  value: T.anything,
26
25
  state: Orb::Internal::Type::Converter::CoerceState
@@ -29,7 +28,7 @@ module Orb
29
28
  def coerce(value, state:); end
30
29
 
31
30
  # @api private
32
- sig(:final) do
31
+ sig do
33
32
  override.params(
34
33
  value: T.anything,
35
34
  state: Orb::Internal::Type::Converter::DumpState
@@ -215,6 +215,14 @@ module Orb
215
215
  def encode_content(headers, body); end
216
216
 
217
217
  # @api private
218
+ #
219
+ # https://www.iana.org/assignments/character-sets/character-sets.xhtml
220
+ sig { params(content_type: String, text: String).void }
221
+ def force_charset!(content_type, text:); end
222
+
223
+ # @api private
224
+ #
225
+ # Assumes each chunk in stream has `Encoding::BINARY`.
218
226
  sig do
219
227
  params(
220
228
  headers: T.any(T::Hash[String, String], Net::HTTPHeader),
@@ -263,12 +271,19 @@ module Orb
263
271
 
264
272
  class << self
265
273
  # @api private
274
+ #
275
+ # Assumes Strings have been forced into having `Encoding::BINARY`.
276
+ #
277
+ # This decoder is responsible for reassembling lines split across multiple
278
+ # fragments.
266
279
  sig { params(enum: T::Enumerable[String]).returns(T::Enumerable[String]) }
267
280
  def decode_lines(enum); end
268
281
 
269
282
  # @api private
270
283
  #
271
284
  # https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
285
+ #
286
+ # Assumes that `lines` has been decoded with `#decode_lines`.
272
287
  sig do
273
288
  params(lines: T::Enumerable[String]).returns(T::Enumerable[Orb::Internal::Util::ServerSentEvent])
274
289
  end
@@ -7,6 +7,6 @@ module Orb
7
7
  # this alias might be refined in the future.
8
8
  AnyHash = T.type_alias { T::Hash[Symbol, T.anything] }
9
9
 
10
- OMIT = T.let(T.anything, T.anything)
10
+ OMIT = T.let(Object.new.freeze, T.anything)
11
11
  end
12
12
  end
@@ -35,6 +35,8 @@ module Orb
35
35
  | Orb::Internal::Type::Converter::input type_info,
36
36
  ?::Hash[Symbol, top] spec
37
37
  ) -> void
38
+
39
+ def inspect: (?depth: Integer) -> String
38
40
  end
39
41
  end
40
42
  end
@@ -69,12 +69,20 @@ module Orb
69
69
 
70
70
  def deconstruct_keys: (::Array[Symbol]? keys) -> ::Hash[Symbol, top]
71
71
 
72
+ def self.walk: (
73
+ Orb::Internal::Type::BaseModel model
74
+ ) -> ::Hash[Symbol, top]
75
+
72
76
  def to_json: (*top a) -> String
73
77
 
74
78
  def to_yaml: (*top a) -> String
75
79
 
76
80
  def initialize: (?::Hash[Symbol, top] | self data) -> void
77
81
 
82
+ def self.inspect: (?depth: Integer) -> String
83
+
84
+ def to_s: -> String
85
+
78
86
  def inspect: -> String
79
87
  end
80
88
  end
@@ -23,6 +23,8 @@ module Orb
23
23
  state: Orb::Internal::Type::Converter::dump_state
24
24
  ) -> top
25
25
 
26
+ def inspect: (?depth: Integer) -> String
27
+
26
28
  def self.type_info: (
27
29
  {
28
30
  const: (nil | bool | Integer | Float | Symbol)?,
@@ -44,6 +46,8 @@ module Orb
44
46
  top value,
45
47
  ?state: Orb::Internal::Type::Converter::dump_state
46
48
  ) -> top
49
+
50
+ def self.inspect: (top target, depth: Integer) -> String
47
51
  end
48
52
  end
49
53
  end
@@ -21,6 +21,8 @@ module Orb
21
21
  Symbol | top value,
22
22
  state: Orb::Internal::Type::Converter::dump_state
23
23
  ) -> (Symbol | top)
24
+
25
+ def inspect: (?depth: Integer) -> String
24
26
  end
25
27
  end
26
28
  end
@@ -35,6 +35,8 @@ module Orb
35
35
  | Orb::Internal::Type::Converter::input type_info,
36
36
  ?::Hash[Symbol, top] spec
37
37
  ) -> void
38
+
39
+ def inspect: (?depth: Integer) -> String
38
40
  end
39
41
  end
40
42
  end
@@ -39,6 +39,8 @@ module Orb
39
39
  top value,
40
40
  state: Orb::Internal::Type::Converter::dump_state
41
41
  ) -> top
42
+
43
+ def inspect: (?depth: Integer) -> String
42
44
  end
43
45
  end
44
46
  end
@@ -120,6 +120,8 @@ module Orb
120
120
  top body
121
121
  ) -> top
122
122
 
123
+ def self?.force_charset!: (String content_type, text: String) -> void
124
+
123
125
  def self?.decode_content: (
124
126
  ::Hash[String, String] headers,
125
127
  stream: Enumerable[String],
data/sig/orb/internal.rbs CHANGED
@@ -1,5 +1,5 @@
1
1
  module Orb
2
2
  module Internal
3
- OMIT: top
3
+ OMIT: Object
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: orb-billing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Orb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-12 00:00:00.000000000 Z
11
+ date: 2025-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -31,6 +31,7 @@ extensions: []
31
31
  extra_rdoc_files:
32
32
  - README.md
33
33
  files:
34
+ - ".ignore"
34
35
  - CHANGELOG.md
35
36
  - README.md
36
37
  - SECURITY.md