modern_treasury 0.1.0.pre.alpha.17 → 0.1.0.pre.alpha.18

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.ignore +2 -0
  3. data/CHANGELOG.md +635 -0
  4. data/README.md +37 -12
  5. data/SECURITY.md +27 -0
  6. data/lib/modern_treasury/client.rb +3 -2
  7. data/lib/modern_treasury/internal/page.rb +27 -23
  8. data/lib/modern_treasury/internal/transport/base_client.rb +19 -5
  9. data/lib/modern_treasury/internal/transport/pooled_net_requester.rb +20 -7
  10. data/lib/modern_treasury/internal/type/array_of.rb +23 -4
  11. data/lib/modern_treasury/internal/type/base_model.rb +45 -10
  12. data/lib/modern_treasury/internal/type/base_page.rb +1 -0
  13. data/lib/modern_treasury/internal/type/boolean.rb +7 -1
  14. data/lib/modern_treasury/internal/type/converter.rb +66 -17
  15. data/lib/modern_treasury/internal/type/enum.rb +24 -4
  16. data/lib/modern_treasury/internal/type/hash_of.rb +23 -4
  17. data/lib/modern_treasury/internal/type/io_like.rb +77 -0
  18. data/lib/modern_treasury/internal/type/request_parameters.rb +11 -2
  19. data/lib/modern_treasury/internal/type/union.rb +30 -6
  20. data/lib/modern_treasury/internal/type/unknown.rb +7 -1
  21. data/lib/modern_treasury/internal/util.rb +115 -29
  22. data/lib/modern_treasury/internal.rb +5 -1
  23. data/lib/modern_treasury/models/document_create_params.rb +3 -3
  24. data/lib/modern_treasury/models/payment_order_create_params.rb +3 -3
  25. data/lib/modern_treasury/resources/documents.rb +1 -1
  26. data/lib/modern_treasury/version.rb +1 -1
  27. data/lib/modern_treasury.rb +1 -0
  28. data/rbi/lib/modern_treasury/client.rbi +3 -2
  29. data/rbi/lib/modern_treasury/internal/page.rbi +1 -0
  30. data/rbi/lib/modern_treasury/internal/transport/base_client.rbi +1 -0
  31. data/rbi/lib/modern_treasury/internal/transport/pooled_net_requester.rbi +1 -1
  32. data/rbi/lib/modern_treasury/internal/type/array_of.rbi +18 -12
  33. data/rbi/lib/modern_treasury/internal/type/base_model.rbi +13 -3
  34. data/rbi/lib/modern_treasury/internal/type/boolean.rbi +16 -12
  35. data/rbi/lib/modern_treasury/internal/type/converter.rbi +24 -8
  36. data/rbi/lib/modern_treasury/internal/type/enum.rbi +15 -5
  37. data/rbi/lib/modern_treasury/internal/type/hash_of.rbi +18 -12
  38. data/rbi/lib/modern_treasury/internal/type/io_like.rbi +46 -0
  39. data/rbi/lib/modern_treasury/internal/type/union.rbi +11 -3
  40. data/rbi/lib/modern_treasury/internal/type/unknown.rbi +10 -7
  41. data/rbi/lib/modern_treasury/internal/util.rbi +50 -4
  42. data/rbi/lib/modern_treasury/internal.rbi +1 -1
  43. data/rbi/lib/modern_treasury/models/document_create_params.rbi +3 -3
  44. data/rbi/lib/modern_treasury/models/payment_order_create_params.rbi +3 -3
  45. data/rbi/lib/modern_treasury/resources/documents.rbi +1 -1
  46. data/sig/modern_treasury/internal/transport/pooled_net_requester.rbs +1 -1
  47. data/sig/modern_treasury/internal/type/array_of.rbs +7 -2
  48. data/sig/modern_treasury/internal/type/base_model.rbs +7 -2
  49. data/sig/modern_treasury/internal/type/boolean.rbs +5 -2
  50. data/sig/modern_treasury/internal/type/converter.rbs +15 -5
  51. data/sig/modern_treasury/internal/type/enum.rbs +7 -2
  52. data/sig/modern_treasury/internal/type/hash_of.rbs +7 -2
  53. data/sig/modern_treasury/internal/type/io_like.rbs +23 -0
  54. data/sig/modern_treasury/internal/type/union.rbs +7 -2
  55. data/sig/modern_treasury/internal/type/unknown.rbs +5 -2
  56. data/sig/modern_treasury/internal/util.rbs +19 -2
  57. data/sig/modern_treasury/internal.rbs +1 -1
  58. data/sig/modern_treasury/models/document_create_params.rbs +3 -3
  59. data/sig/modern_treasury/models/payment_order_create_params.rbs +3 -3
  60. data/sig/modern_treasury/resources/documents.rbs +1 -1
  61. metadata +8 -2
data/README.md CHANGED
@@ -4,9 +4,9 @@ The Modern Treasury Ruby library provides convenient access to the Modern Treasu
4
4
 
5
5
  ## Documentation
6
6
 
7
- Documentation for released of this gem can be found [on RubyDoc](https://gemdocs.org/gems/modern_treasury).
7
+ Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/modern_treasury).
8
8
 
9
- The underlying REST API documentation can be found on [docs.moderntreasury.com](https://docs.moderntreasury.com).
9
+ The REST API documentation can be found on [docs.moderntreasury.com](https://docs.moderntreasury.com).
10
10
 
11
11
  ## Installation
12
12
 
@@ -15,17 +15,11 @@ 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 "modern_treasury", "~> 0.1.0.pre.alpha.16"
18
+ gem "modern_treasury", "~> 0.1.0.pre.alpha.18"
19
19
  ```
20
20
 
21
21
  <!-- x-release-please-end -->
22
22
 
23
- To fetch an initial copy of the gem:
24
-
25
- ```sh
26
- bundle install
27
- ```
28
-
29
23
  ## Usage
30
24
 
31
25
  ```ruby
@@ -61,9 +55,34 @@ page.auto_paging_each do |counterparty|
61
55
  end
62
56
  ```
63
57
 
58
+ ### File uploads
59
+
60
+ Request parameters that correspond to file uploads can be passed as `StringIO`, or a [`Pathname`](https://rubyapi.org/3.1/o/pathname) instance.
61
+
62
+ ```ruby
63
+ require "pathname"
64
+
65
+ # using `Pathname`, the file will be lazily read, without reading everything in to memory
66
+ document = modern_treasury.documents.create(
67
+ documentable_id: "24c6b7a3-02...",
68
+ documentable_type: "counterparties",
69
+ file: Pathname("my/file.txt")
70
+ )
71
+
72
+ file = File.read("my/file.txt")
73
+ # using `StringIO`, useful if you already have the data in memory
74
+ document = modern_treasury.documents.create(
75
+ documentable_id: "24c6b7a3-02...",
76
+ documentable_type: "counterparties",
77
+ file: StringIO.new(file)
78
+ )
79
+
80
+ puts(document.id)
81
+ ```
82
+
64
83
  ### Errors
65
84
 
66
- When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `ModernTreasury::Error` will be thrown:
85
+ When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `ModernTreasury::Errors::APIError` will be thrown:
67
86
 
68
87
  ```ruby
69
88
  begin
@@ -141,6 +160,13 @@ After Solargraph is installed, **you must populate its index** either via the pr
141
160
  bundle exec solargraph gems
142
161
  ```
143
162
 
163
+ Note: if you had installed the gem either using a `git:` or `github:` URL, or had vendored the gem using bundler, you will need to set up your [`.solargraph.yml`](https://solargraph.org/guides/configuration) to include the path to the gem's `lib` directory.
164
+
165
+ ```yaml
166
+ include:
167
+ - 'vendor/bundle/ruby/*/gems/modern_treasury-*/lib/**/*.rb'
168
+ ```
169
+
144
170
  Otherwise Solargraph will not be able to provide type information or auto-completion for any non-indexed libraries.
145
171
 
146
172
  ### Sorbet
@@ -182,8 +208,7 @@ If you want to explicitly send an extra param, you can do so with the `extra_que
182
208
  To make requests to undocumented endpoints, you can make requests using `client.request`. Options on the client will be respected (such as retries) when making this request.
183
209
 
184
210
  ```ruby
185
- response =
186
- client.request(
211
+ response = client.request(
187
212
  method: :post,
188
213
  path: '/undocumented/endpoint',
189
214
  query: {"dog": "woof"},
data/SECURITY.md ADDED
@@ -0,0 +1,27 @@
1
+ # Security Policy
2
+
3
+ ## Reporting Security Issues
4
+
5
+ This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
6
+
7
+ To report a security issue, please contact the Stainless team at security@stainless.com.
8
+
9
+ ## Responsible Disclosure
10
+
11
+ We appreciate the efforts of security researchers and individuals who help us maintain the security of
12
+ SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
13
+ disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
14
+ before making any information public.
15
+
16
+ ## Reporting Non-SDK Related Security Issues
17
+
18
+ If you encounter security issues that are not directly related to SDKs but pertain to the services
19
+ or products provided by Modern Treasury please follow the respective company's security reporting guidelines.
20
+
21
+ ### Modern Treasury Terms and Policies
22
+
23
+ Please contact sdk-feedback@moderntreasury.com for any questions or concerns regarding security of our services.
24
+
25
+ ---
26
+
27
+ Thank you for helping us keep the SDKs and systems they interact with secure.
@@ -167,7 +167,8 @@ module ModernTreasury
167
167
  #
168
168
  # @param organization_id [String, nil] Defaults to `ENV["MODERN_TREASURY_ORGANIZATION_ID"]`
169
169
  #
170
- # @param base_url [String, nil] Override the default base URL for the API, e.g., `"https://api.example.com/v2/"`
170
+ # @param base_url [String, nil] Override the default base URL for the API, e.g.,
171
+ # `"https://api.example.com/v2/"`. Defaults to `ENV["MODERN_TREASURY_BASE_URL"]`
171
172
  #
172
173
  # @param max_retries [Integer] Max number of retries to attempt after a failed retryable request.
173
174
  #
@@ -181,7 +182,7 @@ module ModernTreasury
181
182
  def initialize(
182
183
  api_key: ENV["MODERN_TREASURY_API_KEY"],
183
184
  organization_id: ENV["MODERN_TREASURY_ORGANIZATION_ID"],
184
- base_url: nil,
185
+ base_url: ENV["MODERN_TREASURY_BASE_URL"],
185
186
  max_retries: DEFAULT_MAX_RETRIES,
186
187
  timeout: DEFAULT_TIMEOUT_IN_SECONDS,
187
188
  initial_retry_delay: DEFAULT_INITIAL_RETRY_DELAY,
@@ -22,27 +22,6 @@ module ModernTreasury
22
22
  # @return [String]
23
23
  attr_accessor :after_cursor
24
24
 
25
- # @api private
26
- #
27
- # @param client [ModernTreasury::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 Array
37
- replace(page_data.map { ModernTreasury::Internal::Type::Converter.coerce(model, _1) })
38
- else
39
- end
40
-
41
- @per_page = ModernTreasury::Internal::Util.coerce_integer(headers["X-Per-Page"])
42
-
43
- @after_cursor = headers["X-After-Cursor"]&.to_s
44
- end
45
-
46
25
  # @return [Boolean]
47
26
  def next_page?
48
27
  !after_cursor.nil?
@@ -67,18 +46,43 @@ module ModernTreasury
67
46
  unless block_given?
68
47
  raise ArgumentError.new("A block must be given to ##{__method__}")
69
48
  end
49
+
70
50
  page = self
71
51
  loop do
72
- page.each { blk.call(_1) }
52
+ page.each(&blk)
53
+
73
54
  break unless page.next_page?
74
55
  page = page.next_page
75
56
  end
76
57
  end
77
58
 
59
+ # @api private
60
+ #
61
+ # @param client [ModernTreasury::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 Array
70
+ replace(page_data.map { ModernTreasury::Internal::Type::Converter.coerce(@model, _1) })
71
+ else
72
+ end
73
+
74
+ @per_page = ModernTreasury::Internal::Util.coerce_integer(headers["X-Per-Page"])
75
+ @after_cursor = headers["X-After-Cursor"]&.to_s
76
+ end
77
+
78
+ # @api private
79
+ #
78
80
  # @return [String]
79
81
  def inspect
80
82
  # rubocop:disable Layout/LineLength
81
- "#<#{self.class}:0x#{object_id.to_s(16)} per_page=#{per_page.inspect} after_cursor=#{after_cursor.inspect}>"
83
+ model = ModernTreasury::Internal::Type::Converter.inspect(@model, depth: 1)
84
+
85
+ "#<#{self.class}[#{model}]:0x#{object_id.to_s(16)} per_page=#{per_page.inspect} after_cursor=#{after_cursor.inspect}>"
82
86
  # rubocop:enable Layout/LineLength
83
87
  end
84
88
  end
@@ -93,7 +93,11 @@ module ModernTreasury
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 ModernTreasury::Errors::APIConnectionError.new(url: url, message: message)
96
+ raise ModernTreasury::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 ModernTreasury
101
105
  case [url.scheme, location.scheme]
102
106
  in ["https", "http"]
103
107
  message = "Tried to redirect to a insecure URL"
104
- raise ModernTreasury::Errors::APIConnectionError.new(url: url, message: message)
108
+ raise ModernTreasury::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 ModernTreasury
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
 
@@ -353,7 +361,11 @@ module ModernTreasury
353
361
  self.class.reap_connection!(status, stream: stream)
354
362
 
355
363
  message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
356
- raise ModernTreasury::Errors::APIConnectionError.new(url: url, message: message)
364
+ raise ModernTreasury::Errors::APIConnectionError.new(
365
+ url: url,
366
+ response: response,
367
+ message: message
368
+ )
357
369
  in 300..399
358
370
  self.class.reap_connection!(status, stream: stream)
359
371
 
@@ -383,7 +395,7 @@ module ModernTreasury
383
395
  in (400..) | ModernTreasury::Errors::APIConnectionError
384
396
  self.class.reap_connection!(status, stream: stream)
385
397
 
386
- delay = retry_delay(response, retry_count: retry_count)
398
+ delay = retry_delay(response || {}, retry_count: retry_count)
387
399
  sleep(delay)
388
400
 
389
401
  send_request(
@@ -463,6 +475,8 @@ module ModernTreasury
463
475
  end
464
476
  end
465
477
 
478
+ # @api private
479
+ #
466
480
  # @return [String]
467
481
  def inspect
468
482
  # rubocop:disable Layout/LineLength
@@ -54,14 +54,18 @@ module ModernTreasury
54
54
  # @param blk [Proc]
55
55
  #
56
56
  # @yieldparam [String]
57
- # @return [Net::HTTPGenericRequest]
57
+ # @return [Array(Net::HTTPGenericRequest, Proc)]
58
58
  def build_request(request, &blk)
59
59
  method, url, headers, body = request.fetch_values(:method, :url, :headers, :body)
60
+
61
+ # ensure we construct a URI class of the right scheme
62
+ url = URI(url.to_s)
63
+
60
64
  req = Net::HTTPGenericRequest.new(
61
65
  method.to_s.upcase,
62
66
  !body.nil?,
63
67
  method != :head,
64
- url.to_s
68
+ url
65
69
  )
66
70
 
67
71
  headers.each { req[_1] = _2 }
@@ -75,12 +79,12 @@ module ModernTreasury
75
79
  in StringIO
76
80
  req["content-length"] ||= body.size.to_s unless req["transfer-encoding"]
77
81
  req.body_stream = ModernTreasury::Internal::Util::ReadIOAdapter.new(body, &blk)
78
- in IO | Enumerator
82
+ in Pathname | IO | Enumerator
79
83
  req["transfer-encoding"] ||= "chunked" unless req["content-length"]
80
84
  req.body_stream = ModernTreasury::Internal::Util::ReadIOAdapter.new(body, &blk)
81
85
  end
82
86
 
83
- req
87
+ [req, req.body_stream&.method(:close)]
84
88
  end
85
89
  end
86
90
 
@@ -123,13 +127,17 @@ module ModernTreasury
123
127
  def execute(request)
124
128
  url, deadline = request.fetch_values(:url, :deadline)
125
129
 
130
+ req = nil
126
131
  eof = false
127
132
  finished = false
133
+ closing = nil
134
+
135
+ # rubocop:disable Metrics/BlockLength
128
136
  enum = Enumerator.new do |y|
129
137
  with_pool(url, deadline: deadline) do |conn|
130
138
  next if finished
131
139
 
132
- req = self.class.build_request(request) do
140
+ req, closing = self.class.build_request(request) do
133
141
  self.class.calibrate_socket_timeout(conn, deadline)
134
142
  end
135
143
 
@@ -145,7 +153,7 @@ module ModernTreasury
145
153
  break if finished
146
154
 
147
155
  rsp.read_body do |bytes|
148
- y << bytes
156
+ y << bytes.force_encoding(Encoding::BINARY)
149
157
  break if finished
150
158
 
151
159
  self.class.calibrate_socket_timeout(conn, deadline)
@@ -154,8 +162,11 @@ module ModernTreasury
154
162
  end
155
163
  end
156
164
  rescue Timeout::Error
157
- raise ModernTreasury::Errors::APITimeoutError
165
+ raise ModernTreasury::Errors::APITimeoutError.new(url: url, request: req)
166
+ rescue StandardError
167
+ raise ModernTreasury::Errors::APIConnectionError.new(url: url, request: req)
158
168
  end
169
+ # rubocop:enable Metrics/BlockLength
159
170
 
160
171
  conn, _, response = enum.next
161
172
  body = ModernTreasury::Internal::Util.fused_enum(enum, external: true) do
@@ -165,7 +176,9 @@ module ModernTreasury
165
176
  rescue StopIteration
166
177
  nil
167
178
  end
179
+ ensure
168
180
  conn.finish if !eof && conn&.started?
181
+ closing&.call
169
182
  end
170
183
  [Integer(response.code), response, (response.body = body)]
171
184
  end
@@ -13,6 +13,10 @@ module ModernTreasury
13
13
  class ArrayOf
14
14
  include ModernTreasury::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, ModernTreasury::Internal::Type::Converter, Class]
17
21
  #
18
22
  # @param spec [Hash{Symbol=>Object}] .
@@ -24,7 +28,7 @@ module ModernTreasury
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
  #
@@ -79,12 +83,16 @@ module ModernTreasury
79
83
  #
80
84
  # @param value [Array<Object>, Object]
81
85
  #
86
+ # @param state [Hash{Symbol=>Object}] .
87
+ #
88
+ # @option state [Boolean] :can_retry
89
+ #
82
90
  # @return [Array<Object>, Object]
83
- def dump(value)
91
+ def dump(value, state:)
84
92
  target = item_type
85
93
  if value.is_a?(Array)
86
94
  value.map do
87
- ModernTreasury::Internal::Type::Converter.dump(target, _1)
95
+ ModernTreasury::Internal::Type::Converter.dump(target, _1, state: state)
88
96
  end
89
97
  else
90
98
  super
@@ -116,7 +124,18 @@ module ModernTreasury
116
124
  # @option spec [Boolean] :"nil?"
117
125
  def initialize(type_info, spec = {})
118
126
  @item_type_fn = ModernTreasury::Internal::Type::Converter.type_info(type_info || spec)
119
- @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 = ModernTreasury::Internal::Type::Converter.inspect(item_type, depth: depth.succ)
137
+
138
+ "#{self.class}[#{[items, nilable? ? 'nil' : nil].compact.join(' | ')}]"
120
139
  end
121
140
  end
122
141
  end
@@ -62,7 +62,7 @@ module ModernTreasury
62
62
 
63
63
  setter = "#{name_sym}="
64
64
  api_name = info.fetch(:api_name, name_sym)
65
- nilable = info[:nil?]
65
+ nilable = info.fetch(:nil?, false)
66
66
  const = if required && !nilable
67
67
  info.fetch(
68
68
  :const,
@@ -256,8 +256,12 @@ module ModernTreasury
256
256
  #
257
257
  # @param value [ModernTreasury::Internal::Type::BaseModel, Object]
258
258
  #
259
+ # @param state [Hash{Symbol=>Object}] .
260
+ #
261
+ # @option state [Boolean] :can_retry
262
+ #
259
263
  # @return [Hash{Object=>Object}, Object]
260
- def dump(value)
264
+ def dump(value, state:)
261
265
  unless (coerced = ModernTreasury::Internal::Util.coerce_hash(value)).is_a?(Hash)
262
266
  return super
263
267
  end
@@ -268,7 +272,7 @@ module ModernTreasury
268
272
  name = key.is_a?(String) ? key.to_sym : key
269
273
  case (field = known_fields[name])
270
274
  in nil
271
- acc.store(name, super(val))
275
+ acc.store(name, super(val, state: state))
272
276
  else
273
277
  api_name, mode, type_fn = field.fetch_values(:api_name, :mode, :type_fn)
274
278
  case mode
@@ -276,7 +280,10 @@ module ModernTreasury
276
280
  next
277
281
  else
278
282
  target = type_fn.call
279
- acc.store(api_name, ModernTreasury::Internal::Type::Converter.dump(target, val))
283
+ acc.store(
284
+ api_name,
285
+ ModernTreasury::Internal::Type::Converter.dump(target, val, state: state)
286
+ )
280
287
  end
281
288
  end
282
289
  end
@@ -341,12 +348,12 @@ module ModernTreasury
341
348
  # @param a [Object]
342
349
  #
343
350
  # @return [String]
344
- def to_json(*a) = self.class.dump(self).to_json(*a)
351
+ def to_json(*a) = ModernTreasury::Internal::Type::Converter.dump(self.class, self).to_json(*a)
345
352
 
346
353
  # @param a [Object]
347
354
  #
348
355
  # @return [String]
349
- def to_yaml(*a) = self.class.dump(self).to_yaml(*a)
356
+ def to_yaml(*a) = ModernTreasury::Internal::Type::Converter.dump(self.class, self).to_yaml(*a)
350
357
 
351
358
  # Create a new instance of a model.
352
359
  #
@@ -361,14 +368,42 @@ module ModernTreasury
361
368
  end
362
369
  end
363
370
 
371
+ class << self
372
+ # @api private
373
+ #
374
+ # @param depth [Integer]
375
+ #
376
+ # @return [String]
377
+ def inspect(depth: 0)
378
+ return super() if depth.positive?
379
+
380
+ depth = depth.succ
381
+ deferred = fields.transform_values do |field|
382
+ type, required, nilable = field.fetch_values(:type, :required, :nilable)
383
+ -> do
384
+ [
385
+ ModernTreasury::Internal::Type::Converter.inspect(type, depth: depth),
386
+ !required || nilable ? "nil" : nil
387
+ ].compact.join(" | ")
388
+ end
389
+ .tap { _1.define_singleton_method(:inspect) { call } }
390
+ end
391
+
392
+ "#{name}[#{deferred.inspect}]"
393
+ end
394
+ end
395
+
396
+ # @api private
397
+ #
364
398
  # @return [String]
365
399
  def inspect
366
- rows = self.class.known_fields.keys.map do
367
- "#{_1}=#{@data.key?(_1) ? public_send(_1) : ''}"
400
+ rows = @data.map do
401
+ "#{_1}=#{self.class.known_fields.key?(_1) ? public_send(_1).inspect : ''}"
368
402
  rescue ModernTreasury::Errors::ConversionError
369
- "#{_1}=#{@data.fetch(_1)}"
403
+ "#{_1}=#{_2.inspect}"
370
404
  end
371
- "#<#{self.class.name}:0x#{object_id.to_s(16)} #{rows.join(' ')}>"
405
+
406
+ "#<#{self.class}:0x#{object_id.to_s(16)} #{rows.join(' ')}>"
372
407
  end
373
408
  end
374
409
  end
@@ -36,6 +36,7 @@ module ModernTreasury
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 ModernTreasury
11
11
  class Boolean
12
12
  extend ModernTreasury::Internal::Type::Converter
13
13
 
14
+ private_class_method :new
15
+
14
16
  # @param other [Object]
15
17
  #
16
18
  # @return [Boolean]
@@ -45,8 +47,12 @@ module ModernTreasury
45
47
  # #
46
48
  # # @param value [Boolean, Object]
47
49
  # #
50
+ # # @param state [Hash{Symbol=>Object}] .
51
+ # #
52
+ # # @option state [Boolean] :can_retry
53
+ # #
48
54
  # # @return [Boolean, Object]
49
- # def dump(value) = super
55
+ # def dump(value, state:) = super
50
56
  end
51
57
  end
52
58
  end
@@ -26,20 +26,38 @@ module ModernTreasury
26
26
  #
27
27
  # @param value [Object]
28
28
  #
29
+ # @param state [Hash{Symbol=>Object}] .
30
+ #
31
+ # @option state [Boolean] :can_retry
32
+ #
29
33
  # @return [Object]
30
- def dump(value)
34
+ def dump(value, state:)
31
35
  case value
32
36
  in Array
33
- value.map { ModernTreasury::Internal::Type::Unknown.dump(_1) }
37
+ value.map { ModernTreasury::Internal::Type::Unknown.dump(_1, state: state) }
34
38
  in Hash
35
- value.transform_values { ModernTreasury::Internal::Type::Unknown.dump(_1) }
39
+ value.transform_values { ModernTreasury::Internal::Type::Unknown.dump(_1, state: state) }
36
40
  in ModernTreasury::Internal::Type::BaseModel
37
- value.class.dump(value)
41
+ value.class.dump(value, state: state)
42
+ in StringIO
43
+ value.string
44
+ in Pathname | IO
45
+ state[:can_retry] = false if value.is_a?(IO)
46
+ ModernTreasury::Internal::Util::SerializationAdapter.new(value)
38
47
  else
39
48
  value
40
49
  end
41
50
  end
42
51
 
52
+ # @api private
53
+ #
54
+ # @param depth [Integer]
55
+ #
56
+ # @return [String]
57
+ def inspect(depth: 0)
58
+ super()
59
+ end
60
+
43
61
  # rubocop:enable Lint/UnusedMethodArgument
44
62
 
45
63
  class << self
@@ -140,9 +158,9 @@ module ModernTreasury
140
158
  if value.is_a?(Integer)
141
159
  exactness[:yes] += 1
142
160
  return value
143
- elsif strictness == :strong
161
+ elsif strictness == :strong && Integer(value, exception: false) != value
144
162
  message = "no implicit conversion of #{value.class} into #{target.inspect}"
145
- raise TypeError.new(message)
163
+ raise value.is_a?(Numeric) ? ArgumentError.new(message) : TypeError.new(message)
146
164
  else
147
165
  Kernel.then do
148
166
  return Integer(value).tap { exactness[:maybe] += 1 }
@@ -182,18 +200,26 @@ module ModernTreasury
182
200
  rescue ArgumentError, TypeError => e
183
201
  raise e if strictness == :strong
184
202
  end
185
- in -> { _1 <= IO } if value.is_a?(String)
203
+ in -> { _1 <= StringIO } if value.is_a?(String)
186
204
  exactness[:yes] += 1
187
205
  return StringIO.new(value.b)
188
206
  else
189
207
  end
190
208
  in Symbol
191
- if (value.is_a?(Symbol) || value.is_a?(String)) && value.to_sym == target
192
- exactness[:yes] += 1
193
- return target
194
- elsif strictness == :strong
195
- message = "cannot convert non-matching #{value.class} into #{target.inspect}"
196
- raise ArgumentError.new(message)
209
+ case value
210
+ in Symbol | String
211
+ if value.to_sym == target
212
+ exactness[:yes] += 1
213
+ return target
214
+ else
215
+ exactness[:maybe] += 1
216
+ return value
217
+ end
218
+ else
219
+ if strictness == :strong
220
+ message = "cannot convert non-matching #{value.class} into #{target.inspect}"
221
+ raise ArgumentError.new(message)
222
+ end
197
223
  end
198
224
  else
199
225
  end
@@ -207,13 +233,36 @@ module ModernTreasury
207
233
  # @api private
208
234
  #
209
235
  # @param target [ModernTreasury::Internal::Type::Converter, Class]
236
+ #
210
237
  # @param value [Object]
211
238
  #
239
+ # @param state [Hash{Symbol=>Object}] .
240
+ #
241
+ # @option state [Boolean] :can_retry
242
+ #
212
243
  # @return [Object]
213
- def dump(target, value)
214
- # rubocop:disable Layout/LineLength
215
- target.is_a?(ModernTreasury::Internal::Type::Converter) ? target.dump(value) : ModernTreasury::Internal::Type::Unknown.dump(value)
216
- # rubocop:enable Layout/LineLength
244
+ def dump(target, value, state: {can_retry: true})
245
+ case target
246
+ in ModernTreasury::Internal::Type::Converter
247
+ target.dump(value, state: state)
248
+ else
249
+ ModernTreasury::Internal::Type::Unknown.dump(value, state: state)
250
+ end
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 ModernTreasury::Internal::Type::Converter
262
+ target.inspect(depth: depth.succ)
263
+ else
264
+ target.inspect
265
+ end
217
266
  end
218
267
  end
219
268
  end