test-test-sink-test-test2 0.0.5

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3c0f9378f1675d98710fa5ec56bd73650d25ca22789e0916102b29c485a47c28
4
+ data.tar.gz: 3051b450c74b423e9ba5561f1c15748619e819d4ee871b024122d8f7331772d4
5
+ SHA512:
6
+ metadata.gz: 420b8a6a0e53b0b8f4604b9c1d72eb0b03ddac8998a49fd7aefe34d4c8ecfa00dda11487ecfe25e3b1946058a4fa28a769b053fb37801f992733fd9ee7662062
7
+ data.tar.gz: f3a704f7fcd330785b1cea5d2c74ff80e12eecc472f4e1427f64635e5ee6553de922ef3a7c223e0d4d2a1331c936e9fc4ef009c2e5f280d14ccd96fe63d30e82
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Sink Ruby API library
2
+
3
+ The Sink Ruby library provides convenient access to the Sink REST API from any Ruby 3.0+
4
+ application.
5
+
6
+ It is generated with [Stainless](https://www.stainlessapi.com/).
7
+
8
+ ## Documentation
9
+
10
+ Documentation for the most recent version of this gem can be found [on RubyDoc](https://rubydoc.info/github/stainless-sdks/sink-ruby-public).
11
+
12
+ The underlying REST API documentation can be found on [stainlessapi.com](https://stainlessapi.com).
13
+
14
+ ## Installation
15
+
16
+ To use this gem during the beta, install directly from GitHub with Bundler by
17
+ adding the following to your application's `Gemfile`:
18
+
19
+ ```ruby
20
+ gem "sink", git: "https://github.com/stainless-sdks/sink-ruby-public", branch: "main"
21
+ ```
22
+
23
+ To fetch an initial copy of the gem:
24
+
25
+ ```sh
26
+ bundle install
27
+ ```
28
+
29
+ To update the version used by your application when updates are pushed to
30
+ GitHub:
31
+
32
+ ```sh
33
+ bundle update sink
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```ruby
39
+ require "sink"
40
+
41
+ sink = Sink::Client.new(
42
+ user_token: "My User Token", # defaults to ENV["SINK_CUSTOM_API_KEY_ENV"]
43
+ environment: "sandbox", # defaults to "production"
44
+ username: "Robert",
45
+ some_number_arg_required_no_default: 0,
46
+ some_number_arg_required_no_default_no_env: 0,
47
+ required_arg_no_env: "<example>"
48
+ )
49
+
50
+ card = sink.cards.create(
51
+ type: "SINGLE_USE",
52
+ exp_month: "08",
53
+ not_: "TEST",
54
+ shipping_address: {
55
+ "address1" => "180 Varick St",
56
+ "city" => "New York",
57
+ "country" => "USA",
58
+ "first_name" => "Jason",
59
+ "last_name" => "Mimosa",
60
+ "state" => "NY",
61
+ "postal_code" => "H0H0H0"
62
+ }
63
+ )
64
+
65
+ puts(card.token)
66
+ ```
67
+
68
+ ### Errors
69
+
70
+ When the library is unable to connect to the API, or if the API returns a
71
+ non-success status code (i.e., 4xx or 5xx response), a subclass of
72
+ `Sink::HTTP::Error` will be thrown:
73
+
74
+ ```ruby
75
+ begin
76
+ sink.cards.create(type: "an_incorrect_type")
77
+ rescue Sink::HTTP::Error => e
78
+ puts(e.code) # 400
79
+ end
80
+ ```
81
+
82
+ Error codes are as followed:
83
+
84
+ | Cause | Error Type |
85
+ | ---------------- | -------------------------- |
86
+ | HTTP 400 | `BadRequestError` |
87
+ | HTTP 401 | `AuthenticationError` |
88
+ | HTTP 403 | `PermissionDeniedError` |
89
+ | HTTP 404 | `NotFoundError` |
90
+ | HTTP 409 | `ConflictError` |
91
+ | HTTP 422 | `UnprocessableEntityError` |
92
+ | HTTP 429 | `RateLimitError` |
93
+ | HTTP >=500 | `InternalServerError` |
94
+ | Other HTTP error | `APIStatusError` |
95
+ | Timeout | `APITimeoutError` |
96
+ | Network error | `APIConnectionError` |
97
+
98
+ ### Retries
99
+
100
+ Certain errors will be automatically retried 1 times by default, with a short
101
+ exponential backoff. Connection errors (for example, due to a network
102
+ connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, >=500 Internal errors,
103
+ and timeouts will all be retried by default.
104
+
105
+ You can use the `max_retries` option to configure or disable this:
106
+
107
+ ```ruby
108
+ # Configure the default for all requests:
109
+ sink = Sink::Client.new(
110
+ max_retries: 0, # default is 1
111
+ username: "Robert",
112
+ some_number_arg_required_no_default: 0,
113
+ some_number_arg_required_no_default_no_env: 0,
114
+ required_arg_no_env: "<example>"
115
+ )
116
+
117
+ # Or, configure per-request:
118
+ sink.cards.provision_foo("my card token", digital_wallet: "GOOGLE_PAY", max_retries: 5)
119
+ ```
120
+
121
+ ### Timeouts
122
+
123
+ By default, requests will time out after 60 seconds.
124
+ Timeouts are applied separately to the initial connection and the overall request time,
125
+ so in some cases a request could wait 2\*timeout seconds before it fails.
126
+
127
+ You can use the `timeout` option to configure or disable this:
128
+
129
+ ```ruby
130
+ # Configure the default for all requests:
131
+ sink = Sink::Client.new(
132
+ timeout: nil, # default is 60
133
+ username: "Robert",
134
+ some_number_arg_required_no_default: 0,
135
+ some_number_arg_required_no_default_no_env: 0,
136
+ required_arg_no_env: "<example>"
137
+ )
138
+
139
+ # Or, configure per-request:
140
+ sink.cards.create(type: "DIGITAL", timeout: 5)
141
+ ```
142
+
143
+ ## Versioning
144
+
145
+ This package follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions. As the
146
+ library is in initial development and has a major version of `0`, APIs may change
147
+ at any time.
148
+
149
+ ## Requirements
150
+
151
+ Ruby 3.0 or higher.
@@ -0,0 +1,404 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sink
4
+ # @!visibility private
5
+ class BaseClient
6
+ attr_accessor :requester
7
+
8
+ # @param base_url [String]
9
+ # @param timeout [Integer, nil]
10
+ # @param headers [Hash{String => String}]
11
+ # @param max_retries [Integer]
12
+ # @param idempotency_header [String, nil]
13
+ def initialize(
14
+ base_url:,
15
+ timeout: nil,
16
+ headers: {},
17
+ max_retries: 0,
18
+ idempotency_header: nil
19
+ )
20
+ self.requester = PooledNetRequester.new
21
+ base_url_parsed = URI.parse(base_url)
22
+ @headers = Util.normalized_headers(
23
+ {
24
+ "X-Stainless-Lang" => "ruby",
25
+ "X-Stainless-Package-Version" => Sink::VERSION,
26
+ "X-Stainless-Runtime" => RUBY_ENGINE,
27
+ "X-Stainless-Runtime-Version" => RUBY_ENGINE_VERSION,
28
+ "Accept" => "application/json"
29
+ },
30
+ headers
31
+ )
32
+ @host = base_url_parsed.host
33
+ @scheme = base_url_parsed.scheme
34
+ @port = base_url_parsed.port
35
+ @base_path = self.class.normalize_path(base_url_parsed.path)
36
+ @max_retries = max_retries
37
+ @timeout = timeout
38
+ @idempotency_header = idempotency_header
39
+ end
40
+
41
+ # @return [Hash{String => String}]
42
+ def auth_headers
43
+ {}
44
+ end
45
+
46
+ def validate_request(req, opts)
47
+ if (body = req[:body])
48
+ # Body can be at least a Hash or Array, just check for Hash shape for now.
49
+ if body.is_a?(Hash)
50
+ body.each_key do |k|
51
+ unless k.is_a?(Symbol)
52
+ raise ArgumentError, "Request body keys must be Symbols, got #{k.inspect}"
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ unless opts.is_a?(Hash) || opts.is_a?(Sink::RequestOption)
59
+ raise ArgumentError, "Request `opts` must be a Hash or RequestOptions, got #{opts.inspect}"
60
+ end
61
+ opts.to_h.each_key do |k|
62
+ unless k.is_a?(Symbol)
63
+ raise ArgumentError, "Request `opts` keys must be Symbols, got #{k.inspect}"
64
+ end
65
+ unless (valid_keys = Sink::RequestOptions.options).include?(k)
66
+ raise ArgumentError, "Request `opts` keys must be one of #{valid_keys}, got #{k.inspect}"
67
+ end
68
+ end
69
+ end
70
+
71
+ def self.normalize_path(path)
72
+ path.gsub(/\/+/, "/")
73
+ end
74
+
75
+ def resolve_uri_elements(req)
76
+ from_args =
77
+ if req[:url]
78
+ uri = req[:url].is_a?(URI::Generic) ? req[:url] : URI.parse(req[:url])
79
+ {
80
+ host: uri.host,
81
+ scheme: uri.scheme,
82
+ path: uri.path,
83
+ query: CGI.parse(uri.query || ""),
84
+ port: uri.port
85
+ }
86
+ else
87
+ from_req = req.slice(:host, :scheme, :path, :port, :query)
88
+ from_req[:path] = self.class.normalize_path("/#{@base_path}/#{from_req[:path]}")
89
+ from_req
90
+ end
91
+
92
+ uri_components = {host: @host, scheme: @scheme, port: @port}.merge(from_args)
93
+
94
+ if req[:extra_query]
95
+ uri_components[:query] = Util.deep_merge(uri_components[:query], req[:extra_query], concat: true)
96
+ end
97
+
98
+ uri_components
99
+ end
100
+
101
+ def prep_request(options)
102
+ method = options.fetch(:method)
103
+
104
+ headers = Util.normalized_headers(@headers, auth_headers, options[:headers], options[:extra_headers])
105
+ if @idempotency_header && !headers[@idempotency_header] && ![:get, :head, :options].include?(method)
106
+ headers[@idempotency_header.to_s.downcase] = options[:idempotency_key] || generate_idempotency_key
107
+ end
108
+ if !headers.key?("x-stainless-retry-count")
109
+ headers["x-stainless-retry-count"] = "0"
110
+ end
111
+ headers.compact!
112
+ headers.transform_values!(&:to_s)
113
+
114
+ body =
115
+ case method
116
+ when :post, :put, :patch, :delete
117
+ body = options[:body]
118
+ if body
119
+ if headers["content-type"] == "application/json"
120
+ JSON.dump(body)
121
+ else
122
+ body
123
+ end
124
+ end
125
+ else
126
+ nil
127
+ end
128
+ if options[:extra_body]
129
+ body = Util.deep_merge(body, options[:extra_body])
130
+ end
131
+
132
+ url_elements = resolve_uri_elements(options)
133
+
134
+ {method: method, headers: headers, body: body}.merge(url_elements)
135
+ end
136
+
137
+ def generate_idempotency_key
138
+ "stainless-ruby-retry-#{SecureRandom.uuid}"
139
+ end
140
+
141
+ def should_retry?(response)
142
+ should_retry_header = response["x-should-retry"]
143
+
144
+ case should_retry_header
145
+ when "true"
146
+ true
147
+ when "false"
148
+ false
149
+ else
150
+ response_code = response.code.to_i
151
+ # retry on:
152
+ # 408: timeouts
153
+ # 409: locks
154
+ # 429: rate limits
155
+ # 500+: unknown errors
156
+ [408, 409, 429].include?(response_code) || response_code >= 500
157
+ end
158
+ end
159
+
160
+ def make_status_error(message:, body:, response:)
161
+ raise NotImplementedError
162
+ end
163
+
164
+ def make_status_error_from_response(response)
165
+ err_body =
166
+ begin
167
+ JSON.parse(response.body)
168
+ rescue StandardError
169
+ response
170
+ end
171
+
172
+ # We include the body in the error message as well as returning it
173
+ # since logging error messages is a common and quick way to assess what's
174
+ # wrong with a response.
175
+ message = "Error code: #{response.code}; Response: #{response.body}"
176
+
177
+ make_status_error(message: message, body: err_body, response: response)
178
+ end
179
+
180
+ def header_based_retry(response)
181
+ # Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it.
182
+ retry_after_millis = Float(response["retry-after-ms"], exception: false)
183
+ if retry_after_millis
184
+ retry_after = retry_after_millis / 1000.0
185
+ elsif response["retry-after"]
186
+ retry_after = Float(response["retry-after"], exception: false)
187
+ if retry_after.nil?
188
+ begin
189
+ base = Time.now
190
+ if response["x-stainless-mock-sleep-base"]
191
+ base = Time.httpdate(response["x-stainless-mock-sleep-base"])
192
+ end
193
+ retry_after = Time.httpdate(response["retry-after"]) - base
194
+ rescue StandardError # rubocop:disable Lint/SuppressedException
195
+ end
196
+ end
197
+ end
198
+ retry_after
199
+ rescue StandardError # rubocop:disable Lint/SuppressedException
200
+ end
201
+
202
+ def send_request(request, max_retries:, timeout:, redirect_count:)
203
+ delay = 0.6
204
+ max_delay = 8.0
205
+ # Don't send the current retry count in the headers if the caller modified the header defaults.
206
+ should_send_retry_count = request[:headers]["x-stainless-retry-count"] == "0"
207
+ retries = 0
208
+ loop do # rubocop:disable Metrics/BlockLength
209
+ if should_send_retry_count
210
+ request[:headers]["x-stainless-retry-count"] = retries.to_s
211
+ end
212
+
213
+ begin
214
+ response = @requester.execute(request, timeout: timeout)
215
+ status = response.code.to_i
216
+
217
+ if status < 300
218
+ return response
219
+ elsif status < 400
220
+ begin
221
+ prev_uri = URI.parse(Util.uri_from_req(request, absolute: true))
222
+ location = URI.join(prev_uri, response["location"])
223
+ rescue ArgumentError
224
+ message = "server responded with status #{status} but no valid location header"
225
+ raise HTTP::APIConnectionError.new(message: message, request: request)
226
+ end
227
+ # from whatwg fetch spec
228
+ if redirect_count == 20
229
+ message = "failed to complete the request within 20 redirects"
230
+ raise HTTP::APIConnectionError.new(message: message, request: request)
231
+ end
232
+ if location.scheme != "http" && location.scheme != "https"
233
+ message = "tried to redirect to a non-http URL"
234
+ raise HTTP::APIConnectionError.new(message: message, request: request)
235
+ end
236
+ request = request.merge(resolve_uri_elements({url: location}))
237
+ # from whatwg fetch spec
238
+ if ([301, 302].include?(status) && request[:method] == :post) || (status == 303)
239
+ request[:method] = request[:method] == :head ? :head : :get
240
+ request[:body] = nil
241
+ request[:headers] = request[:headers].reject do |k|
242
+ %w[content-encoding content-language content-location content-type content-length].include?(k)
243
+ end
244
+ end
245
+ # from undici
246
+ if Sink::Util.uri_origin(prev_uri) != Sink::Util.uri_origin(location)
247
+ request[:headers] = request[:headers].reject do |k|
248
+ %w[authorization cookie proxy-authorization host].include?(k)
249
+ end
250
+ end
251
+ return send_request(
252
+ request,
253
+ max_retries: max_retries,
254
+ timeout: timeout,
255
+ redirect_count: redirect_count + 1
256
+ )
257
+ end
258
+ rescue Net::HTTPBadResponse
259
+ if retries >= max_retries
260
+ message = "failed to complete the request within #{max_retries} retries"
261
+ raise HTTP::APIConnectionError.new(message: message, request: request)
262
+ end
263
+ rescue Timeout::Error
264
+ if retries >= max_retries
265
+ message = "failed to complete the request within #{max_retries} retries"
266
+ raise HTTP::APITimeoutError.new(message: message, request: request)
267
+ end
268
+ end
269
+
270
+ if (response && !should_retry?(response)) || retries >= max_retries
271
+ raise make_status_error_from_response(response)
272
+ end
273
+
274
+ retries += 1
275
+ base_delay = header_based_retry(response)
276
+ if base_delay
277
+ delay = base_delay
278
+ else
279
+ base_delay = (delay * (2**retries))
280
+ jitter_factor = 1 - (0.25 * rand)
281
+ delay = (base_delay * jitter_factor).clamp(0, max_delay)
282
+ end
283
+
284
+ if response&.key?("x-stainless-mock-sleep")
285
+ request[:headers]["x-stainless-mock-slept"] = delay
286
+ else
287
+ sleep(delay)
288
+ end
289
+ end
290
+ end
291
+
292
+ # Execute the request specified by req + opts. This is the method that all
293
+ # resource methods call into.
294
+ # Params req & opts are kept separate up until this point so that we can
295
+ # validate opts as it was given to us by the user.
296
+ def request(req, opts)
297
+ validate_request(req, opts)
298
+ options = Util.deep_merge(req, opts)
299
+ request_args = prep_request(options)
300
+ response = send_request(
301
+ request_args,
302
+ max_retries: opts.fetch(:max_retries, @max_retries),
303
+ timeout: opts.fetch(:timeout, @timeout),
304
+ redirect_count: 0
305
+ )
306
+ raw_data =
307
+ case response.content_type
308
+ when "application/json"
309
+ begin
310
+ data = JSON.parse(response.body, symbolize_names: true)
311
+ req[:unwrap] ? data[req[:unwrap]] : data
312
+ rescue JSON::ParserError
313
+ response.body
314
+ end
315
+ # TODO: parsing other response types
316
+ else
317
+ response.body
318
+ end
319
+
320
+ if (page = req[:page])
321
+ model = req.fetch(:model)
322
+ page.new(model, raw_data, response, self, req, opts)
323
+ elsif (model = req[:model])
324
+ Converter.convert(model, raw_data)
325
+ else
326
+ raw_data
327
+ end
328
+ end
329
+
330
+ # @return [String]
331
+ def inspect
332
+ base_url = Util.uri_from_req(
333
+ {host: @host, scheme: @scheme, port: @port, path: @base_path},
334
+ absolute: true
335
+ )
336
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} base_url=#{base_url.inspect} max_retries=#{@max_retries.inspect} timeout=#{@timeout.inspect}>"
337
+ end
338
+ end
339
+
340
+ class Error < StandardError
341
+ end
342
+
343
+ module HTTP
344
+ class Error < Sink::Error
345
+ end
346
+
347
+ class ResponseError < Error
348
+ attr_reader :response, :body
349
+
350
+ # @!attribute [r] code
351
+ # @return [Integer]
352
+ attr_reader :code
353
+
354
+ def initialize(message:, response:, body:)
355
+ super(message)
356
+ @response = response
357
+ @body = body
358
+ @code = response.code.to_i
359
+ end
360
+ end
361
+
362
+ class RequestError < Error
363
+ attr_reader :request
364
+
365
+ def initialize(message:, request:)
366
+ super(message)
367
+ @request = request
368
+ end
369
+ end
370
+
371
+ class BadRequestError < ResponseError
372
+ end
373
+
374
+ class AuthenticationError < ResponseError
375
+ end
376
+
377
+ class PermissionDeniedError < ResponseError
378
+ end
379
+
380
+ class NotFoundError < ResponseError
381
+ end
382
+
383
+ class ConflictError < ResponseError
384
+ end
385
+
386
+ class UnprocessableEntityError < ResponseError
387
+ end
388
+
389
+ class RateLimitError < ResponseError
390
+ end
391
+
392
+ class InternalServerError < ResponseError
393
+ end
394
+
395
+ class APIStatusError < ResponseError
396
+ end
397
+
398
+ class APIConnectionError < RequestError
399
+ end
400
+
401
+ class APITimeoutError < RequestError
402
+ end
403
+ end
404
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sink
4
+ # @!visibility private
5
+ module Converter
6
+ # Based on `value`, returns a value that conforms to `type`, to the extent possible:
7
+ # - If the given `value` conforms to `type` already, the given `value`.
8
+ # - If it's possible and safe to convert the given `value` to `type`, such a converted value.
9
+ # - Otherwise, the given `value` unaltered.
10
+ def self.convert(type, value)
11
+ # If `type.is_a?(Converter)`, `type` is an instance of a class that mixes
12
+ # in `Converter`, indicating that the type should define `#convert` on this
13
+ # instance. This is used for Enums and ArrayOfs, which are parameterized.
14
+ # If `type.include?(Converter)`, `type` is a class that mixes in `Converter`
15
+ # which we use to signal that the class should define `.convert`. This is
16
+ # used where the class itself fully specifies the type, like model classes.
17
+ # We don't monkey-patch Ruby-native types, so those need to be handled
18
+ # directly.
19
+ if type.is_a?(Converter) || type.include?(Converter)
20
+ type.convert(value)
21
+ elsif type == Date
22
+ Date.parse(value)
23
+ elsif type == Time
24
+ Time.parse(value)
25
+ elsif type == NilClass
26
+ nil
27
+ elsif type == Float
28
+ value.is_a?(Numeric) ? value.to_f : value
29
+ elsif [Object, Integer, String, Hash].include?(type)
30
+ value
31
+ else
32
+ raise StandardError, "Unexpected type #{type}"
33
+ end
34
+ end
35
+ end
36
+
37
+ # When we don't know what to expect for the value.
38
+ # @!visibility private
39
+ class Unknown
40
+ include Converter
41
+
42
+ def self.convert(value)
43
+ value
44
+ end
45
+ end
46
+
47
+ # Ruby has no Boolean class; this is something for models to refer to.
48
+ # @!visibility private
49
+ class BooleanModel
50
+ include Converter
51
+
52
+ def self.convert(value)
53
+ value
54
+ end
55
+ end
56
+
57
+ # A value from among a specified list of options. OpenAPI enum values map to
58
+ # Ruby values in the SDK as follows:
59
+ # boolean => true|false
60
+ # integer => Integer
61
+ # float => Float
62
+ # string => Symbol
63
+ # We can therefore convert string values to Symbols, but can't convert other
64
+ # values safely.
65
+ # @!visibility private
66
+ class Enum
67
+ include Converter
68
+
69
+ def self.convert(value)
70
+ if value.is_a?(String)
71
+ value.to_sym
72
+ else
73
+ value
74
+ end
75
+ end
76
+
77
+ # @return [Array<Symbol>] All of the valid Symbol values for this enum.
78
+ def self.values
79
+ @values ||= constants.map { |c| const_get(c) }
80
+ end
81
+ end
82
+
83
+ # Array of items of a given type.
84
+ # @!visibility private
85
+ class ArrayOf
86
+ include Converter
87
+
88
+ def initialize(items_type_info = nil, enum: nil)
89
+ @items_type_fn = enum || (items_type_info.is_a?(Proc) ? items_type_info : -> { items_type_info })
90
+ end
91
+
92
+ def convert(value)
93
+ items_type = @items_type_fn.call
94
+ value.map { |item| Converter.convert(items_type, item) }
95
+ end
96
+ end
97
+
98
+ class BaseModel
99
+ include Converter
100
+
101
+ # @!visibility private
102
+ # Assumes superclass fields are totally defined before fields are accessed / defined on subclasses.
103
+ # @return [Hash{Symbol => Hash{Symbol => Object}}]
104
+ def self.fields
105
+ @fields ||= (superclass == BaseModel ? {} : superclass.fields.dup)
106
+ end
107
+
108
+ # @!visibility private
109
+ # @param name_sym [Symbol]
110
+ # @param api_name [Symbol, nil]
111
+ # @param type_info [Proc, Object]
112
+ # @param mode [Symbol]
113
+ # @return [void]
114
+ def self.add_field(name_sym, api_name:, type_info:, mode:)
115
+ type_fn = type_info.is_a?(Proc) ? type_info : -> { type_info }
116
+ key = api_name || name_sym
117
+ fields[name_sym] = {type_fn: type_fn, mode: mode, key: key}
118
+
119
+ define_method(name_sym) do
120
+ field_type = type_fn.call
121
+ Converter.convert(field_type, @data[key])
122
+ rescue StandardError
123
+ name = self.class.name.split("::").last
124
+ raise ConversionError,
125
+ "Failed to parse #{name}.#{name_sym} as #{field_type.inspect}. " \
126
+ "To get the unparsed API response, use #{name}[:#{key}]."
127
+ end
128
+ define_method("#{name_sym}=") { |val| @data[key] = val }
129
+ end
130
+
131
+ # @!visibility private
132
+ # NB `required` is just a signal to the reader. We don't do runtime validation anyway.
133
+ def self.required(name_sym, type_info = nil, mode = :rw, api_name: nil, enum: nil)
134
+ add_field(name_sym, api_name: api_name, type_info: enum || type_info, mode: mode)
135
+ end
136
+
137
+ # @!visibility private
138
+ # NB `optional` is just a signal to the reader. We don't do runtime validation anyway.
139
+ def self.optional(name_sym, type_info = nil, mode = :rw, api_name: nil, enum: nil)
140
+ add_field(name_sym, api_name: api_name, type_info: enum || type_info, mode: mode)
141
+ end
142
+
143
+ # @!visibility private
144
+ def self.convert(data)
145
+ new(data)
146
+ end
147
+
148
+ # Create a new instance of a model.
149
+ # @param data [Hash{Symbol => Object}] Raw data to initialize the model with.
150
+ def initialize(data = {})
151
+ @data = {}
152
+ # TODO: what if data isn't a hash?
153
+ data.each do |field_name, value|
154
+ next if value.nil?
155
+
156
+ field = self.class.fields[field_name.to_sym]
157
+ if field
158
+ next if field[:mode] == :w
159
+ end
160
+ @data[field_name.to_sym] = value
161
+ end
162
+ end
163
+
164
+ # Returns a Hash of the data underlying this object.
165
+ # Keys are Symbols and values are the raw values from the response.
166
+ # The return value indicates which values were ever set on the object -
167
+ # i.e. there will be a key in this hash if they ever were, even if the
168
+ # set value was nil.
169
+ # This method is not recursive.
170
+ # The returned value is shared by the object, so it should not be mutated.
171
+ #
172
+ # @return [Hash{Symbol => Object}] Data for this object.
173
+ def to_h
174
+ @data
175
+ end
176
+
177
+ alias_method :to_hash, :to_h
178
+
179
+ # Returns the raw value associated with the given key, if found. Otherwise, nil is returned.
180
+ # It is valid to lookup keys that are not in the API spec, for example to access
181
+ # undocumented features.
182
+ # This method does not parse response data into higher-level types.
183
+ # Lookup by anything other than a Symbol is an ArgumentError.
184
+ #
185
+ # @param key [Symbol] Key to look up by.
186
+ #
187
+ # @return [Object, nil] The raw value at the given key.
188
+ def [](key)
189
+ if !key.instance_of?(Symbol)
190
+ raise ArgumentError, "Expected symbol key for lookup, got #{key.inspect}"
191
+ end
192
+ @data[key]
193
+ end
194
+
195
+ # @param keys [Array<Symbol>, nil]
196
+ # @return [Hash{Symbol => Object}]
197
+ def deconstruct_keys(keys)
198
+ (keys || self.class.fields.keys).to_h do |k|
199
+ if !k.instance_of?(Symbol)
200
+ raise ArgumentError, "Expected symbol key for lookup, got #{k.inspect}"
201
+ end
202
+
203
+ if !self.class.fields.key?(k)
204
+ raise KeyError, "Expected one of #{self.class.fields.keys}, got #{k.inspect}"
205
+ end
206
+
207
+ [k, method(k).call]
208
+ end
209
+ end
210
+
211
+ # @return [String]
212
+ def inspect
213
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} #{deconstruct_keys(nil).map do |k, v|
214
+ "#{k}=#{v.inspect}"
215
+ end.join(' ')}>"
216
+ end
217
+
218
+ # @return [String]
219
+ def to_s
220
+ @data.to_s
221
+ end
222
+ end
223
+
224
+ class ConversionError < Error
225
+ end
226
+ end
@@ -0,0 +1,391 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sink
4
+ class Client < BaseClient
5
+ # Default max number of retries to attempt after a failed retryable request.
6
+ DEFAULT_MAX_RETRIES = 1
7
+
8
+ # Client option
9
+ # @return [String]
10
+ attr_reader :user_token
11
+
12
+ # Client option
13
+ # @return [String]
14
+ attr_reader :api_key_header
15
+
16
+ # Client option
17
+ # @return [String]
18
+ attr_reader :api_key_query
19
+
20
+ # Client option
21
+ # @return [String]
22
+ attr_reader :username
23
+
24
+ # Client option
25
+ # @return [String]
26
+ attr_reader :client_id
27
+
28
+ # Client option
29
+ # @return [String]
30
+ attr_reader :client_secret
31
+
32
+ # Client option
33
+ # @return [Boolean]
34
+ attr_reader :some_boolean_arg
35
+
36
+ # Client option
37
+ # @return [Integer]
38
+ attr_reader :some_integer_arg
39
+
40
+ # Client option
41
+ # @return [Float]
42
+ attr_reader :some_number_arg
43
+
44
+ # Client option
45
+ # @return [Float]
46
+ attr_reader :some_number_arg_required
47
+
48
+ # Client option
49
+ # @return [Float]
50
+ attr_reader :some_number_arg_required_no_default
51
+
52
+ # Client option
53
+ # @return [Float]
54
+ attr_reader :some_number_arg_required_no_default_no_env
55
+
56
+ # Client option
57
+ # @return [String]
58
+ attr_reader :required_arg_no_env
59
+
60
+ # Client option
61
+ # @return [String]
62
+ attr_reader :required_arg_no_env_with_default
63
+
64
+ # Client option
65
+ # @return [String]
66
+ attr_reader :client_path_param
67
+
68
+ # Client option
69
+ # @return [String]
70
+ attr_reader :camel_case_path
71
+
72
+ # Client option
73
+ # @return [String]
74
+ attr_reader :client_query_param
75
+
76
+ # Client option
77
+ # @return [String]
78
+ attr_reader :client_path_or_query_param
79
+
80
+ # @return [Sink::Resources::Testing]
81
+ attr_reader :testing
82
+
83
+ # @return [Sink::Resources::ComplexQueries]
84
+ attr_reader :complex_queries
85
+
86
+ # @return [Sink::Resources::Casing]
87
+ attr_reader :casing
88
+
89
+ # @return [Sink::Resources::Tools]
90
+ attr_reader :tools
91
+
92
+ # @return [Sink::Resources::UndocumentedResource]
93
+ attr_reader :undocumented_resource
94
+
95
+ # @return [Sink::Resources::MethodConfig]
96
+ attr_reader :method_config
97
+
98
+ # @return [Sink::Resources::Streaming]
99
+ attr_reader :streaming
100
+
101
+ # @return [Sink::Resources::PaginationTests]
102
+ attr_reader :pagination_tests
103
+
104
+ # @return [Sink::Resources::Docstrings]
105
+ attr_reader :docstrings
106
+
107
+ # @return [Sink::Resources::InvalidSchemas]
108
+ attr_reader :invalid_schemas
109
+
110
+ # @return [Sink::Resources::ResourceRefs]
111
+ attr_reader :resource_refs
112
+
113
+ # @return [Sink::Resources::Cards]
114
+ attr_reader :cards
115
+
116
+ # @return [Sink::Resources::Files]
117
+ attr_reader :files
118
+
119
+ # @return [Sink::Resources::Resources]
120
+ attr_reader :resources
121
+
122
+ # @return [Sink::Resources::ConfigTools]
123
+ attr_reader :config_tools
124
+
125
+ # Stainless API company
126
+ # @return [Sink::Resources::Company]
127
+ attr_reader :company
128
+
129
+ # @return [Sink::Resources::OpenAPIFormats]
130
+ attr_reader :openapi_formats
131
+
132
+ # @return [Sink::Resources::Parent]
133
+ attr_reader :parent
134
+
135
+ # @return [Sink::Resources::Envelopes]
136
+ attr_reader :envelopes
137
+
138
+ # @return [Sink::Resources::Types]
139
+ attr_reader :types
140
+
141
+ # @return [Sink::Resources::Clients]
142
+ attr_reader :clients
143
+
144
+ # @return [Sink::Resources::Names]
145
+ attr_reader :names
146
+
147
+ # Widget is love
148
+ # Widget is life
149
+ # @return [Sink::Resources::Widgets]
150
+ attr_reader :widgets
151
+
152
+ # @return [Sink::Resources::Responses]
153
+ attr_reader :responses
154
+
155
+ # @return [Sink::Resources::PathParams]
156
+ attr_reader :path_params
157
+
158
+ # @return [Sink::Resources::PositionalParams]
159
+ attr_reader :positional_params
160
+
161
+ # @return [Sink::Resources::EmptyBody]
162
+ attr_reader :empty_body
163
+
164
+ # @return [Sink::Resources::QueryParams]
165
+ attr_reader :query_params
166
+
167
+ # @return [Sink::Resources::BodyParams]
168
+ attr_reader :body_params
169
+
170
+ # @return [Sink::Resources::HeaderParams]
171
+ attr_reader :header_params
172
+
173
+ # @return [Sink::Resources::MixedParams]
174
+ attr_reader :mixed_params
175
+
176
+ # @return [Sink::Resources::MakeAmbiguousSchemasLooser]
177
+ attr_reader :make_ambiguous_schemas_looser
178
+
179
+ # @return [Sink::Resources::MakeAmbiguousSchemasExplicit]
180
+ attr_reader :make_ambiguous_schemas_explicit
181
+
182
+ # @return [Sink::Resources::DecoratorTests]
183
+ attr_reader :decorator_tests
184
+
185
+ # @return [Sink::Resources::Tests]
186
+ attr_reader :tests
187
+
188
+ # @return [Sink::Resources::DeeplyNested]
189
+ attr_reader :deeply_nested
190
+
191
+ # @return [Sink::Resources::Version1_30Names]
192
+ attr_reader :version_1_30_names
193
+
194
+ # @return [Sink::Resources::Recursion]
195
+ attr_reader :recursion
196
+
197
+ # @return [Sink::Resources::SharedQueryParams]
198
+ attr_reader :shared_query_params
199
+
200
+ # @return [Sink::Resources::ModelReferencedInParentAndChild]
201
+ attr_reader :model_referenced_in_parent_and_child
202
+
203
+ # Creates and returns a new client for interacting with the API.
204
+ #
205
+ # @param environment ["production", "sandbox", nil] Specifies the environment to use for the API.
206
+ #
207
+ # Each environment maps to a different base URL:
208
+ #
209
+ # - `production` corresponds to `https://demo.stainlessapi.com/`
210
+ # - `sandbox` corresponds to `https://demo-sanbox.stainlessapi.com/`
211
+ # @param base_url [String, nil] Override the default base URL for the API, e.g., `"https://api.example.com/v2/"`
212
+ # @param user_token [String, nil] The API Key for the SINK API, sent as a bearer token Defaults to
213
+ # `ENV["SINK_CUSTOM_API_KEY_ENV"]`
214
+ # @param api_key_header [String, nil] The API Key for the SINK API, sent as an api key header Defaults to
215
+ # `ENV["SINK_CUSTOM_API_KEY_HEADER_ENV"]`
216
+ # @param api_key_query [String, nil] The API Key for the SINK API, sent as an api key query Defaults to
217
+ # `ENV["SINK_CUSTOM_API_KEY_QUERY_ENV"]`
218
+ # @param username [String, nil] Defaults to `ENV["SINK_USER"]`
219
+ # @param client_id [String, nil] Defaults to `ENV["SINK_CLIENT_ID"]`
220
+ # @param client_secret [String, nil] Defaults to `ENV["SINK_CLIENT_SECRET"]`
221
+ # @param some_boolean_arg [Boolean, nil] Defaults to `ENV["SINK_SOME_BOOLEAN_ARG"]`
222
+ # @param some_integer_arg [Integer, nil] Defaults to `ENV["SINK_SOME_INTEGER_ARG"]`
223
+ # @param some_number_arg [Float, nil] Defaults to `ENV["SINK_SOME_NUMBER_ARG"]`
224
+ # @param some_number_arg_required [Float, nil] Defaults to `ENV["SINK_SOME_NUMBER_ARG"]`
225
+ # @param some_number_arg_required_no_default [Float, nil] Defaults to `ENV["SINK_SOME_NUMBER_ARG"]`
226
+ # @param some_number_arg_required_no_default_no_env [Float, nil]
227
+ # @param required_arg_no_env [String, nil]
228
+ # @param required_arg_no_env_with_default [String, nil]
229
+ # @param client_path_param [String, nil]
230
+ # @param camel_case_path [String, nil]
231
+ # @param client_query_param [String, nil]
232
+ # @param client_path_or_query_param [String, nil]
233
+ # @param max_retries [Integer] Max number of retries to attempt after a failed retryable request.
234
+ #
235
+ # @return [Sink::Client]
236
+ def initialize(
237
+ environment: nil,
238
+ base_url: nil,
239
+ user_token: nil,
240
+ api_key_header: nil,
241
+ api_key_query: nil,
242
+ username: nil,
243
+ client_id: nil,
244
+ client_secret: nil,
245
+ some_boolean_arg: nil,
246
+ some_integer_arg: nil,
247
+ some_number_arg: nil,
248
+ some_number_arg_required: nil,
249
+ some_number_arg_required_no_default: nil,
250
+ some_number_arg_required_no_default_no_env: nil,
251
+ required_arg_no_env: nil,
252
+ required_arg_no_env_with_default: nil,
253
+ client_path_param: nil,
254
+ camel_case_path: nil,
255
+ client_query_param: nil,
256
+ client_path_or_query_param: nil,
257
+ max_retries: DEFAULT_MAX_RETRIES,
258
+ timeout: 60
259
+ )
260
+ environments = {"production" => "https://demo.stainlessapi.com/", "sandbox" => "https://demo-sanbox.stainlessapi.com/"}
261
+ if environment && base_url
262
+ raise ArgumentError, "both environment and base_url given, expected only one"
263
+ elsif environment
264
+ if !environments.key?(environment.to_s)
265
+ raise ArgumentError, "environment must be one of #{environments.keys}, got #{environment}"
266
+ end
267
+ base_url = environments[environment.to_s]
268
+ elsif !base_url
269
+ base_url = "https://demo.stainlessapi.com/"
270
+ end
271
+
272
+ username_header = [username, ENV["SINK_USER"]].find { |v| !v.nil? }
273
+ if username_header.nil?
274
+ raise ArgumentError, "username is required"
275
+ end
276
+ client_secret_header = [client_secret, ENV["SINK_CLIENT_SECRET"], "hellosecret"].find { |v| !v.nil? }
277
+ some_integer_arg_header = [some_integer_arg, ENV["SINK_SOME_INTEGER_ARG"], 123].find { |v| !v.nil? }
278
+ headers = {
279
+ "My-Api-Version" => "11",
280
+ "X-Enable-Metrics" => "1",
281
+ "X-Client-UserName" => username_header,
282
+ "X-Client-Secret" => client_secret_header,
283
+ "X-Integer" => some_integer_arg_header
284
+ }
285
+
286
+ idempotency_header = "Idempotency-Key"
287
+
288
+ @user_token = [user_token, ENV["SINK_CUSTOM_API_KEY_ENV"]].find { |v| !v.nil? }
289
+ @api_key_header = [api_key_header, ENV["SINK_CUSTOM_API_KEY_HEADER_ENV"]].find { |v| !v.nil? }
290
+ @api_key_query = [api_key_query, ENV["SINK_CUSTOM_API_KEY_QUERY_ENV"]].find { |v| !v.nil? }
291
+ @client_id = [client_id, ENV["SINK_CLIENT_ID"]].find { |v| !v.nil? }
292
+ @some_boolean_arg = [some_boolean_arg, ENV["SINK_SOME_BOOLEAN_ARG"], true].find { |v| !v.nil? }
293
+ @some_number_arg = [some_number_arg, ENV["SINK_SOME_NUMBER_ARG"], 1.2].find { |v| !v.nil? }
294
+ @some_number_arg_required = [some_number_arg_required, ENV["SINK_SOME_NUMBER_ARG"], 1.2].find do |v|
295
+ !v.nil?
296
+ end
297
+ @some_number_arg_required_no_default = [
298
+ some_number_arg_required_no_default,
299
+ ENV["SINK_SOME_NUMBER_ARG"]
300
+ ].find do |v|
301
+ !v.nil?
302
+ end
303
+ if @some_number_arg_required_no_default.nil?
304
+ raise ArgumentError, "some_number_arg_required_no_default is required"
305
+ end
306
+ @some_number_arg_required_no_default_no_env = some_number_arg_required_no_default_no_env
307
+ if @some_number_arg_required_no_default_no_env.nil?
308
+ raise ArgumentError, "some_number_arg_required_no_default_no_env is required"
309
+ end
310
+ @required_arg_no_env = required_arg_no_env
311
+ if @required_arg_no_env.nil?
312
+ raise ArgumentError, "required_arg_no_env is required"
313
+ end
314
+ @required_arg_no_env_with_default = [required_arg_no_env_with_default, "hi!"].find { |v| !v.nil? }
315
+ @client_query_param = client_query_param
316
+
317
+ super(
318
+ base_url: base_url,
319
+ max_retries: max_retries,
320
+ timeout: timeout,
321
+ headers: headers,
322
+ idempotency_header: idempotency_header
323
+ )
324
+
325
+ @testing = Sink::Resources::Testing.new(client: self)
326
+ @complex_queries = Sink::Resources::ComplexQueries.new(client: self)
327
+ @casing = Sink::Resources::Casing.new(client: self)
328
+ @tools = Sink::Resources::Tools.new(client: self)
329
+ @undocumented_resource = Sink::Resources::UndocumentedResource.new(client: self)
330
+ @method_config = Sink::Resources::MethodConfig.new(client: self)
331
+ @streaming = Sink::Resources::Streaming.new(client: self)
332
+ @pagination_tests = Sink::Resources::PaginationTests.new(client: self)
333
+ @docstrings = Sink::Resources::Docstrings.new(client: self)
334
+ @invalid_schemas = Sink::Resources::InvalidSchemas.new(client: self)
335
+ @resource_refs = Sink::Resources::ResourceRefs.new(client: self)
336
+ @cards = Sink::Resources::Cards.new(client: self)
337
+ @files = Sink::Resources::Files.new(client: self)
338
+ @resources = Sink::Resources::Resources.new(client: self)
339
+ @config_tools = Sink::Resources::ConfigTools.new(client: self)
340
+ @company = Sink::Resources::Company.new(client: self)
341
+ @openapi_formats = Sink::Resources::OpenAPIFormats.new(client: self)
342
+ @parent = Sink::Resources::Parent.new(client: self)
343
+ @envelopes = Sink::Resources::Envelopes.new(client: self)
344
+ @types = Sink::Resources::Types.new(client: self)
345
+ @clients = Sink::Resources::Clients.new(client: self)
346
+ @names = Sink::Resources::Names.new(client: self)
347
+ @widgets = Sink::Resources::Widgets.new(client: self)
348
+ @responses = Sink::Resources::Responses.new(client: self)
349
+ @path_params = Sink::Resources::PathParams.new(client: self)
350
+ @positional_params = Sink::Resources::PositionalParams.new(client: self)
351
+ @empty_body = Sink::Resources::EmptyBody.new(client: self)
352
+ @query_params = Sink::Resources::QueryParams.new(client: self)
353
+ @body_params = Sink::Resources::BodyParams.new(client: self)
354
+ @header_params = Sink::Resources::HeaderParams.new(client: self)
355
+ @mixed_params = Sink::Resources::MixedParams.new(client: self)
356
+ @make_ambiguous_schemas_looser = Sink::Resources::MakeAmbiguousSchemasLooser.new(client: self)
357
+ @make_ambiguous_schemas_explicit = Sink::Resources::MakeAmbiguousSchemasExplicit.new(client: self)
358
+ @decorator_tests = Sink::Resources::DecoratorTests.new(client: self)
359
+ @tests = Sink::Resources::Tests.new(client: self)
360
+ @deeply_nested = Sink::Resources::DeeplyNested.new(client: self)
361
+ @version_1_30_names = Sink::Resources::Version1_30Names.new(client: self)
362
+ @recursion = Sink::Resources::Recursion.new(client: self)
363
+ @shared_query_params = Sink::Resources::SharedQueryParams.new(client: self)
364
+ @model_referenced_in_parent_and_child = Sink::Resources::ModelReferencedInParentAndChild.new(client: self)
365
+ end
366
+
367
+ # @!visibility private
368
+ def make_status_error(message:, body:, response:)
369
+ case response.code.to_i
370
+ when 400
371
+ Sink::HTTP::BadRequestError.new(message: message, response: response, body: body)
372
+ when 401
373
+ Sink::HTTP::AuthenticationError.new(message: message, response: response, body: body)
374
+ when 403
375
+ Sink::HTTP::PermissionDeniedError.new(message: message, response: response, body: body)
376
+ when 404
377
+ Sink::HTTP::NotFoundError.new(message: message, response: response, body: body)
378
+ when 409
379
+ Sink::HTTP::ConflictError.new(message: message, response: response, body: body)
380
+ when 422
381
+ Sink::HTTP::UnprocessableEntityError.new(message: message, response: response, body: body)
382
+ when 429
383
+ Sink::HTTP::RateLimitError.new(message: message, response: response, body: body)
384
+ when 500..599
385
+ Sink::HTTP::InternalServerError.new(message: message, response: response, body: body)
386
+ else
387
+ Sink::HTTP::APIStatusError.new(message: message, response: response, body: body)
388
+ end
389
+ end
390
+ end
391
+ end
data/lib/sink/util.rb ADDED
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sink
4
+ # @!visibility private
5
+ class Util
6
+ # Use this to indicate that a value should be explicitly removed from a data structure
7
+ # when using `Sink::Util.deep_merge`.
8
+ # E.g. merging `{a: 1}` and `{a: OMIT}` should produce `{}`, where merging `{a: 1}` and
9
+ # `{}` would produce `{a: 1}`.
10
+ OMIT = Object.new.freeze
11
+
12
+ # Recursively merge one hash with another.
13
+ # If the values at a given key are not both hashes, just take the new value.
14
+ # @param concat [true, false] whether to merge sequences by concatenation
15
+ def self.deep_merge(left, right, concat: false)
16
+ right_cleaned = if right.is_a?(Hash)
17
+ right.reject { |_, value| value == OMIT }
18
+ else
19
+ right
20
+ end
21
+
22
+ if left.is_a?(Hash) && right_cleaned.is_a?(Hash)
23
+ left
24
+ .reject { |key, _| right[key] == OMIT }
25
+ .merge(right_cleaned) do |_k, old_val, new_val|
26
+ deep_merge(old_val, new_val, concat: concat)
27
+ end
28
+ elsif left.is_a?(Array) && right_cleaned.is_a?(Array) && concat
29
+ left.concat(right_cleaned)
30
+ else
31
+ right_cleaned
32
+ end
33
+ end
34
+
35
+ def self.coerce_integer(str)
36
+ Integer(str, exception: false) || str
37
+ end
38
+
39
+ def self.coerce_float(str)
40
+ Float(str, exception: false) || str
41
+ end
42
+
43
+ def self.coerce_boolean(input)
44
+ case input
45
+ in "true"
46
+ true
47
+ in "false"
48
+ false
49
+ else
50
+ input
51
+ end
52
+ end
53
+
54
+ def self.uri_from_req(req, absolute:)
55
+ query_string = ("?#{URI.encode_www_form(req[:query])}" if req[:query])
56
+ uri = String.new
57
+ if absolute
58
+ uri << "#{req[:scheme]}://#{req[:host]}"
59
+ if req[:port] && !(req[:scheme] == "https" && req[:port] == 443) && !(req[:scheme] == "http" && req[:port] == 80)
60
+ uri << ":#{req[:port]}"
61
+ end
62
+ end
63
+ uri << ((req[:path] || "/") + (query_string || ""))
64
+ end
65
+
66
+ def self.uri_origin(uri)
67
+ if uri.respond_to?(:origin)
68
+ uri.origin
69
+ else
70
+ "#{uri.scheme}://#{uri.host}#{uri.port == uri.default_port ? '' : ":#{uri.port}"}"
71
+ end
72
+ end
73
+
74
+ def self.normalized_headers(*headers)
75
+ {}.merge(*headers.compact).transform_keys(&:downcase)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sink
4
+ VERSION = "0.0.5"
5
+ end
data/lib/sink.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Standard libraries.
4
+ require "cgi"
5
+ require "date"
6
+ require "json"
7
+ require "net/http"
8
+ require "securerandom"
9
+ require "time"
10
+ require "uri"
11
+
12
+ # Gems.
13
+ require "connection_pool"
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: test-test-sink-test-test2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Sink
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: connection_pool
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email: dev@stainlessapi.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - README.md
33
+ files:
34
+ - README.md
35
+ - lib/sink.rb
36
+ - lib/sink/base_client.rb
37
+ - lib/sink/base_model.rb
38
+ - lib/sink/client.rb
39
+ - lib/sink/util.rb
40
+ - lib/sink/version.rb
41
+ homepage: https://rubydoc.info/github/ms-jpq/test-publishing-yard
42
+ licenses: []
43
+ metadata:
44
+ homepage_uri: https://rubydoc.info/github/ms-jpq/test-publishing-yard
45
+ source_code_uri: https://github.com/ms-jpq/test-publishing-yard
46
+ rubygems_mfa_required: 'true'
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 3.0.0
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.3.27
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Ruby library to access the Sink API
66
+ test_files: []