files.com 1.1.786 → 1.1.787
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 +4 -4
- data/README.md +2 -2
- data/_VERSION +1 -1
- data/lib/files.com/api.rb +20 -5
- data/lib/files.com/api_client.rb +196 -17
- data/lib/files.com/list.rb +4 -2
- data/lib/files.com/models/action_log.rb +4 -1
- data/lib/files.com/models/action_notification_export.rb +3 -1
- data/lib/files.com/models/action_notification_export_result.rb +4 -1
- data/lib/files.com/models/ai_assistant_personality.rb +20 -7
- data/lib/files.com/models/ai_task.rb +22 -10
- data/lib/files.com/models/api_key.rb +15 -7
- data/lib/files.com/models/api_request_log.rb +4 -1
- data/lib/files.com/models/app.rb +4 -1
- data/lib/files.com/models/as2_incoming_message.rb +4 -1
- data/lib/files.com/models/as2_outgoing_message.rb +4 -1
- data/lib/files.com/models/as2_partner.rb +18 -7
- data/lib/files.com/models/as2_station.rb +14 -7
- data/lib/files.com/models/automation.rb +47 -13
- data/lib/files.com/models/automation_log.rb +4 -1
- data/lib/files.com/models/automation_run.rb +16 -9
- data/lib/files.com/models/bandwidth_snapshot.rb +4 -1
- data/lib/files.com/models/behavior.rb +27 -9
- data/lib/files.com/models/bundle.rb +57 -10
- data/lib/files.com/models/bundle_action.rb +4 -1
- data/lib/files.com/models/bundle_download.rb +4 -1
- data/lib/files.com/models/bundle_notification.rb +20 -7
- data/lib/files.com/models/bundle_recipient.rb +6 -1
- data/lib/files.com/models/bundle_registration.rb +4 -1
- data/lib/files.com/models/chat_session.rb +5 -2
- data/lib/files.com/models/child_site_management_policy.rb +18 -7
- data/lib/files.com/models/clickwrap.rb +14 -7
- data/lib/files.com/models/custom_domain.rb +18 -8
- data/lib/files.com/models/desktop_configuration_profile.rb +21 -7
- data/lib/files.com/models/dns_record.rb +4 -1
- data/lib/files.com/models/email_incoming_message.rb +4 -1
- data/lib/files.com/models/email_log.rb +4 -1
- data/lib/files.com/models/event_channel.rb +20 -7
- data/lib/files.com/models/event_delivery_attempt.rb +5 -2
- data/lib/files.com/models/event_record.rb +5 -2
- data/lib/files.com/models/event_subscription.rb +25 -7
- data/lib/files.com/models/event_target.rb +22 -7
- data/lib/files.com/models/exavault_api_request_log.rb +4 -1
- data/lib/files.com/models/expectation.rb +23 -10
- data/lib/files.com/models/expectation_evaluation.rb +5 -2
- data/lib/files.com/models/expectation_incident.rb +20 -11
- data/lib/files.com/models/external_event.rb +6 -2
- data/lib/files.com/models/file.rb +110 -37
- data/lib/files.com/models/file_comment.rb +14 -7
- data/lib/files.com/models/file_comment_reaction.rb +6 -3
- data/lib/files.com/models/file_migration.rb +1 -1
- data/lib/files.com/models/file_migration_log.rb +4 -1
- data/lib/files.com/models/folder.rb +11 -3
- data/lib/files.com/models/form_field_set.rb +26 -7
- data/lib/files.com/models/ftp_action_log.rb +4 -1
- data/lib/files.com/models/gpg_key.rb +15 -7
- data/lib/files.com/models/group.rb +30 -7
- data/lib/files.com/models/group_user.rb +16 -6
- data/lib/files.com/models/history.rb +23 -8
- data/lib/files.com/models/history_export.rb +2 -1
- data/lib/files.com/models/history_export_result.rb +4 -1
- data/lib/files.com/models/holiday_calendar.rb +15 -7
- data/lib/files.com/models/holiday_region.rb +4 -1
- data/lib/files.com/models/inbound_s3_log.rb +4 -1
- data/lib/files.com/models/inbox_recipient.rb +6 -1
- data/lib/files.com/models/inbox_registration.rb +4 -1
- data/lib/files.com/models/inbox_upload.rb +4 -1
- data/lib/files.com/models/integration_centric_profile.rb +17 -7
- data/lib/files.com/models/invoice.rb +5 -2
- data/lib/files.com/models/ip_address.rb +16 -4
- data/lib/files.com/models/key_lifecycle_rule.rb +17 -7
- data/lib/files.com/models/lock.rb +16 -6
- data/lib/files.com/models/metadata_category.rb +19 -9
- data/lib/files.com/models/notification.rb +39 -7
- data/lib/files.com/models/outbound_connection_log.rb +4 -1
- data/lib/files.com/models/partner.rb +32 -7
- data/lib/files.com/models/partner_channel.rb +17 -7
- data/lib/files.com/models/partner_channel_template.rb +17 -7
- data/lib/files.com/models/partner_site.rb +5 -3
- data/lib/files.com/models/partner_site_request.rb +10 -4
- data/lib/files.com/models/payment.rb +5 -2
- data/lib/files.com/models/pending_work_event.rb +5 -2
- data/lib/files.com/models/permission.rb +12 -4
- data/lib/files.com/models/public_hosting_request_log.rb +4 -1
- data/lib/files.com/models/public_key.rb +15 -7
- data/lib/files.com/models/remote_bandwidth_snapshot.rb +4 -1
- data/lib/files.com/models/remote_mount_backend.rb +25 -10
- data/lib/files.com/models/remote_server.rb +46 -14
- data/lib/files.com/models/remote_server_credential.rb +14 -7
- data/lib/files.com/models/request.rb +17 -6
- data/lib/files.com/models/restore.rb +8 -1
- data/lib/files.com/models/schedule.rb +14 -7
- data/lib/files.com/models/scheduled_export.rb +18 -7
- data/lib/files.com/models/scim_log.rb +5 -2
- data/lib/files.com/models/secret.rb +15 -7
- data/lib/files.com/models/session.rb +1 -0
- data/lib/files.com/models/settings_change.rb +4 -1
- data/lib/files.com/models/sftp_action_log.rb +4 -1
- data/lib/files.com/models/sftp_host_key.rb +17 -7
- data/lib/files.com/models/share_group.rb +14 -7
- data/lib/files.com/models/siem_http_destination.rb +67 -7
- data/lib/files.com/models/siem_http_destination_event.rb +5 -2
- data/lib/files.com/models/site.rb +109 -0
- data/lib/files.com/models/site_subdomain_redirect.rb +10 -5
- data/lib/files.com/models/snapshot.rb +19 -10
- data/lib/files.com/models/sso_event.rb +5 -2
- data/lib/files.com/models/sso_strategy.rb +10 -5
- data/lib/files.com/models/style.rb +9 -6
- data/lib/files.com/models/sync.rb +36 -13
- data/lib/files.com/models/sync_log.rb +4 -1
- data/lib/files.com/models/sync_run.rb +5 -2
- data/lib/files.com/models/usage_daily_snapshot.rb +4 -1
- data/lib/files.com/models/usage_snapshot.rb +4 -1
- data/lib/files.com/models/user.rb +124 -16
- data/lib/files.com/models/user_additional_email_recipient.rb +14 -7
- data/lib/files.com/models/user_cipher_use.rb +4 -1
- data/lib/files.com/models/user_lifecycle_rule.rb +26 -7
- data/lib/files.com/models/user_request.rb +11 -5
- data/lib/files.com/models/user_security_event.rb +5 -2
- data/lib/files.com/models/user_sftp_client_use.rb +4 -1
- data/lib/files.com/models/web_dav_action_log.rb +4 -1
- data/lib/files.com/models/webhook_test.rb +3 -0
- data/lib/files.com/models/workspace.rb +14 -7
- data/lib/files.com/util.rb +32 -1
- data/lib/files.com.rb +54 -1
- data/spec/lib/api_client_spec.rb +483 -0
- data/spec/list_spec.rb +31 -2
- data/spec/models/persistence_spec.rb +1 -1
- data/spec/models/user_spec.rb +20 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3be6daf56fa9341e6485d6cb0bb2308fd12187b7192dec2d34e9027f215e8e50
|
|
4
|
+
data.tar.gz: ba1c8e66a7043f1beeded14ddbd3767168cb69cf5ea5a2ab1b46bbe961c7c9ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 840ca503641785a0c6c699c0de6397718de6f2eda8297ae25f14f23a16a066df3c585566b85ab89a81049891a891825c79ceb8c324ec0f2eedcfa0bfc7074f25
|
|
7
|
+
data.tar.gz: d6c74d466ff98e3b8eb31f7d7bbb4b38a603fb67b0ec6c2eae183d55901d6dfd40870dfa32bf42f7b346fecc8847d9919144a4ce22c4f2893b32d8d91ddee7cb
|
data/README.md
CHANGED
|
@@ -305,7 +305,7 @@ end
|
|
|
305
305
|
|
|
306
306
|
```ruby title="Range Filter Example"
|
|
307
307
|
begin
|
|
308
|
-
# users
|
|
308
|
+
# users whose last login was on or after 2024-01-01
|
|
309
309
|
Files::User.list(
|
|
310
310
|
filter_gteq: { "last_login_at": "2024-01-01" }
|
|
311
311
|
)
|
|
@@ -320,7 +320,7 @@ end
|
|
|
320
320
|
begin
|
|
321
321
|
# users whose usernames start with 'test'
|
|
322
322
|
Files::User.list(
|
|
323
|
-
|
|
323
|
+
filter_prefix: { username: "test" }
|
|
324
324
|
)
|
|
325
325
|
rescue Files::NotAuthenticatedError => e
|
|
326
326
|
puts "Authentication Error Occurred (#{e.class.to_s}): " + e.message
|
data/_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.787
|
data/lib/files.com/api.rb
CHANGED
|
@@ -3,14 +3,21 @@
|
|
|
3
3
|
module Files
|
|
4
4
|
class Api
|
|
5
5
|
# A given block receives the response and can reject it by raising. The
|
|
6
|
-
#
|
|
7
|
-
# the request, and
|
|
6
|
+
# returned options take on the resolved client and credentials, the
|
|
7
|
+
# regional origin a lockout region mismatch moved the request to, and the
|
|
8
|
+
# region_http_pairs the request used, only after the request, and that
|
|
9
|
+
# check, succeed; the caller's options are not changed. An options base_url
|
|
10
|
+
# sends the request there instead of to Files.base_url, and an options
|
|
11
|
+
# region_http_pairs is used instead of the client's.
|
|
8
12
|
def self.send_request(path, verb, params, options)
|
|
9
13
|
warn_on_options_in_params(params)
|
|
10
14
|
|
|
15
|
+
options = options.dup
|
|
11
16
|
headers = options.clone
|
|
12
17
|
api_key = headers.delete(:api_key)
|
|
18
|
+
base_url = headers.delete(:base_url)
|
|
13
19
|
client = headers.delete(:client) || ApiClient.active_client
|
|
20
|
+
region_http_pairs = headers.delete(:region_http_pairs)
|
|
14
21
|
session_id = headers.delete(:session_id)
|
|
15
22
|
workspace_id = headers.delete(:workspace_id)
|
|
16
23
|
if session = headers.delete(:session)
|
|
@@ -18,13 +25,21 @@ module Files
|
|
|
18
25
|
session_id = session.id
|
|
19
26
|
end
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
request_options = { api_key: api_key, headers: headers, params: params, session_id: session_id, workspace_id: workspace_id }
|
|
29
|
+
# Only an origin or pairs the request carries are passed on; without them the client uses its own as before.
|
|
30
|
+
request_options[:base_url] = base_url unless base_url.nil?
|
|
31
|
+
request_options[:region_http_pairs] = region_http_pairs unless region_http_pairs.nil?
|
|
32
|
+
regional_base_url = nil
|
|
33
|
+
resp, api_key, session_id = client.execute_request(verb, path, **request_options) do |origin, used_region_http_pairs|
|
|
34
|
+
regional_base_url = origin
|
|
35
|
+
region_http_pairs = used_region_http_pairs
|
|
36
|
+
end
|
|
24
37
|
yield resp if block_given?
|
|
25
38
|
options[:client] = client
|
|
26
39
|
options[:api_key] = api_key
|
|
27
40
|
options[:session_id] = session_id
|
|
41
|
+
options[:base_url] = regional_base_url if regional_base_url
|
|
42
|
+
options[:region_http_pairs] = region_http_pairs unless region_http_pairs.nil?
|
|
28
43
|
|
|
29
44
|
# Hash#select returns an array before 1.9
|
|
30
45
|
options_to_persist = {}
|
data/lib/files.com/api_client.rb
CHANGED
|
@@ -12,6 +12,11 @@ module Files
|
|
|
12
12
|
USE_GLOBAL = Object.new.freeze
|
|
13
13
|
private_constant :USE_GLOBAL
|
|
14
14
|
|
|
15
|
+
# Local and Files.com development domains. A lockout region mismatch moves a request to an https:// origin on one
|
|
16
|
+
# of these, or on a name under one, only with Files.region_handler's approval.
|
|
17
|
+
DEVELOPMENT_DOMAINS = %w[localhost local filesrails.test files.test].freeze
|
|
18
|
+
private_constant :DEVELOPMENT_DOMAINS
|
|
19
|
+
|
|
15
20
|
# Creates a client that sends requests through +conn+, or through a new default connection when +conn+ is nil.
|
|
16
21
|
#
|
|
17
22
|
# By default the client logs its ordinary API requests, responses and errors through the library-wide Files.logger and
|
|
@@ -32,11 +37,18 @@ module Files
|
|
|
32
37
|
# +workspace_id+ gives this client its own workspace; 0 is sent as "0". A request's own workspace_id option still
|
|
33
38
|
# takes precedence, and a request whose workspace_id is nil uses this client's. Pass workspace_id: nil to name no
|
|
34
39
|
# workspace for this client's requests even when Files.workspace_id is set.
|
|
35
|
-
|
|
40
|
+
#
|
|
41
|
+
# A lockout region mismatch moves its requests over plain HTTP only from the source to the destination of a pair in
|
|
42
|
+
# the library-wide Files.region_http_pairs, read at the start of each request, unless +region_http_pairs+ gives this
|
|
43
|
+
# client its own pairs, in the same form. The client keeps a frozen copy of them, and [] allows none. A request's own
|
|
44
|
+
# region_http_pairs option, such as the one a returned record keeps, takes precedence over both. Plain HTTP is for
|
|
45
|
+
# your own development servers only (see Files.region_http_pairs=).
|
|
46
|
+
def initialize(conn = nil, logger: USE_GLOBAL, log_level: USE_GLOBAL, workspace_id: USE_GLOBAL, region_http_pairs: USE_GLOBAL)
|
|
36
47
|
self.conn = conn || self.class.default_conn
|
|
37
48
|
@logger = logger
|
|
38
49
|
@log_level = log_level.equal?(USE_GLOBAL) ? log_level : Util.normalize_log_level(log_level)
|
|
39
50
|
@workspace_id = workspace_id
|
|
51
|
+
@region_http_pairs = region_http_pairs.equal?(USE_GLOBAL) ? region_http_pairs : Util.checked_region_http_pairs(region_http_pairs)
|
|
40
52
|
@system_profiler = SystemProfiler.new
|
|
41
53
|
@last_request_metrics = nil
|
|
42
54
|
end
|
|
@@ -96,8 +108,9 @@ module Files
|
|
|
96
108
|
return false if num_retries >= Files.max_network_retries
|
|
97
109
|
return true if error.is_a?(Faraday::TimeoutError)
|
|
98
110
|
return true if error.is_a?(Faraday::ConnectionFailed)
|
|
99
|
-
|
|
100
|
-
return true if error.is_a?(Faraday::
|
|
111
|
+
# 501 Not Implemented means the API does not support the request, so repeating it cannot succeed.
|
|
112
|
+
return true if error.is_a?(Faraday::ServerError) and error.response_status != 501
|
|
113
|
+
return true if error.is_a?(Faraday::ClientError) and [ 405, 429 ].include?(error.response_status)
|
|
101
114
|
|
|
102
115
|
false
|
|
103
116
|
end
|
|
@@ -124,7 +137,17 @@ module Files
|
|
|
124
137
|
end
|
|
125
138
|
end
|
|
126
139
|
|
|
127
|
-
|
|
140
|
+
# The frozen [source, destination] pairs of http:// origins this client's requests use when they name none of their
|
|
141
|
+
# own: the client's own, or Files.region_http_pairs.
|
|
142
|
+
def region_http_pairs
|
|
143
|
+
@region_http_pairs.equal?(USE_GLOBAL) ? Files.region_http_pairs : @region_http_pairs
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns the response and the API key and session ID the request used. +region_http_pairs+, such as the pairs a
|
|
147
|
+
# returned record keeps, is used instead of this client's. The block, if given, receives the request's context once
|
|
148
|
+
# its response has arrived: the regional origin a lockout region mismatch moved it to, or nil, and the frozen
|
|
149
|
+
# region_http_pairs it used.
|
|
150
|
+
def execute_request(method, path, base_url: nil, api_key: nil, session_id: nil, workspace_id: nil, region_http_pairs: nil, headers: {}, params: {}, &on_request_context)
|
|
128
151
|
base_url ||= Files.base_url
|
|
129
152
|
if api_key.nil?
|
|
130
153
|
session_id ||= Files.session_id
|
|
@@ -155,12 +178,22 @@ module Files
|
|
|
155
178
|
context = RequestLogContext.new
|
|
156
179
|
context.api_key = api_key
|
|
157
180
|
context.body = body
|
|
181
|
+
context.headers = headers
|
|
158
182
|
context.method = method
|
|
159
183
|
context.path = path
|
|
160
184
|
context.query_params = query_params if query_params
|
|
161
185
|
context.session_id = session_id
|
|
162
186
|
|
|
163
|
-
|
|
187
|
+
regional_base_url = nil
|
|
188
|
+
# Fixed before the request starts, so later changes to the caller's or the library-wide pairs cannot affect it.
|
|
189
|
+
http_pairs = region_http_pairs.nil? ? self.region_http_pairs : Util.checked_region_http_pairs(region_http_pairs)
|
|
190
|
+
relocate = lambda do |error_response|
|
|
191
|
+
regional_base_url = region_continuation_base_url(error_response, base_url, body, http_pairs)
|
|
192
|
+
url = api_url(path, regional_base_url) if regional_base_url
|
|
193
|
+
regional_base_url
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
http_resp = execute_request_with_rescues(base_url, context, relocate: relocate) do
|
|
164
197
|
conn.run_request(method, url, body, headers) do |req|
|
|
165
198
|
req.options.open_timeout = Files.open_timeout
|
|
166
199
|
req.options.timeout = Files.read_timeout
|
|
@@ -175,6 +208,7 @@ module Files
|
|
|
175
208
|
end
|
|
176
209
|
|
|
177
210
|
@last_response = resp
|
|
211
|
+
on_request_context&.call(regional_base_url, http_pairs)
|
|
178
212
|
[ resp, api_key, session_id ]
|
|
179
213
|
end
|
|
180
214
|
|
|
@@ -287,15 +321,143 @@ module Files
|
|
|
287
321
|
end
|
|
288
322
|
|
|
289
323
|
private def api_url(url = "", base_url = nil)
|
|
324
|
+
begin
|
|
325
|
+
root = Addressable::URI.parse(base_url)
|
|
326
|
+
rescue Addressable::URI::InvalidURIError
|
|
327
|
+
# The parser's message can include the whole URL and its credentials, so only the cause carries it.
|
|
328
|
+
raise InvalidParameterError, "Invalid Files.com base URL. Use an http:// or https:// URL with a valid host and port, such as https://app.files.com."
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Other schemes fail inside Net::HTTP with a NoMethodError rather than a Files::Error.
|
|
332
|
+
raise InvalidParameterError, "Unsupported Files.com base URL scheme \"#{root.scheme}\". Use an http:// or https:// base URL, such as https://app.files.com." unless root.scheme.nil? || %w[http https].include?(root.scheme.downcase)
|
|
333
|
+
|
|
290
334
|
uri = Addressable::URI.new
|
|
291
|
-
uri.host =
|
|
292
|
-
uri.port =
|
|
335
|
+
uri.host = root.host
|
|
336
|
+
uri.port = root.port
|
|
293
337
|
uri.path = "/api/rest/v1#{Files::URI.normalized_path(url)}"
|
|
294
|
-
uri.scheme =
|
|
338
|
+
uri.scheme = root.scheme
|
|
295
339
|
|
|
296
340
|
uri.to_s
|
|
297
341
|
end
|
|
298
342
|
|
|
343
|
+
# The origin a lockout region mismatch moves this request to, or nil to raise the error as it is. The API answers
|
|
344
|
+
# with that error, naming the site's own origin in data.host, when the site is pinned to another region or to a
|
|
345
|
+
# custom domain. Only the configured HTTPS origin, reached over this client's verified connection, may send the
|
|
346
|
+
# credentials elsewhere, apart from an http:// origin to the destination +http_pairs+ pairs it with.
|
|
347
|
+
private def region_continuation_base_url(error_response, base_url, body, http_pairs)
|
|
348
|
+
return if Files.region_policy == :disabled
|
|
349
|
+
|
|
350
|
+
error_object = error_response.data
|
|
351
|
+
return unless error_response.http_status == 403 && error_object[:type] == "not-authenticated/lockout-region-mismatch"
|
|
352
|
+
return unless replayable?(body)
|
|
353
|
+
|
|
354
|
+
host = error_object[:data].is_a?(Hash) ? error_object[:data][:host] : nil
|
|
355
|
+
return http_pair_origin(base_url, host, http_pairs) unless verified_https_connection?(base_url)
|
|
356
|
+
|
|
357
|
+
configured = Addressable::URI.parse(base_url)
|
|
358
|
+
original_origin = https_origin(configured.normalized_host, configured.inferred_port)
|
|
359
|
+
target_origin, approval_required = region_target(host)
|
|
360
|
+
# A mismatch that names the origin it came from cannot be resolved by moving, so the handler is not asked.
|
|
361
|
+
return if target_origin.nil? || target_origin == original_origin
|
|
362
|
+
|
|
363
|
+
if Files.region_handler
|
|
364
|
+
target_origin = approved_region_origin(original_origin, target_origin, approval_required) { |decision| region_target(decision)&.first }
|
|
365
|
+
elsif approval_required
|
|
366
|
+
return
|
|
367
|
+
end
|
|
368
|
+
return if target_origin.nil? || target_origin == original_origin
|
|
369
|
+
|
|
370
|
+
Util.log_info("Region continuation", { from: original_origin, to: target_origin }, log_settings)
|
|
371
|
+
target_origin
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# The origin Files.region_handler approves: the target for true, or another origin it returns that the block gives
|
|
375
|
+
# back unchanged, as the canonical form of an origin the request may move to. nil when it declines. The handler
|
|
376
|
+
# gets frozen copies, so it cannot change the origins used here.
|
|
377
|
+
private def approved_region_origin(original_origin, target_origin, approval_required)
|
|
378
|
+
event = { reason: :lockout_region_mismatch, original_origin: original_origin.dup.freeze, target_origin: target_origin.dup.freeze, approval_required: approval_required }.freeze
|
|
379
|
+
decision = Files.region_handler.call(event)
|
|
380
|
+
return target_origin if decision == true
|
|
381
|
+
return unless decision.is_a?(String)
|
|
382
|
+
|
|
383
|
+
approved_origin = yield(decision)
|
|
384
|
+
approved_origin if approved_origin == decision
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# The http:// origin data.host names when +http_pairs+ pairs it with +base_url+'s own http:// origin, or nil. Plain
|
|
388
|
+
# HTTP proves nothing about who answered, so only that exact pair, the caller's own choice, gives the authority to
|
|
389
|
+
# continue. Within it a bare host means that host over plain HTTP, on port 80 unless it names another port. The
|
|
390
|
+
# region handler may still decline, or choose another destination paired with the same source; the request then
|
|
391
|
+
# continues at that pair's own frozen destination, not at the String the handler returned.
|
|
392
|
+
private def http_pair_origin(base_url, host, http_pairs)
|
|
393
|
+
original_origin = Util.http_origin(base_url)
|
|
394
|
+
target_origin = Util.http_origin(host.is_a?(String) && !host.include?("://") ? "http://#{host}" : host)
|
|
395
|
+
return unless original_origin && target_origin != original_origin && http_pairs.include?([ original_origin, target_origin ])
|
|
396
|
+
|
|
397
|
+
if Files.region_handler
|
|
398
|
+
target_origin = approved_region_origin(original_origin, target_origin, false) do |decision|
|
|
399
|
+
http_pairs.find { |source, destination| source == original_origin && destination == decision }&.last
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
return if target_origin.nil? || target_origin == original_origin
|
|
403
|
+
|
|
404
|
+
Util.log_info("Region continuation", { from: original_origin, to: target_origin }, log_settings)
|
|
405
|
+
target_origin
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Whether a response through this client came from +base_url+ over verified HTTPS: the SDK's own middleware and a
|
|
409
|
+
# Net::HTTP adapter, with certificate and host name verification on. Other middleware or adapters, such as a
|
|
410
|
+
# caller's, could produce a response that never came from there.
|
|
411
|
+
private def verified_https_connection?(base_url)
|
|
412
|
+
return false unless Addressable::URI.parse(base_url).scheme.to_s.casecmp?("https") && conn.is_a?(Faraday::Connection)
|
|
413
|
+
|
|
414
|
+
ssl = conn.ssl
|
|
415
|
+
ssl.verify? && ssl.verify_hostname? && [ nil, OpenSSL::SSL::VERIFY_PEER ].include?(ssl.verify_mode) &&
|
|
416
|
+
conn.builder.handlers == self.class.build_default_conn.builder.handlers &&
|
|
417
|
+
[ Faraday::Adapter::NetHttp, Faraday::Adapter::NetHttpPersistent ].include?(conn.builder.adapter&.klass)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Whether +body+ can be sent again. A file in a multipart body has already been read.
|
|
421
|
+
private def replayable?(body)
|
|
422
|
+
case body
|
|
423
|
+
when Hash then body.values.all? { |value| replayable?(value) }
|
|
424
|
+
when Array then body.all? { |value| replayable?(value) }
|
|
425
|
+
else !body.respond_to?(:read)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# The HTTPS origin data.host names and whether continuing there needs the region handler's approval, or nil when
|
|
430
|
+
# data.host is not a plain origin. It may be a host name, with or without a port, or an https:// origin whose path
|
|
431
|
+
# is empty or "/"; credentials, a query, a fragment, a path, another scheme, spaces or backslashes are refused.
|
|
432
|
+
# Without approval the request continues only to a DNS name on port 443: an IP address, a local or Files.com
|
|
433
|
+
# development name (DEVELOPMENT_DOMAINS, or a name without a dot) or another port needs the handler to allow it.
|
|
434
|
+
private def region_target(host)
|
|
435
|
+
return unless host.is_a?(String) && !host.match?(/[[:space:][:cntrl:]\\]/)
|
|
436
|
+
|
|
437
|
+
origin = host.include?("://") ? host : "https://#{host}"
|
|
438
|
+
return unless origin.match?(/\Ahttps:\/\/[^\/?#@]+\/?\z/i)
|
|
439
|
+
|
|
440
|
+
uri = Addressable::URI.parse(origin)
|
|
441
|
+
name = uri.normalized_host.to_s
|
|
442
|
+
port = uri.port || 443
|
|
443
|
+
ip_address = name.match?(/\A\[[0-9a-f:.]+\]\z/) || name.match?(/\A\d{1,3}(\.\d{1,3}){3}\z/)
|
|
444
|
+
return unless port.between?(1, 65_535) && (ip_address || dns_name?(name))
|
|
445
|
+
|
|
446
|
+
local = ip_address || !name.include?(".") || DEVELOPMENT_DOMAINS.any? { |domain| name == domain || name.end_with?(".#{domain}") }
|
|
447
|
+
[ https_origin(name, port), local || port != 443 ]
|
|
448
|
+
rescue Addressable::URI::InvalidURIError, ArgumentError
|
|
449
|
+
nil
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
private def dns_name?(name)
|
|
453
|
+
labels = name.split(".", -1)
|
|
454
|
+
name.length <= 253 && labels.all? { |label| label.match?(/\A[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\z/) } && !labels.last.match?(/\A\d+\z/)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
private def https_origin(host, port)
|
|
458
|
+
port == 443 ? "https://#{host}" : "https://#{host}:#{port}"
|
|
459
|
+
end
|
|
460
|
+
|
|
299
461
|
private def check_api_key!(api_key)
|
|
300
462
|
raise AuthenticationError, "No Files.com API key provided. Set your API key using \"Files.api_key = <API-KEY>\". You can generate API keys from the Files.com's web interface. " unless api_key
|
|
301
463
|
raise AuthenticationError, "Your API key must be a string" unless api_key.is_a?(String)
|
|
@@ -311,20 +473,23 @@ module Files
|
|
|
311
473
|
raise AuthenticationError, "The provided Session ID is invalid (it contains whitespace)"
|
|
312
474
|
end
|
|
313
475
|
|
|
314
|
-
|
|
476
|
+
# +relocate+, given only for ordinary API requests, receives a structured error response and returns the regional
|
|
477
|
+
# origin to send the request to instead, or nil. The request is sent there at most once, and retries before and
|
|
478
|
+
# after count together.
|
|
479
|
+
def execute_request_with_rescues(base_url, context, skip_body_logging: false, is_transfer: false, relocate: nil)
|
|
315
480
|
num_retries = 0
|
|
316
481
|
begin
|
|
317
482
|
request_start = Time.now
|
|
318
483
|
log_request(context, num_retries, no_body: skip_body_logging)
|
|
319
484
|
resp = yield
|
|
320
|
-
log_response(context, request_start, resp.status, resp.body, no_body: skip_body_logging)
|
|
485
|
+
log_response(context, request_start, resp.status, resp.body, headers: resp.headers, no_body: skip_body_logging)
|
|
321
486
|
rescue StandardError => e
|
|
322
487
|
error_context = context
|
|
323
488
|
|
|
324
489
|
if e.respond_to?(:response) && e.response
|
|
325
490
|
error_context = context
|
|
326
491
|
log_response(error_context, request_start,
|
|
327
|
-
e.response[:status], e.response[:body], no_body: skip_body_logging
|
|
492
|
+
e.response[:status], e.response[:body], headers: e.response[:headers], no_body: skip_body_logging
|
|
328
493
|
)
|
|
329
494
|
else
|
|
330
495
|
log_response_error(error_context, request_start, e)
|
|
@@ -332,7 +497,9 @@ module Files
|
|
|
332
497
|
|
|
333
498
|
if self.class.should_retry?(e, num_retries)
|
|
334
499
|
num_retries += 1
|
|
335
|
-
|
|
500
|
+
wait_seconds = self.class.sleep_time(num_retries)
|
|
501
|
+
log_retry(context, num_retries, e, wait_seconds) unless is_transfer
|
|
502
|
+
sleep wait_seconds
|
|
336
503
|
retry
|
|
337
504
|
end
|
|
338
505
|
|
|
@@ -340,7 +507,14 @@ module Files
|
|
|
340
507
|
|
|
341
508
|
case e
|
|
342
509
|
when Faraday::ClientError, Faraday::ServerError
|
|
343
|
-
|
|
510
|
+
error_response = api_error_response(e.response)
|
|
511
|
+
if relocate && error_response && (regional_base_url = relocate.call(error_response))
|
|
512
|
+
relocate = nil
|
|
513
|
+
base_url = regional_base_url
|
|
514
|
+
retry
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
if error_response
|
|
344
518
|
handle_error_response(error_response, error_context, is_transfer: is_transfer)
|
|
345
519
|
else
|
|
346
520
|
handle_network_error(e, error_context, num_retries, base_url, is_transfer: is_transfer)
|
|
@@ -348,6 +522,7 @@ module Files
|
|
|
348
522
|
else
|
|
349
523
|
raise e.exception("Transfer request failed (#{e.class})"), cause: nil if is_transfer
|
|
350
524
|
|
|
525
|
+
handle_network_error(e, error_context, num_retries, base_url) if e.is_a?(Faraday::ConnectionFailed) || e.is_a?(Faraday::SSLError)
|
|
351
526
|
raise
|
|
352
527
|
end
|
|
353
528
|
end
|
|
@@ -536,12 +711,12 @@ module Files
|
|
|
536
711
|
|
|
537
712
|
private def log_request(context, num_retries, no_body: false)
|
|
538
713
|
Util.log_info("Request", { method: context.method, num_retries: num_retries, path: context.path }, log_settings)
|
|
539
|
-
Util.log_debug("Request details", { body: context.body, query_params: context.query_params }, log_settings) unless no_body
|
|
714
|
+
Util.log_debug("Request details", { body: context.body, headers: context.headers, query_params: context.query_params }, log_settings) unless no_body
|
|
540
715
|
end
|
|
541
716
|
|
|
542
|
-
private def log_response(context, request_start, status, body, no_body: false)
|
|
717
|
+
private def log_response(context, request_start, status, body, headers: nil, no_body: false)
|
|
543
718
|
Util.log_info("Response", { elapsed: Time.now - request_start, method: context.method, path: context.path, status: status }, log_settings)
|
|
544
|
-
Util.log_debug("Response details", { body: body }, log_settings) unless no_body
|
|
719
|
+
Util.log_debug("Response details", { body: body, headers: headers }, log_settings) unless no_body
|
|
545
720
|
end
|
|
546
721
|
|
|
547
722
|
private def log_response_error(context, request_start, error)
|
|
@@ -549,6 +724,10 @@ module Files
|
|
|
549
724
|
Util.log_debug("Error details", { error_message: error.message }, log_settings)
|
|
550
725
|
end
|
|
551
726
|
|
|
727
|
+
private def log_retry(context, attempt, error, wait_seconds)
|
|
728
|
+
Util.log_info("Retry", { attempt: attempt, error_type: error.class, max_retries: Files.max_network_retries, method: context.method, path: context.path, status: error.response_status, wait_seconds: wait_seconds }, log_settings)
|
|
729
|
+
end
|
|
730
|
+
|
|
552
731
|
# This client's own logger and level where it set them, and the library-wide ones otherwise.
|
|
553
732
|
private def log_settings
|
|
554
733
|
Util::LogSettings.new(@logger.equal?(USE_GLOBAL) ? Files.logger : @logger,
|
|
@@ -562,7 +741,7 @@ module Files
|
|
|
562
741
|
end
|
|
563
742
|
|
|
564
743
|
class RequestLogContext
|
|
565
|
-
attr_accessor :body, :api_key, :method, :path, :query_params, :session_id
|
|
744
|
+
attr_accessor :body, :api_key, :headers, :method, :path, :query_params, :session_id
|
|
566
745
|
end
|
|
567
746
|
end
|
|
568
747
|
end
|
data/lib/files.com/list.rb
CHANGED
|
@@ -118,8 +118,10 @@ module Files
|
|
|
118
118
|
def request_page(cursor)
|
|
119
119
|
response, options = @request.call(cursor ? @params.merge(cursor: cursor) : @params) { |reply| reply.require_list(@resource_wrapper) }
|
|
120
120
|
records = response.data.map { |attributes| @resource_wrapper.new(attributes, options) }
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
# The API documents X-Files-Cursor-Next for the next page's cursor; X-Files-Cursor is the older header. A blank
|
|
122
|
+
# cursor means there is no next page.
|
|
123
|
+
next_cursor = %w[x-files-cursor-next x-files-cursor].map { |name| response.http_headers[name] }.find { |value| !value.to_s.strip.empty? }
|
|
124
|
+
Page.new(records, cursor, next_cursor)
|
|
123
125
|
end
|
|
124
126
|
|
|
125
127
|
def with_page(page)
|
|
@@ -105,8 +105,11 @@ module Files
|
|
|
105
105
|
raise InvalidParameterError.new("Bad parameter: filter_lt must be an Hash") if !params[:filter_lt].nil? and !params[:filter_lt].is_a?(Hash)
|
|
106
106
|
raise InvalidParameterError.new("Bad parameter: filter_lteq must be an Hash") if !params[:filter_lteq].nil? and !params[:filter_lteq].is_a?(Hash)
|
|
107
107
|
|
|
108
|
+
list_options = options.dup
|
|
108
109
|
List.new(ActionLog, params) do |page_params, &check_page|
|
|
109
|
-
Api.send_request("/action_logs", :get, page_params,
|
|
110
|
+
response, page_options = Api.send_request("/action_logs", :get, page_params, list_options, &check_page)
|
|
111
|
+
list_options = list_options.merge(page_options)
|
|
112
|
+
[ response, page_options ]
|
|
110
113
|
end
|
|
111
114
|
end
|
|
112
115
|
|
|
@@ -152,6 +152,7 @@ module Files
|
|
|
152
152
|
else
|
|
153
153
|
new_obj = ActionNotificationExport.create(@attributes, @options)
|
|
154
154
|
@attributes = new_obj.attributes
|
|
155
|
+
@options = @options.merge(new_obj.options)
|
|
155
156
|
end
|
|
156
157
|
|
|
157
158
|
true
|
|
@@ -165,7 +166,7 @@ module Files
|
|
|
165
166
|
#
|
|
166
167
|
# Returns a Files::ActionNotificationExport.
|
|
167
168
|
def self.find(id, params = {}, options = {})
|
|
168
|
-
params
|
|
169
|
+
params = (params || {}).dup
|
|
169
170
|
params[:id] = id
|
|
170
171
|
raise InvalidParameterError.new("Bad parameter: id must be an Integer") if !params[:id].nil? and !params[:id].is_a?(Integer)
|
|
171
172
|
raise MissingParameterError.new("Parameter missing: id") if params[:id].nil?
|
|
@@ -204,6 +205,7 @@ module Files
|
|
|
204
205
|
raise InvalidParameterError.new("Bad parameter: query_request_method must be an String") if !params[:query_request_method].nil? and !params[:query_request_method].is_a?(String)
|
|
205
206
|
raise InvalidParameterError.new("Bad parameter: query_request_url must be an String") if !params[:query_request_url].nil? and !params[:query_request_url].is_a?(String)
|
|
206
207
|
raise InvalidParameterError.new("Bad parameter: query_status must be an String") if !params[:query_status].nil? and !params[:query_status].is_a?(String)
|
|
208
|
+
raise InvalidParameterError.new("Bad parameter: query_success must be a single value, not #{params[:query_success].class}") if [ Array, Hash ].any? { |klass| params[:query_success].is_a?(klass) }
|
|
207
209
|
raise InvalidParameterError.new("Bad parameter: query_path must be an String") if !params[:query_path].nil? and !params[:query_path].is_a?(String)
|
|
208
210
|
raise InvalidParameterError.new("Bad parameter: query_folder must be an String") if !params[:query_folder].nil? and !params[:query_folder].is_a?(String)
|
|
209
211
|
|
|
@@ -78,8 +78,11 @@ module Files
|
|
|
78
78
|
raise InvalidParameterError.new("Bad parameter: action_notification_export_id must be an Integer") if !params[:action_notification_export_id].nil? and !params[:action_notification_export_id].is_a?(Integer)
|
|
79
79
|
raise MissingParameterError.new("Parameter missing: action_notification_export_id") if params[:action_notification_export_id].nil?
|
|
80
80
|
|
|
81
|
+
list_options = options.dup
|
|
81
82
|
List.new(ActionNotificationExportResult, params) do |page_params, &check_page|
|
|
82
|
-
Api.send_request("/action_notification_export_results", :get, page_params,
|
|
83
|
+
response, page_options = Api.send_request("/action_notification_export_results", :get, page_params, list_options, &check_page)
|
|
84
|
+
list_options = list_options.merge(page_options)
|
|
85
|
+
[ response, page_options ]
|
|
83
86
|
end
|
|
84
87
|
end
|
|
85
88
|
|
|
@@ -82,29 +82,34 @@ module Files
|
|
|
82
82
|
# use_by_default - boolean - Whether this personality is the default personality for the Workspace.
|
|
83
83
|
# workspace_id - int64 - Workspace ID. `0` means the default workspace.
|
|
84
84
|
def update(params = {})
|
|
85
|
-
params
|
|
85
|
+
params = (params || {}).dup
|
|
86
86
|
params[:id] = @attributes[:id]
|
|
87
87
|
raise MissingParameterError.new("Current object doesn't have a id") unless @attributes[:id]
|
|
88
88
|
raise InvalidParameterError.new("Bad parameter: id must be an Integer") if !params[:id].nil? and !params[:id].is_a?(Integer)
|
|
89
|
+
raise InvalidParameterError.new("Bad parameter: apply_to_all_workspaces must be a single value, not #{params[:apply_to_all_workspaces].class}") if [ Array, Hash ].any? { |klass| params[:apply_to_all_workspaces].is_a?(klass) }
|
|
89
90
|
raise InvalidParameterError.new("Bad parameter: name must be an String") if !params[:name].nil? and !params[:name].is_a?(String)
|
|
90
91
|
raise InvalidParameterError.new("Bad parameter: system_prompt must be an String") if !params[:system_prompt].nil? and !params[:system_prompt].is_a?(String)
|
|
92
|
+
raise InvalidParameterError.new("Bad parameter: use_by_default must be a single value, not #{params[:use_by_default].class}") if [ Array, Hash ].any? { |klass| params[:use_by_default].is_a?(klass) }
|
|
91
93
|
raise InvalidParameterError.new("Bad parameter: workspace_id must be an Integer") if !params[:workspace_id].nil? and !params[:workspace_id].is_a?(Integer)
|
|
92
94
|
raise MissingParameterError.new("Parameter missing: id") if params[:id].nil?
|
|
93
95
|
|
|
94
96
|
response, options = Api.send_request("/ai_assistant_personalities/#{@attributes[:id]}", :patch, params, @options) { |reply| reply.require_object("AiAssistantPersonality") }
|
|
95
97
|
@attributes = response.data
|
|
98
|
+
@options = @options.merge(options)
|
|
96
99
|
[ response, options ]
|
|
97
100
|
end
|
|
98
101
|
|
|
99
102
|
# Returns the [response, options] pair from Files::Api.send_request.
|
|
100
103
|
def delete(params = {})
|
|
101
|
-
params
|
|
104
|
+
params = (params || {}).dup
|
|
102
105
|
params[:id] = @attributes[:id]
|
|
103
106
|
raise MissingParameterError.new("Current object doesn't have a id") unless @attributes[:id]
|
|
104
107
|
raise InvalidParameterError.new("Bad parameter: id must be an Integer") if !params[:id].nil? and !params[:id].is_a?(Integer)
|
|
105
108
|
raise MissingParameterError.new("Parameter missing: id") if params[:id].nil?
|
|
106
109
|
|
|
107
|
-
Api.send_request("/ai_assistant_personalities/#{@attributes[:id]}", :delete, params, @options)
|
|
110
|
+
response, options = Api.send_request("/ai_assistant_personalities/#{@attributes[:id]}", :delete, params, @options)
|
|
111
|
+
@options = @options.merge(options)
|
|
112
|
+
[ response, options ]
|
|
108
113
|
end
|
|
109
114
|
|
|
110
115
|
# Alias for #delete. Returns nil.
|
|
@@ -119,6 +124,7 @@ module Files
|
|
|
119
124
|
else
|
|
120
125
|
new_obj = AiAssistantPersonality.create(@attributes, @options)
|
|
121
126
|
@attributes = new_obj.attributes
|
|
127
|
+
@options = @options.merge(new_obj.options)
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
true
|
|
@@ -140,8 +146,11 @@ module Files
|
|
|
140
146
|
raise InvalidParameterError.new("Bad parameter: sort_by must be an Hash") if !params[:sort_by].nil? and !params[:sort_by].is_a?(Hash)
|
|
141
147
|
raise InvalidParameterError.new("Bad parameter: filter must be an Hash") if !params[:filter].nil? and !params[:filter].is_a?(Hash)
|
|
142
148
|
|
|
149
|
+
list_options = options.dup
|
|
143
150
|
List.new(AiAssistantPersonality, params) do |page_params, &check_page|
|
|
144
|
-
Api.send_request("/ai_assistant_personalities", :get, page_params,
|
|
151
|
+
response, page_options = Api.send_request("/ai_assistant_personalities", :get, page_params, list_options, &check_page)
|
|
152
|
+
list_options = list_options.merge(page_options)
|
|
153
|
+
[ response, page_options ]
|
|
145
154
|
end
|
|
146
155
|
end
|
|
147
156
|
|
|
@@ -157,7 +166,7 @@ module Files
|
|
|
157
166
|
#
|
|
158
167
|
# Returns a Files::AiAssistantPersonality.
|
|
159
168
|
def self.find(id, params = {}, options = {})
|
|
160
|
-
params
|
|
169
|
+
params = (params || {}).dup
|
|
161
170
|
params[:id] = id
|
|
162
171
|
raise InvalidParameterError.new("Bad parameter: id must be an Integer") if !params[:id].nil? and !params[:id].is_a?(Integer)
|
|
163
172
|
raise MissingParameterError.new("Parameter missing: id") if params[:id].nil?
|
|
@@ -182,8 +191,10 @@ module Files
|
|
|
182
191
|
#
|
|
183
192
|
# Returns a Files::AiAssistantPersonality.
|
|
184
193
|
def self.create(params = {}, options = {})
|
|
194
|
+
raise InvalidParameterError.new("Bad parameter: apply_to_all_workspaces must be a single value, not #{params[:apply_to_all_workspaces].class}") if [ Array, Hash ].any? { |klass| params[:apply_to_all_workspaces].is_a?(klass) }
|
|
185
195
|
raise InvalidParameterError.new("Bad parameter: name must be an String") if !params[:name].nil? and !params[:name].is_a?(String)
|
|
186
196
|
raise InvalidParameterError.new("Bad parameter: system_prompt must be an String") if !params[:system_prompt].nil? and !params[:system_prompt].is_a?(String)
|
|
197
|
+
raise InvalidParameterError.new("Bad parameter: use_by_default must be a single value, not #{params[:use_by_default].class}") if [ Array, Hash ].any? { |klass| params[:use_by_default].is_a?(klass) }
|
|
187
198
|
raise InvalidParameterError.new("Bad parameter: workspace_id must be an Integer") if !params[:workspace_id].nil? and !params[:workspace_id].is_a?(Integer)
|
|
188
199
|
raise MissingParameterError.new("Parameter missing: name") if params[:name].nil?
|
|
189
200
|
raise MissingParameterError.new("Parameter missing: system_prompt") if params[:system_prompt].nil?
|
|
@@ -204,11 +215,13 @@ module Files
|
|
|
204
215
|
#
|
|
205
216
|
# Returns a Files::AiAssistantPersonality.
|
|
206
217
|
def self.update(id, params = {}, options = {})
|
|
207
|
-
params
|
|
218
|
+
params = (params || {}).dup
|
|
208
219
|
params[:id] = id
|
|
209
220
|
raise InvalidParameterError.new("Bad parameter: id must be an Integer") if !params[:id].nil? and !params[:id].is_a?(Integer)
|
|
221
|
+
raise InvalidParameterError.new("Bad parameter: apply_to_all_workspaces must be a single value, not #{params[:apply_to_all_workspaces].class}") if [ Array, Hash ].any? { |klass| params[:apply_to_all_workspaces].is_a?(klass) }
|
|
210
222
|
raise InvalidParameterError.new("Bad parameter: name must be an String") if !params[:name].nil? and !params[:name].is_a?(String)
|
|
211
223
|
raise InvalidParameterError.new("Bad parameter: system_prompt must be an String") if !params[:system_prompt].nil? and !params[:system_prompt].is_a?(String)
|
|
224
|
+
raise InvalidParameterError.new("Bad parameter: use_by_default must be a single value, not #{params[:use_by_default].class}") if [ Array, Hash ].any? { |klass| params[:use_by_default].is_a?(klass) }
|
|
212
225
|
raise InvalidParameterError.new("Bad parameter: workspace_id must be an Integer") if !params[:workspace_id].nil? and !params[:workspace_id].is_a?(Integer)
|
|
213
226
|
raise MissingParameterError.new("Parameter missing: id") if params[:id].nil?
|
|
214
227
|
|
|
@@ -221,7 +234,7 @@ module Files
|
|
|
221
234
|
#
|
|
222
235
|
# Returns nil.
|
|
223
236
|
def self.delete(id, params = {}, options = {})
|
|
224
|
-
params
|
|
237
|
+
params = (params || {}).dup
|
|
225
238
|
params[:id] = id
|
|
226
239
|
raise InvalidParameterError.new("Bad parameter: id must be an Integer") if !params[:id].nil? and !params[:id].is_a?(Integer)
|
|
227
240
|
raise MissingParameterError.new("Parameter missing: id") if params[:id].nil?
|