yasmina-motor-ruby 0.0.8
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 +7 -0
- data/.fern/metadata.json +10 -0
- data/.fern/replay.lock +10 -0
- data/.fernignore +4 -0
- data/.rubocop.yml +103 -0
- data/CONTRIBUTING.md +120 -0
- data/LICENSE +21 -0
- data/README.md +173 -0
- data/Rakefile +20 -0
- data/custom.gemspec.rb +16 -0
- data/lib/yasminaai/client.rb +37 -0
- data/lib/yasminaai/environment.rb +9 -0
- data/lib/yasminaai/errors/api_error.rb +8 -0
- data/lib/yasminaai/errors/client_error.rb +17 -0
- data/lib/yasminaai/errors/redirect_error.rb +8 -0
- data/lib/yasminaai/errors/response_error.rb +42 -0
- data/lib/yasminaai/errors/server_error.rb +11 -0
- data/lib/yasminaai/errors/timeout_error.rb +8 -0
- data/lib/yasminaai/internal/errors/constraint_error.rb +10 -0
- data/lib/yasminaai/internal/errors/type_error.rb +10 -0
- data/lib/yasminaai/internal/http/base_request.rb +51 -0
- data/lib/yasminaai/internal/http/raw_client.rb +215 -0
- data/lib/yasminaai/internal/iterators/cursor_item_iterator.rb +28 -0
- data/lib/yasminaai/internal/iterators/cursor_page_iterator.rb +63 -0
- data/lib/yasminaai/internal/iterators/item_iterator.rb +65 -0
- data/lib/yasminaai/internal/iterators/offset_item_iterator.rb +30 -0
- data/lib/yasminaai/internal/iterators/offset_page_iterator.rb +103 -0
- data/lib/yasminaai/internal/json/request.rb +41 -0
- data/lib/yasminaai/internal/json/serializable.rb +25 -0
- data/lib/yasminaai/internal/multipart/multipart_encoder.rb +141 -0
- data/lib/yasminaai/internal/multipart/multipart_form_data.rb +78 -0
- data/lib/yasminaai/internal/multipart/multipart_form_data_part.rb +51 -0
- data/lib/yasminaai/internal/multipart/multipart_request.rb +40 -0
- data/lib/yasminaai/internal/types/array.rb +47 -0
- data/lib/yasminaai/internal/types/boolean.rb +34 -0
- data/lib/yasminaai/internal/types/enum.rb +56 -0
- data/lib/yasminaai/internal/types/hash.rb +36 -0
- data/lib/yasminaai/internal/types/model/field.rb +38 -0
- data/lib/yasminaai/internal/types/model.rb +208 -0
- data/lib/yasminaai/internal/types/type.rb +35 -0
- data/lib/yasminaai/internal/types/union.rb +161 -0
- data/lib/yasminaai/internal/types/unknown.rb +15 -0
- data/lib/yasminaai/internal/types/utils.rb +116 -0
- data/lib/yasminaai/ot_ps/client.rb +79 -0
- data/lib/yasminaai/ot_ps/types/post_issue_otp_request.rb +21 -0
- data/lib/yasminaai/ot_ps/types/post_quote_otp_request.rb +15 -0
- data/lib/yasminaai/policies/client.rb +144 -0
- data/lib/yasminaai/policies/types/get_policies_car_policy_request.rb +11 -0
- data/lib/yasminaai/policies/types/get_policies_request.rb +35 -0
- data/lib/yasminaai/policies/types/post_policies_request.rb +21 -0
- data/lib/yasminaai/quotes/client.rb +164 -0
- data/lib/yasminaai/quotes/types/delete_quote_requests_id_request.rb +11 -0
- data/lib/yasminaai/quotes/types/delete_quote_requests_id_response.rb +11 -0
- data/lib/yasminaai/quotes/types/get_quote_requests_id_request.rb +11 -0
- data/lib/yasminaai/quotes/types/get_quote_requests_request.rb +17 -0
- data/lib/yasminaai/quotes/types/post_quote_requests_request.rb +37 -0
- data/lib/yasminaai/quotes/types/post_quote_requests_request_accept_language.rb +13 -0
- data/lib/yasminaai/quotes/types/post_quote_requests_request_drivers_item.rb +15 -0
- data/lib/yasminaai/types/bad_request_error_body.rb +11 -0
- data/lib/yasminaai/types/benefit.rb +21 -0
- data/lib/yasminaai/types/company_quote.rb +25 -0
- data/lib/yasminaai/types/company_quote_type.rb +12 -0
- data/lib/yasminaai/types/error.rb +11 -0
- data/lib/yasminaai/types/paginated_policy_response.rb +35 -0
- data/lib/yasminaai/types/paginated_quote_response.rb +35 -0
- data/lib/yasminaai/types/pagination_link.rb +13 -0
- data/lib/yasminaai/types/policy.rb +39 -0
- data/lib/yasminaai/types/policy_aggregates.rb +14 -0
- data/lib/yasminaai/types/policy_month_aggregate.rb +11 -0
- data/lib/yasminaai/types/quote_price.rb +19 -0
- data/lib/yasminaai/types/quote_request_aggregates.rb +12 -0
- data/lib/yasminaai/types/quote_response.rb +37 -0
- data/lib/yasminaai/types/quote_response_drivers_item.rb +13 -0
- data/lib/yasminaai/types/quote_response_quotes_item.rb +25 -0
- data/lib/yasminaai/types/quote_response_quotes_item_type.rb +12 -0
- data/lib/yasminaai/types/unauthorized_error_body.rb +11 -0
- data/lib/yasminaai/version.rb +5 -0
- data/lib/yasminaai.rb +72 -0
- data/reference.md +872 -0
- metadata +121 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
module Http
|
|
6
|
+
# @api private
|
|
7
|
+
class BaseRequest
|
|
8
|
+
attr_reader :base_url, :path, :method, :headers, :query, :request_options
|
|
9
|
+
|
|
10
|
+
# @param base_url [String] The base URL for the request
|
|
11
|
+
# @param path [String] The path for the request
|
|
12
|
+
# @param method [String] The HTTP method for the request (:get, :post, etc.)
|
|
13
|
+
# @param headers [Hash] Additional headers for the request (optional)
|
|
14
|
+
# @param query [Hash] Query parameters for the request (optional)
|
|
15
|
+
# @param request_options [Yasminaai::RequestOptions, Hash{Symbol=>Object}, nil]
|
|
16
|
+
def initialize(base_url:, path:, method:, headers: {}, query: {}, request_options: {})
|
|
17
|
+
@base_url = base_url
|
|
18
|
+
@path = path
|
|
19
|
+
@method = method
|
|
20
|
+
@headers = headers
|
|
21
|
+
@query = query
|
|
22
|
+
@request_options = request_options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [Hash] The query parameters merged with additional query parameters from request options.
|
|
26
|
+
def encode_query
|
|
27
|
+
additional_query = @request_options&.dig(:additional_query_parameters) || @request_options&.dig("additional_query_parameters") || {}
|
|
28
|
+
@query.merge(additional_query)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Child classes should implement:
|
|
32
|
+
# - encode_headers: Returns the encoded HTTP request headers.
|
|
33
|
+
# - encode_body: Returns the encoded HTTP request body.
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Merges additional_headers from request_options into sdk_headers, filtering out
|
|
38
|
+
# any keys that collide with SDK-set or client-protected headers (case-insensitive).
|
|
39
|
+
# @param sdk_headers [Hash] Headers set by the SDK for this request type.
|
|
40
|
+
# @param protected_keys [Array<String>] Additional header keys that must not be overridden.
|
|
41
|
+
# @return [Hash] The merged headers.
|
|
42
|
+
def merge_additional_headers(sdk_headers, protected_keys: [])
|
|
43
|
+
additional_headers = @request_options&.dig(:additional_headers) || @request_options&.dig("additional_headers") || {}
|
|
44
|
+
all_protected = (sdk_headers.keys + protected_keys).to_set { |k| k.to_s.downcase }
|
|
45
|
+
filtered = additional_headers.reject { |key, _| all_protected.include?(key.to_s.downcase) }
|
|
46
|
+
sdk_headers.merge(filtered)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
module Http
|
|
6
|
+
# @api private
|
|
7
|
+
class RawClient
|
|
8
|
+
# Default HTTP status codes that trigger a retry
|
|
9
|
+
RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504, 521, 522, 524].freeze
|
|
10
|
+
# Initial delay between retries in seconds
|
|
11
|
+
INITIAL_RETRY_DELAY = 0.5
|
|
12
|
+
# Maximum delay between retries in seconds
|
|
13
|
+
MAX_RETRY_DELAY = 60.0
|
|
14
|
+
# Jitter factor for randomizing retry delays (20%)
|
|
15
|
+
JITTER_FACTOR = 0.2
|
|
16
|
+
|
|
17
|
+
# @return [String] The base URL for requests
|
|
18
|
+
attr_reader :base_url
|
|
19
|
+
|
|
20
|
+
# @param base_url [String] The base url for the request.
|
|
21
|
+
# @param max_retries [Integer] The number of times to retry a failed request, defaults to 2.
|
|
22
|
+
# @param timeout [Float] The timeout for the request, defaults to 60.0 seconds.
|
|
23
|
+
# @param headers [Hash] The headers for the request.
|
|
24
|
+
def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {})
|
|
25
|
+
@base_url = base_url
|
|
26
|
+
@max_retries = max_retries
|
|
27
|
+
@timeout = timeout
|
|
28
|
+
@default_headers = {
|
|
29
|
+
"X-Fern-Language": "Ruby",
|
|
30
|
+
"X-Fern-SDK-Name": "yasminaai",
|
|
31
|
+
"X-Fern-SDK-Version": "0.0.1"
|
|
32
|
+
}.merge(headers)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param request [Yasminaai::Internal::Http::BaseRequest] The HTTP request.
|
|
36
|
+
# @return [HTTP::Response] The HTTP response.
|
|
37
|
+
def send(request)
|
|
38
|
+
url = build_url(request)
|
|
39
|
+
attempt = 0
|
|
40
|
+
response = nil
|
|
41
|
+
|
|
42
|
+
loop do
|
|
43
|
+
http_request = build_http_request(
|
|
44
|
+
url:,
|
|
45
|
+
method: request.method,
|
|
46
|
+
headers: request.encode_headers(protected_keys: @default_headers.keys),
|
|
47
|
+
body: request.encode_body
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
conn = connect(url)
|
|
51
|
+
conn.open_timeout = @timeout
|
|
52
|
+
conn.read_timeout = @timeout
|
|
53
|
+
conn.write_timeout = @timeout
|
|
54
|
+
conn.continue_timeout = @timeout
|
|
55
|
+
|
|
56
|
+
response = conn.request(http_request)
|
|
57
|
+
|
|
58
|
+
break unless should_retry?(response, attempt)
|
|
59
|
+
|
|
60
|
+
delay = retry_delay(response, attempt)
|
|
61
|
+
sleep(delay)
|
|
62
|
+
attempt += 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
response
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Determines if a request should be retried based on the response status code.
|
|
69
|
+
# @param response [Net::HTTPResponse] The HTTP response.
|
|
70
|
+
# @param attempt [Integer] The current retry attempt (0-indexed).
|
|
71
|
+
# @return [Boolean] Whether the request should be retried.
|
|
72
|
+
def should_retry?(response, attempt)
|
|
73
|
+
return false if attempt >= @max_retries
|
|
74
|
+
|
|
75
|
+
status = response.code.to_i
|
|
76
|
+
RETRYABLE_STATUSES.include?(status)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Calculates the delay before the next retry attempt using exponential backoff with jitter.
|
|
80
|
+
# Respects Retry-After header if present.
|
|
81
|
+
# @param response [Net::HTTPResponse] The HTTP response.
|
|
82
|
+
# @param attempt [Integer] The current retry attempt (0-indexed).
|
|
83
|
+
# @return [Float] The delay in seconds before the next retry.
|
|
84
|
+
def retry_delay(response, attempt)
|
|
85
|
+
# Check for Retry-After header (can be seconds or HTTP date)
|
|
86
|
+
retry_after = response["Retry-After"]
|
|
87
|
+
if retry_after
|
|
88
|
+
delay = parse_retry_after(retry_after)
|
|
89
|
+
return [delay, MAX_RETRY_DELAY].min if delay&.positive?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Exponential backoff with jitter: base_delay * 2^attempt
|
|
93
|
+
base_delay = INITIAL_RETRY_DELAY * (2**attempt)
|
|
94
|
+
add_jitter([base_delay, MAX_RETRY_DELAY].min)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Parses the Retry-After header value.
|
|
98
|
+
# @param value [String] The Retry-After header value (seconds or HTTP date).
|
|
99
|
+
# @return [Float, nil] The delay in seconds, or nil if parsing fails.
|
|
100
|
+
def parse_retry_after(value)
|
|
101
|
+
# Try parsing as integer (seconds)
|
|
102
|
+
seconds = Integer(value, exception: false)
|
|
103
|
+
return seconds.to_f if seconds
|
|
104
|
+
|
|
105
|
+
# Try parsing as HTTP date
|
|
106
|
+
begin
|
|
107
|
+
retry_time = Time.httpdate(value)
|
|
108
|
+
delay = retry_time - Time.now
|
|
109
|
+
delay.positive? ? delay : nil
|
|
110
|
+
rescue ArgumentError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Adds random jitter to a delay value.
|
|
116
|
+
# @param delay [Float] The base delay in seconds.
|
|
117
|
+
# @return [Float] The delay with jitter applied.
|
|
118
|
+
def add_jitter(delay)
|
|
119
|
+
jitter = delay * JITTER_FACTOR * (rand - 0.5) * 2
|
|
120
|
+
[delay + jitter, 0].max
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
LOCALHOST_HOSTS = %w[localhost 127.0.0.1 [::1]].freeze
|
|
124
|
+
|
|
125
|
+
# @param request [Yasminaai::Internal::Http::BaseRequest] The HTTP request.
|
|
126
|
+
# @return [URI::Generic] The URL.
|
|
127
|
+
def build_url(request)
|
|
128
|
+
encoded_query = request.encode_query
|
|
129
|
+
|
|
130
|
+
# If the path is already an absolute URL, use it directly
|
|
131
|
+
if request.path.start_with?("http://", "https://")
|
|
132
|
+
url = request.path
|
|
133
|
+
url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any?
|
|
134
|
+
parsed = URI.parse(url)
|
|
135
|
+
validate_https!(parsed)
|
|
136
|
+
return parsed
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
path = request.path.start_with?("/") ? request.path[1..] : request.path
|
|
140
|
+
base = request.base_url || @base_url
|
|
141
|
+
url = "#{base.chomp("/")}/#{path}"
|
|
142
|
+
url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any?
|
|
143
|
+
parsed = URI.parse(url)
|
|
144
|
+
validate_https!(parsed)
|
|
145
|
+
parsed
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Raises if the URL uses http:// for a non-localhost host, which would
|
|
149
|
+
# send authentication credentials in plaintext.
|
|
150
|
+
# @param url [URI::Generic] The parsed URL.
|
|
151
|
+
def validate_https!(url)
|
|
152
|
+
return if url.scheme != "http"
|
|
153
|
+
return if LOCALHOST_HOSTS.include?(url.host)
|
|
154
|
+
|
|
155
|
+
raise ArgumentError,
|
|
156
|
+
"Refusing to send request to non-HTTPS URL: #{url}. " \
|
|
157
|
+
"HTTP is only allowed for localhost. Use HTTPS or pass a localhost URL."
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @param url [URI::Generic] The url to the resource.
|
|
161
|
+
# @param method [String] The HTTP method to use.
|
|
162
|
+
# @param headers [Hash] The headers for the request.
|
|
163
|
+
# @param body [String, nil] The body for the request.
|
|
164
|
+
# @return [HTTP::Request] The HTTP request.
|
|
165
|
+
def build_http_request(url:, method:, headers: {}, body: nil)
|
|
166
|
+
request = Net::HTTPGenericRequest.new(
|
|
167
|
+
method,
|
|
168
|
+
!body.nil?,
|
|
169
|
+
method != "HEAD",
|
|
170
|
+
url
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
request_headers = @default_headers.merge(headers)
|
|
174
|
+
request_headers.each { |name, value| request[name] = value }
|
|
175
|
+
request.body = body if body
|
|
176
|
+
|
|
177
|
+
request
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @param query [Hash] The query for the request.
|
|
181
|
+
# @return [String, nil] The encoded query.
|
|
182
|
+
def encode_query(query)
|
|
183
|
+
query.to_h.empty? ? nil : URI.encode_www_form(query)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @param url [URI::Generic] The url to connect to.
|
|
187
|
+
# @return [Net::HTTP] The HTTP connection.
|
|
188
|
+
def connect(url)
|
|
189
|
+
is_https = (url.scheme == "https")
|
|
190
|
+
|
|
191
|
+
port = if url.port
|
|
192
|
+
url.port
|
|
193
|
+
elsif is_https
|
|
194
|
+
Net::HTTP.https_default_port
|
|
195
|
+
else
|
|
196
|
+
Net::HTTP.http_default_port
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
http = Net::HTTP.new(url.host, port)
|
|
200
|
+
http.use_ssl = is_https
|
|
201
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER if is_https
|
|
202
|
+
# NOTE: We handle retries at the application level with HTTP status code awareness,
|
|
203
|
+
# so we set max_retries to 0 to disable Net::HTTP's built-in network-level retries.
|
|
204
|
+
http.max_retries = 0
|
|
205
|
+
http
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# @return [String]
|
|
209
|
+
def inspect
|
|
210
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} @base_url=#{@base_url.inspect}>"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
class CursorItemIterator < ItemIterator
|
|
6
|
+
# Instantiates a CursorItemIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields individual items from it.
|
|
7
|
+
#
|
|
8
|
+
# @param initial_cursor [String] The initial cursor to use when iterating, if any.
|
|
9
|
+
# @param cursor_field [Symbol] The field in API responses to extract the next cursor from.
|
|
10
|
+
# @param item_field [Symbol] The field in API responses to extract the items to iterate over.
|
|
11
|
+
# @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API.
|
|
12
|
+
# @return [Yasminaai::Internal::CursorItemIterator]
|
|
13
|
+
def initialize(initial_cursor:, cursor_field:, item_field:, &)
|
|
14
|
+
super()
|
|
15
|
+
@item_field = item_field
|
|
16
|
+
@page_iterator = CursorPageIterator.new(initial_cursor:, cursor_field:, &)
|
|
17
|
+
@page = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns the CursorPageIterator mediating access to the underlying API.
|
|
21
|
+
#
|
|
22
|
+
# @return [Yasminaai::Internal::CursorPageIterator]
|
|
23
|
+
def pages
|
|
24
|
+
@page_iterator
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
class CursorPageIterator
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# The raw HTTP response from the most recent page response.
|
|
9
|
+
# @return [Net::HTTPResponse, nil]
|
|
10
|
+
attr_reader :http_response
|
|
11
|
+
|
|
12
|
+
# Instantiates a CursorPageIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields pages of items.
|
|
13
|
+
#
|
|
14
|
+
# @param initial_cursor [String] The initial cursor to use when iterating, if any.
|
|
15
|
+
# @param cursor_field [Symbol] The name of the field in API responses to extract the next cursor from.
|
|
16
|
+
# @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API.
|
|
17
|
+
# The block should return a two-element array: [parsed_page, raw_http_response].
|
|
18
|
+
# @return [Yasminaai::Internal::CursorPageIterator]
|
|
19
|
+
def initialize(initial_cursor:, cursor_field:, &block)
|
|
20
|
+
@need_initial_load = initial_cursor.nil?
|
|
21
|
+
@cursor = initial_cursor
|
|
22
|
+
@cursor_field = cursor_field
|
|
23
|
+
@get_next_page = block
|
|
24
|
+
@http_response = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Iterates over each page returned by the API.
|
|
28
|
+
#
|
|
29
|
+
# @param block [Proc] The block which each retrieved page is yielded to.
|
|
30
|
+
# @return [NilClass]
|
|
31
|
+
def each(&block)
|
|
32
|
+
while (page = next_page)
|
|
33
|
+
block.call(page)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Whether another page will be available from the API.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def next?
|
|
41
|
+
@need_initial_load || !@cursor.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Retrieves the next page from the API.
|
|
45
|
+
#
|
|
46
|
+
# @return [Object, nil]
|
|
47
|
+
def next_page
|
|
48
|
+
return if !@need_initial_load && @cursor.nil?
|
|
49
|
+
|
|
50
|
+
@need_initial_load = false
|
|
51
|
+
result = @get_next_page.call(@cursor)
|
|
52
|
+
if result.is_a?(Array)
|
|
53
|
+
fetched_page, raw_response = result
|
|
54
|
+
@http_response = raw_response
|
|
55
|
+
else
|
|
56
|
+
fetched_page = result
|
|
57
|
+
end
|
|
58
|
+
@cursor = fetched_page.send(@cursor_field)
|
|
59
|
+
fetched_page
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
class ItemIterator
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# The raw HTTP response from the most recent page response.
|
|
9
|
+
# @return [Net::HTTPResponse, nil]
|
|
10
|
+
def http_response
|
|
11
|
+
@page_iterator&.http_response
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Iterates over each item returned by the API.
|
|
15
|
+
#
|
|
16
|
+
# @param block [Proc] The block which each retrieved item is yielded to.
|
|
17
|
+
# @return [NilClass]
|
|
18
|
+
def each(&block)
|
|
19
|
+
while (item = next_element)
|
|
20
|
+
block.call(item)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Whether another item will be available from the API.
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def next?
|
|
28
|
+
load_next_page if @page.nil?
|
|
29
|
+
return false if @page.nil?
|
|
30
|
+
|
|
31
|
+
return true if any_items_in_cached_page?
|
|
32
|
+
|
|
33
|
+
load_next_page
|
|
34
|
+
any_items_in_cached_page?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Retrieves the next item from the API.
|
|
38
|
+
def next_element
|
|
39
|
+
item = next_item_from_cached_page
|
|
40
|
+
return item if item
|
|
41
|
+
|
|
42
|
+
load_next_page
|
|
43
|
+
next_item_from_cached_page
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def next_item_from_cached_page
|
|
49
|
+
return unless @page
|
|
50
|
+
|
|
51
|
+
@page.send(@item_field).shift
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def any_items_in_cached_page?
|
|
55
|
+
return false unless @page
|
|
56
|
+
|
|
57
|
+
!@page.send(@item_field).empty?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def load_next_page
|
|
61
|
+
@page = @page_iterator.next_page
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
class OffsetItemIterator < ItemIterator
|
|
6
|
+
# Instantiates an OffsetItemIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields the individual items from it.
|
|
7
|
+
#
|
|
8
|
+
# @param initial_page [Integer] The initial page or offset to start from when iterating.
|
|
9
|
+
# @param item_field [Symbol] The name of the field in API responses to extract the items to iterate over.
|
|
10
|
+
# @param has_next_field [Symbol] The name of the field in API responses containing a boolean of whether another page exists.
|
|
11
|
+
# @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1)
|
|
12
|
+
# @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API.
|
|
13
|
+
#
|
|
14
|
+
# @return [Yasminaai::Internal::OffsetItemIterator]
|
|
15
|
+
def initialize(initial_page:, item_field:, has_next_field:, step:, &)
|
|
16
|
+
super()
|
|
17
|
+
@item_field = item_field
|
|
18
|
+
@page_iterator = OffsetPageIterator.new(initial_page:, item_field:, has_next_field:, step:, &)
|
|
19
|
+
@page = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the OffsetPageIterator that is mediating access to the underlying API.
|
|
23
|
+
#
|
|
24
|
+
# @return [Yasminaai::Internal::OffsetPageIterator]
|
|
25
|
+
def pages
|
|
26
|
+
@page_iterator
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
class OffsetPageIterator
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# The raw HTTP response from the most recent page response.
|
|
9
|
+
# @return [Net::HTTPResponse, nil]
|
|
10
|
+
attr_reader :http_response
|
|
11
|
+
|
|
12
|
+
# Instantiates an OffsetPageIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields pages of items from it.
|
|
13
|
+
#
|
|
14
|
+
# @param initial_page [Integer] The initial page to use when iterating, if any.
|
|
15
|
+
# @param item_field [Symbol] The field to pull the list of items to iterate over.
|
|
16
|
+
# @param has_next_field [Symbol] The field to pull the boolean of whether a next page exists from, if any.
|
|
17
|
+
# @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1)
|
|
18
|
+
# @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API.
|
|
19
|
+
# The block should return a two-element array: [parsed_page, raw_http_response].
|
|
20
|
+
# @return [Yasminaai::Internal::OffsetPageIterator]
|
|
21
|
+
def initialize(initial_page:, item_field:, has_next_field:, step:, &block)
|
|
22
|
+
@page_number = initial_page || (step ? 0 : 1)
|
|
23
|
+
@item_field = item_field
|
|
24
|
+
@has_next_field = has_next_field
|
|
25
|
+
@step = step
|
|
26
|
+
@get_next_page = block
|
|
27
|
+
|
|
28
|
+
# A cache of whether the API has another page, if it gives us that information...
|
|
29
|
+
@next_page = nil
|
|
30
|
+
# ...or the actual next page, preloaded, if it doesn't.
|
|
31
|
+
@has_next_page = nil
|
|
32
|
+
|
|
33
|
+
@http_response = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Iterates over each page returned by the API.
|
|
37
|
+
#
|
|
38
|
+
# @param block [Proc] The block which each retrieved page is yielded to.
|
|
39
|
+
# @return [NilClass]
|
|
40
|
+
def each(&block)
|
|
41
|
+
while (page = next_page)
|
|
42
|
+
block.call(page)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Whether another page will be available from the API.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def next?
|
|
50
|
+
return @has_next_page unless @has_next_page.nil?
|
|
51
|
+
return true if @next_page
|
|
52
|
+
|
|
53
|
+
fetched_page = fetch_page(@page_number)
|
|
54
|
+
fetched_page_items = fetched_page&.send(@item_field)
|
|
55
|
+
if fetched_page_items.nil? || fetched_page_items.empty?
|
|
56
|
+
@has_next_page = false
|
|
57
|
+
else
|
|
58
|
+
@next_page = fetched_page
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the next page from the API.
|
|
64
|
+
def next_page
|
|
65
|
+
return nil if @page_number.nil?
|
|
66
|
+
|
|
67
|
+
if @next_page
|
|
68
|
+
this_page = @next_page
|
|
69
|
+
@next_page = nil
|
|
70
|
+
else
|
|
71
|
+
this_page = fetch_page(@page_number)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@has_next_page = this_page&.send(@has_next_field) if @has_next_field
|
|
75
|
+
|
|
76
|
+
items = this_page.send(@item_field)
|
|
77
|
+
if items.nil? || items.empty?
|
|
78
|
+
@page_number = nil
|
|
79
|
+
return nil
|
|
80
|
+
elsif @step
|
|
81
|
+
@page_number += items.length
|
|
82
|
+
else
|
|
83
|
+
@page_number += 1
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
this_page
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def fetch_page(page_number)
|
|
92
|
+
result = @get_next_page.call(page_number)
|
|
93
|
+
if result.is_a?(Array)
|
|
94
|
+
fetched_page, raw_response = result
|
|
95
|
+
@http_response = raw_response
|
|
96
|
+
fetched_page
|
|
97
|
+
else
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
module JSON
|
|
6
|
+
# @api private
|
|
7
|
+
class Request < Yasminaai::Internal::Http::BaseRequest
|
|
8
|
+
attr_reader :body
|
|
9
|
+
|
|
10
|
+
# @param base_url [String] The base URL for the request
|
|
11
|
+
# @param path [String] The path for the request
|
|
12
|
+
# @param method [Symbol] The HTTP method for the request (:get, :post, etc.)
|
|
13
|
+
# @param headers [Hash] Additional headers for the request (optional)
|
|
14
|
+
# @param query [Hash] Query parameters for the request (optional)
|
|
15
|
+
# @param body [Object, nil] The JSON request body (optional)
|
|
16
|
+
# @param request_options [Yasminaai::RequestOptions, Hash{Symbol=>Object}, nil]
|
|
17
|
+
def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {})
|
|
18
|
+
super(base_url:, path:, method:, headers:, query:, request_options:)
|
|
19
|
+
|
|
20
|
+
@body = body
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Hash] The encoded HTTP request headers.
|
|
24
|
+
# @param protected_keys [Array<String>] Header keys set by the SDK client (e.g. auth, metadata)
|
|
25
|
+
# that must not be overridden by additional_headers from request_options.
|
|
26
|
+
def encode_headers(protected_keys: [])
|
|
27
|
+
sdk_headers = {
|
|
28
|
+
"Content-Type" => "application/json",
|
|
29
|
+
"Accept" => "application/json"
|
|
30
|
+
}.merge(@headers)
|
|
31
|
+
merge_additional_headers(sdk_headers, protected_keys:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [String, nil] The encoded HTTP request body.
|
|
35
|
+
def encode_body
|
|
36
|
+
@body.nil? ? nil : ::JSON.generate(@body)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yasminaai
|
|
4
|
+
module Internal
|
|
5
|
+
module JSON
|
|
6
|
+
module Serializable
|
|
7
|
+
# Loads data from JSON into its deserialized form
|
|
8
|
+
#
|
|
9
|
+
# @param str [String] Raw JSON to load into an object
|
|
10
|
+
# @return [Object]
|
|
11
|
+
def load(str)
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Dumps data from its deserialized form into JSON
|
|
16
|
+
#
|
|
17
|
+
# @param value [Object] The deserialized value
|
|
18
|
+
# @return [String]
|
|
19
|
+
def dump(value)
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|