reactor_sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +281 -0
- data/lib/reactor_sdk/authentication.rb +137 -0
- data/lib/reactor_sdk/client.rb +186 -0
- data/lib/reactor_sdk/configuration.rb +102 -0
- data/lib/reactor_sdk/connection.rb +342 -0
- data/lib/reactor_sdk/endpoints/app_configurations.rb +42 -0
- data/lib/reactor_sdk/endpoints/audit_events.rb +64 -0
- data/lib/reactor_sdk/endpoints/base_endpoint.rb +207 -0
- data/lib/reactor_sdk/endpoints/builds.rb +62 -0
- data/lib/reactor_sdk/endpoints/callbacks.rb +38 -0
- data/lib/reactor_sdk/endpoints/companies.rb +42 -0
- data/lib/reactor_sdk/endpoints/data_elements.rb +251 -0
- data/lib/reactor_sdk/endpoints/environments.rb +174 -0
- data/lib/reactor_sdk/endpoints/extension_package_usage_authorizations.rb +51 -0
- data/lib/reactor_sdk/endpoints/extension_packages.rb +63 -0
- data/lib/reactor_sdk/endpoints/extensions.rb +181 -0
- data/lib/reactor_sdk/endpoints/hosts.rb +101 -0
- data/lib/reactor_sdk/endpoints/libraries.rb +872 -0
- data/lib/reactor_sdk/endpoints/notes.rb +11 -0
- data/lib/reactor_sdk/endpoints/profiles.rb +14 -0
- data/lib/reactor_sdk/endpoints/properties.rb +123 -0
- data/lib/reactor_sdk/endpoints/revisions.rb +102 -0
- data/lib/reactor_sdk/endpoints/rule_components.rb +218 -0
- data/lib/reactor_sdk/endpoints/rules.rb +240 -0
- data/lib/reactor_sdk/endpoints/search.rb +23 -0
- data/lib/reactor_sdk/endpoints/secrets.rb +76 -0
- data/lib/reactor_sdk/error.rb +115 -0
- data/lib/reactor_sdk/library_comparison_builder.rb +74 -0
- data/lib/reactor_sdk/library_snapshot_builder.rb +66 -0
- data/lib/reactor_sdk/paginator.rb +92 -0
- data/lib/reactor_sdk/rate_limiter.rb +96 -0
- data/lib/reactor_sdk/reference_extractor.rb +34 -0
- data/lib/reactor_sdk/resource_metadata.rb +73 -0
- data/lib/reactor_sdk/resource_normalizer.rb +90 -0
- data/lib/reactor_sdk/resources/app_configuration.rb +20 -0
- data/lib/reactor_sdk/resources/audit_event.rb +45 -0
- data/lib/reactor_sdk/resources/base_resource.rb +181 -0
- data/lib/reactor_sdk/resources/build.rb +64 -0
- data/lib/reactor_sdk/resources/callback.rb +16 -0
- data/lib/reactor_sdk/resources/company.rb +38 -0
- data/lib/reactor_sdk/resources/comprehensive_data_element.rb +28 -0
- data/lib/reactor_sdk/resources/comprehensive_extension.rb +30 -0
- data/lib/reactor_sdk/resources/comprehensive_resource.rb +31 -0
- data/lib/reactor_sdk/resources/comprehensive_rule.rb +26 -0
- data/lib/reactor_sdk/resources/comprehensive_upstream_chain.rb +50 -0
- data/lib/reactor_sdk/resources/comprehensive_upstream_chain_entry.rb +34 -0
- data/lib/reactor_sdk/resources/data_element.rb +108 -0
- data/lib/reactor_sdk/resources/environment.rb +45 -0
- data/lib/reactor_sdk/resources/extension.rb +66 -0
- data/lib/reactor_sdk/resources/extension_package.rb +49 -0
- data/lib/reactor_sdk/resources/extension_package_usage_authorization.rb +26 -0
- data/lib/reactor_sdk/resources/host.rb +68 -0
- data/lib/reactor_sdk/resources/library.rb +67 -0
- data/lib/reactor_sdk/resources/library_comparison.rb +72 -0
- data/lib/reactor_sdk/resources/library_comparison_entry.rb +144 -0
- data/lib/reactor_sdk/resources/library_snapshot.rb +118 -0
- data/lib/reactor_sdk/resources/library_snapshot_extension_index.rb +70 -0
- data/lib/reactor_sdk/resources/library_snapshot_index.rb +169 -0
- data/lib/reactor_sdk/resources/library_with_resources.rb +194 -0
- data/lib/reactor_sdk/resources/note.rb +37 -0
- data/lib/reactor_sdk/resources/profile.rb +22 -0
- data/lib/reactor_sdk/resources/property.rb +44 -0
- data/lib/reactor_sdk/resources/revision.rb +156 -0
- data/lib/reactor_sdk/resources/rule.rb +44 -0
- data/lib/reactor_sdk/resources/rule_component.rb +101 -0
- data/lib/reactor_sdk/resources/search_results.rb +28 -0
- data/lib/reactor_sdk/resources/secret.rb +17 -0
- data/lib/reactor_sdk/resources/upstream_chain.rb +80 -0
- data/lib/reactor_sdk/resources/upstream_chain_entry.rb +55 -0
- data/lib/reactor_sdk/response_parser.rb +160 -0
- data/lib/reactor_sdk/version.rb +5 -0
- data/lib/reactor_sdk.rb +79 -0
- data/reactor_sdk.gemspec +70 -0
- data/sig/reactor_sdk.rbs +346 -0
- metadata +293 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file configuration.rb
|
|
5
|
+
# @description Holds and validates all configuration for a ReactorSDK::Client.
|
|
6
|
+
#
|
|
7
|
+
# Validated eagerly on initialization — raises immediately if required
|
|
8
|
+
# values are missing rather than failing mid-request when it is harder
|
|
9
|
+
# to diagnose.
|
|
10
|
+
#
|
|
11
|
+
# The ims_token_url parameter exists specifically for testing — it allows
|
|
12
|
+
# specs to point at a WebMock or VCR stub instead of the real Adobe IMS
|
|
13
|
+
# endpoint without any monkey-patching.
|
|
14
|
+
#
|
|
15
|
+
# @domain Infrastructure
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
module ReactorSDK
|
|
19
|
+
class Configuration
|
|
20
|
+
# Default Reactor API base URL
|
|
21
|
+
DEFAULT_BASE_URL = 'https://reactor.adobe.io'
|
|
22
|
+
|
|
23
|
+
# Default HTTP timeout in seconds
|
|
24
|
+
DEFAULT_TIMEOUT = 30
|
|
25
|
+
|
|
26
|
+
# @return [String] Adobe Developer Console client ID
|
|
27
|
+
attr_reader :client_id
|
|
28
|
+
|
|
29
|
+
# @return [String] Adobe Developer Console client secret
|
|
30
|
+
attr_reader :client_secret
|
|
31
|
+
|
|
32
|
+
# @return [String] Adobe IMS organisation ID (format: XXXXX@AdobeOrg)
|
|
33
|
+
attr_reader :org_id
|
|
34
|
+
|
|
35
|
+
# @return [String] Reactor API base URL
|
|
36
|
+
attr_reader :base_url
|
|
37
|
+
|
|
38
|
+
# @return [String] Adobe IMS token endpoint URL — overridable for testing
|
|
39
|
+
attr_reader :ims_token_url
|
|
40
|
+
|
|
41
|
+
# @return [Integer] HTTP timeout in seconds
|
|
42
|
+
attr_reader :timeout
|
|
43
|
+
|
|
44
|
+
# @return [Logger, nil] Optional logger — if provided, HTTP calls are logged
|
|
45
|
+
attr_reader :logger
|
|
46
|
+
|
|
47
|
+
# @return [Boolean] Whether to auto-refresh the token before expiry
|
|
48
|
+
attr_reader :auto_refresh_token
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Initializes and validates SDK configuration.
|
|
52
|
+
#
|
|
53
|
+
# @param client_id [String] Adobe Developer Console client ID
|
|
54
|
+
# @param client_secret [String] Adobe Developer Console client secret
|
|
55
|
+
# @param org_id [String] Adobe IMS organisation ID
|
|
56
|
+
# @param base_url [String] Override Reactor API base URL (optional)
|
|
57
|
+
# @param ims_token_url [String] Override IMS token URL — for testing only (optional)
|
|
58
|
+
# @param timeout [Integer] HTTP timeout in seconds (optional)
|
|
59
|
+
# @param logger [Logger] Custom logger instance (optional)
|
|
60
|
+
# @param auto_refresh_token [Boolean] Auto-refresh token before expiry (optional)
|
|
61
|
+
# @raise [ReactorSDK::ConfigurationError] if any required value is blank
|
|
62
|
+
#
|
|
63
|
+
def initialize(
|
|
64
|
+
client_id:,
|
|
65
|
+
client_secret:,
|
|
66
|
+
org_id:,
|
|
67
|
+
base_url: DEFAULT_BASE_URL,
|
|
68
|
+
ims_token_url: Authentication::IMS_TOKEN_URL,
|
|
69
|
+
timeout: DEFAULT_TIMEOUT,
|
|
70
|
+
logger: nil,
|
|
71
|
+
auto_refresh_token: true
|
|
72
|
+
)
|
|
73
|
+
@client_id = client_id
|
|
74
|
+
@client_secret = client_secret
|
|
75
|
+
@org_id = org_id
|
|
76
|
+
@base_url = base_url
|
|
77
|
+
@ims_token_url = ims_token_url
|
|
78
|
+
@timeout = timeout
|
|
79
|
+
@logger = logger
|
|
80
|
+
@auto_refresh_token = auto_refresh_token
|
|
81
|
+
|
|
82
|
+
validate!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Checks that all required fields are present and non-blank.
|
|
89
|
+
#
|
|
90
|
+
# @raise [ReactorSDK::ConfigurationError] if any required field is blank
|
|
91
|
+
#
|
|
92
|
+
def validate!
|
|
93
|
+
%i[client_id client_secret org_id].each do |field|
|
|
94
|
+
value = public_send(field)
|
|
95
|
+
if value.nil? || value.strip.empty?
|
|
96
|
+
raise ConfigurationError,
|
|
97
|
+
"#{field} is required and cannot be blank"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file connection.rb
|
|
5
|
+
# @description Authenticated Faraday HTTP connection for the Reactor API.
|
|
6
|
+
#
|
|
7
|
+
# Responsibilities:
|
|
8
|
+
# - Injects required Adobe auth headers on every request
|
|
9
|
+
# - Enforces rate limiting via RateLimiter before every request
|
|
10
|
+
# - Retries automatically on 429 and 5xx via faraday-retry middleware
|
|
11
|
+
# - Translates HTTP error status codes into typed ReactorSDK errors
|
|
12
|
+
# - Parses JSON responses into Ruby hashes
|
|
13
|
+
#
|
|
14
|
+
# @domain Infrastructure
|
|
15
|
+
# @depends ReactorSDK::Authentication, ReactorSDK::RateLimiter
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
module ReactorSDK
|
|
19
|
+
class Connection
|
|
20
|
+
# Pins the Reactor API to version 1 on every request.
|
|
21
|
+
ACCEPT_HEADER = 'application/vnd.api+json;revision=1'
|
|
22
|
+
|
|
23
|
+
# Required content type for all write requests
|
|
24
|
+
CONTENT_TYPE = 'application/vnd.api+json'
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# @param config [ReactorSDK::Configuration] SDK configuration
|
|
28
|
+
# @param auth [ReactorSDK::Authentication] Token provider
|
|
29
|
+
# @param rate_limiter [ReactorSDK::RateLimiter] Request throttler
|
|
30
|
+
#
|
|
31
|
+
def initialize(config, auth, rate_limiter = RateLimiter.new)
|
|
32
|
+
@config = config
|
|
33
|
+
@auth = auth
|
|
34
|
+
@rate_limiter = rate_limiter
|
|
35
|
+
@http = build_faraday_connection
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Executes an authenticated GET request.
|
|
40
|
+
#
|
|
41
|
+
# @param path [String] Relative path
|
|
42
|
+
# @param params [Hash] Optional query string parameters
|
|
43
|
+
# @return [Hash, nil] Parsed JSON response body
|
|
44
|
+
# @raise [ReactorSDK::Error] on non-2xx after all retries exhausted
|
|
45
|
+
#
|
|
46
|
+
def get(path, params: {})
|
|
47
|
+
@rate_limiter.acquire
|
|
48
|
+
response = @http.get(path, params) { |req| inject_headers(req) }
|
|
49
|
+
handle_response(response)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Executes an authenticated POST request.
|
|
54
|
+
#
|
|
55
|
+
# @param path [String] Relative API path
|
|
56
|
+
# @param body [Hash] Request body
|
|
57
|
+
# @return [Hash, nil] Parsed JSON response body
|
|
58
|
+
# @raise [ReactorSDK::Error] on non-2xx after all retries exhausted
|
|
59
|
+
#
|
|
60
|
+
def post(path, body)
|
|
61
|
+
@rate_limiter.acquire
|
|
62
|
+
response = @http.post(path, body.to_json) { |req| inject_headers(req) }
|
|
63
|
+
handle_response(response)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Executes an authenticated multipart POST request.
|
|
68
|
+
#
|
|
69
|
+
# @param path [String] Relative API path
|
|
70
|
+
# @param file_path [String] Path to the file to upload
|
|
71
|
+
# @param field_name [String] Multipart field name
|
|
72
|
+
# @param mime_type [String] MIME type for the uploaded file
|
|
73
|
+
# @return [Hash, nil] Parsed JSON response body
|
|
74
|
+
#
|
|
75
|
+
def post_multipart(path, file_path:, field_name: 'package', mime_type: 'application/octet-stream')
|
|
76
|
+
@rate_limiter.acquire
|
|
77
|
+
response = @http.post(path) do |req|
|
|
78
|
+
inject_headers(req, content_type: nil)
|
|
79
|
+
req.body = {
|
|
80
|
+
field_name => Faraday::Multipart::FilePart.new(file_path, mime_type)
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
handle_response(response)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Executes an authenticated PATCH request.
|
|
88
|
+
#
|
|
89
|
+
# @param path [String] Relative API path
|
|
90
|
+
# @param body [Hash] Partial update body
|
|
91
|
+
# @return [Hash, nil] Parsed JSON response body
|
|
92
|
+
# @raise [ReactorSDK::Error] on non-2xx after all retries exhausted
|
|
93
|
+
#
|
|
94
|
+
def patch(path, body)
|
|
95
|
+
@rate_limiter.acquire
|
|
96
|
+
response = @http.patch(path, body.to_json) { |req| inject_headers(req) }
|
|
97
|
+
handle_response(response)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Executes an authenticated DELETE request with no body.
|
|
102
|
+
#
|
|
103
|
+
# @param path [String] Relative API path
|
|
104
|
+
# @return [nil]
|
|
105
|
+
# @raise [ReactorSDK::Error] on non-2xx after all retries exhausted
|
|
106
|
+
#
|
|
107
|
+
def delete(path)
|
|
108
|
+
@rate_limiter.acquire
|
|
109
|
+
response = @http.delete(path) { |req| inject_headers(req) }
|
|
110
|
+
handle_response(response)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Executes an authenticated DELETE request with a JSON body.
|
|
115
|
+
# Used for JSON:API relationship removal.
|
|
116
|
+
#
|
|
117
|
+
# @param path [String] Relative API path
|
|
118
|
+
# @param body [Hash] Relationship payload
|
|
119
|
+
# @return [nil]
|
|
120
|
+
# @raise [ReactorSDK::Error] on non-2xx after all retries exhausted
|
|
121
|
+
#
|
|
122
|
+
def delete_relationship(path, body)
|
|
123
|
+
@rate_limiter.acquire
|
|
124
|
+
response = @http.run_request(:delete, path, body.to_json, {}) do |req|
|
|
125
|
+
inject_headers(req)
|
|
126
|
+
end
|
|
127
|
+
handle_response(response)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Builds the Faraday connection with retry middleware.
|
|
134
|
+
#
|
|
135
|
+
# @return [Faraday::Connection]
|
|
136
|
+
#
|
|
137
|
+
def build_faraday_connection
|
|
138
|
+
Faraday.new(url: @config.base_url) do |f|
|
|
139
|
+
f.request :multipart
|
|
140
|
+
f.request :retry, retry_options
|
|
141
|
+
f.response :logger, @config.logger if @config.logger
|
|
142
|
+
f.adapter :net_http
|
|
143
|
+
f.options.timeout = @config.timeout
|
|
144
|
+
f.options.open_timeout = 10
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
##
|
|
149
|
+
# Retry configuration for transient Adobe API failures.
|
|
150
|
+
#
|
|
151
|
+
# @return [Hash]
|
|
152
|
+
#
|
|
153
|
+
def retry_options
|
|
154
|
+
{
|
|
155
|
+
max: 3,
|
|
156
|
+
interval: 1.0,
|
|
157
|
+
interval_randomness: 0.5,
|
|
158
|
+
backoff_factor: 2,
|
|
159
|
+
retry_statuses: [429, 500, 502, 503, 504]
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
##
|
|
164
|
+
# Injects required Adobe authentication and versioning headers.
|
|
165
|
+
#
|
|
166
|
+
# @param req [Faraday::Request] Outgoing request
|
|
167
|
+
# @sideeffect Modifies req.headers
|
|
168
|
+
#
|
|
169
|
+
def inject_headers(req, content_type: CONTENT_TYPE)
|
|
170
|
+
req.headers['Authorization'] = "Bearer #{@auth.access_token}"
|
|
171
|
+
req.headers['x-api-key'] = @config.client_id
|
|
172
|
+
req.headers['x-gw-ims-org-id'] = @config.org_id
|
|
173
|
+
req.headers['Accept'] = ACCEPT_HEADER
|
|
174
|
+
req.headers['Content-Type'] = content_type if content_type
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Parses the response and raises typed errors for non-2xx responses.
|
|
179
|
+
#
|
|
180
|
+
# @param response [Faraday::Response] Raw HTTP response
|
|
181
|
+
# @return [Hash, nil] Parsed response body or nil for 204
|
|
182
|
+
# @raise [ReactorSDK::Error] on non-2xx response
|
|
183
|
+
#
|
|
184
|
+
def handle_response(response)
|
|
185
|
+
return nil if response.status == 204
|
|
186
|
+
|
|
187
|
+
body = parse_body(response.body)
|
|
188
|
+
return body if response.status.between?(200, 299)
|
|
189
|
+
|
|
190
|
+
raise_error_for_status(response, body)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# Raises the appropriate typed error for a non-2xx response.
|
|
195
|
+
# Extracts Adobe error detail from the response body when available.
|
|
196
|
+
#
|
|
197
|
+
# @param response [Faraday::Response] Raw HTTP response
|
|
198
|
+
# @param body [Hash, nil] Parsed response body
|
|
199
|
+
# @raise [ReactorSDK::Error]
|
|
200
|
+
#
|
|
201
|
+
def raise_error_for_status(response, body)
|
|
202
|
+
raise_rate_limit_error(response) if response.status == 429
|
|
203
|
+
|
|
204
|
+
raise build_error_for_status(response, body)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
##
|
|
208
|
+
# Builds the appropriate typed error for a non-2xx response.
|
|
209
|
+
#
|
|
210
|
+
# @param response [Faraday::Response]
|
|
211
|
+
# @param body [Hash, nil]
|
|
212
|
+
# @return [ReactorSDK::Error]
|
|
213
|
+
#
|
|
214
|
+
def build_error_for_status(response, body)
|
|
215
|
+
adobe_message = extract_adobe_message(body)
|
|
216
|
+
|
|
217
|
+
case response.status
|
|
218
|
+
when 400, 422
|
|
219
|
+
unprocessable_error(validation_message(response.status, adobe_message), body, status: response.status)
|
|
220
|
+
when 401
|
|
221
|
+
AuthenticationError.new('Unauthorized — check your Adobe IMS token', status: 401)
|
|
222
|
+
when 403
|
|
223
|
+
AuthorizationError.new('Forbidden — token lacks permission for this resource', status: 403)
|
|
224
|
+
when 404
|
|
225
|
+
ResourceNotFoundError.new("Resource not found: #{response.env.url.path}", status: 404)
|
|
226
|
+
when 405
|
|
227
|
+
method_not_allowed_error(response)
|
|
228
|
+
when 409
|
|
229
|
+
conflict_error(adobe_message)
|
|
230
|
+
when 500..599
|
|
231
|
+
ServerError.new("Adobe API server error (HTTP #{response.status})", status: response.status)
|
|
232
|
+
else
|
|
233
|
+
Error.new("Unexpected response status: #{response.status}", status: response.status)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
##
|
|
238
|
+
# Returns the default validation message for 400/422 responses.
|
|
239
|
+
#
|
|
240
|
+
# @param status [Integer]
|
|
241
|
+
# @param adobe_message [String, nil]
|
|
242
|
+
# @return [String]
|
|
243
|
+
#
|
|
244
|
+
def validation_message(status, adobe_message)
|
|
245
|
+
return adobe_message if adobe_message
|
|
246
|
+
return 'Bad request — check payload structure and required relationships' if status == 400
|
|
247
|
+
|
|
248
|
+
'Validation failed'
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
##
|
|
252
|
+
# Builds a validation-style error with the Adobe errors array attached.
|
|
253
|
+
#
|
|
254
|
+
# @param message [String]
|
|
255
|
+
# @param body [Hash, nil]
|
|
256
|
+
# @param status [Integer]
|
|
257
|
+
# @return [ReactorSDK::UnprocessableEntityError]
|
|
258
|
+
#
|
|
259
|
+
def unprocessable_error(message, body, status:)
|
|
260
|
+
UnprocessableEntityError.new(
|
|
261
|
+
message,
|
|
262
|
+
validation_errors: Array(body&.dig('errors')),
|
|
263
|
+
status: status
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
##
|
|
268
|
+
# Builds a 405 error with request context.
|
|
269
|
+
#
|
|
270
|
+
# @param response [Faraday::Response]
|
|
271
|
+
# @return [ReactorSDK::Error]
|
|
272
|
+
#
|
|
273
|
+
def method_not_allowed_error(response)
|
|
274
|
+
Error.new(
|
|
275
|
+
'Method not allowed — check the correct endpoint for this operation. ' \
|
|
276
|
+
"Adobe returned 405 for #{response.env.method.upcase} #{response.env.url.path}",
|
|
277
|
+
status: 405
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
##
|
|
282
|
+
# Builds a 409 conflict error with Launch-specific guidance.
|
|
283
|
+
#
|
|
284
|
+
# @param adobe_message [String, nil]
|
|
285
|
+
# @return [ReactorSDK::Error]
|
|
286
|
+
#
|
|
287
|
+
def conflict_error(adobe_message)
|
|
288
|
+
Error.new(
|
|
289
|
+
adobe_message || 'Conflict — resource may need to be revised before this operation. ' \
|
|
290
|
+
'Call revise() on the resource before adding it to a library.',
|
|
291
|
+
status: 409
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
##
|
|
296
|
+
# Extracts a human-readable message from the Adobe error response body.
|
|
297
|
+
# Adobe returns errors in JSON:API format under the "errors" array.
|
|
298
|
+
#
|
|
299
|
+
# @param body [Hash, nil] Parsed response body
|
|
300
|
+
# @return [String, nil] First error detail or title, or nil if not present
|
|
301
|
+
#
|
|
302
|
+
def extract_adobe_message(body)
|
|
303
|
+
return nil unless body.is_a?(Hash)
|
|
304
|
+
|
|
305
|
+
errors = body['errors']
|
|
306
|
+
return nil unless errors.is_a?(Array) && errors.any?
|
|
307
|
+
|
|
308
|
+
first = errors.first
|
|
309
|
+
first['detail'] || first['title']
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
##
|
|
313
|
+
# Raises a RateLimitError with retry_after from the response header.
|
|
314
|
+
#
|
|
315
|
+
# @param response [Faraday::Response]
|
|
316
|
+
# @raise [ReactorSDK::RateLimitError]
|
|
317
|
+
#
|
|
318
|
+
def raise_rate_limit_error(response)
|
|
319
|
+
retry_after = response.headers['Retry-After']&.to_i
|
|
320
|
+
raise RateLimitError.new(
|
|
321
|
+
"Rate limit exceeded — retry after #{retry_after} seconds",
|
|
322
|
+
retry_after: retry_after,
|
|
323
|
+
status: 429
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
##
|
|
328
|
+
# Parses a JSON string into a Ruby Hash.
|
|
329
|
+
#
|
|
330
|
+
# @param body [String] Raw response body
|
|
331
|
+
# @return [Hash, nil]
|
|
332
|
+
# @raise [ReactorSDK::ParseError] if body is not valid JSON
|
|
333
|
+
#
|
|
334
|
+
def parse_body(body)
|
|
335
|
+
return nil if body.nil? || body.strip.empty?
|
|
336
|
+
|
|
337
|
+
JSON.parse(body)
|
|
338
|
+
rescue JSON::ParserError => e
|
|
339
|
+
raise ParseError.new('Could not parse API response as JSON', cause: e)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Endpoints
|
|
5
|
+
class AppConfigurations < BaseEndpoint
|
|
6
|
+
def list_for_company(company_id)
|
|
7
|
+
list_resources("/companies/#{company_id}/app_configurations", Resources::AppConfiguration)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(config_id)
|
|
11
|
+
fetch_resource("/app_configurations/#{config_id}", Resources::AppConfiguration)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create(company_id:, attributes:)
|
|
15
|
+
create_resource(
|
|
16
|
+
"/companies/#{company_id}/app_configurations",
|
|
17
|
+
'app_configurations',
|
|
18
|
+
Resources::AppConfiguration,
|
|
19
|
+
attributes: attributes
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def update(config_id, attributes)
|
|
24
|
+
update_resource(
|
|
25
|
+
"/app_configurations/#{config_id}",
|
|
26
|
+
config_id,
|
|
27
|
+
'app_configurations',
|
|
28
|
+
Resources::AppConfiguration,
|
|
29
|
+
attributes: attributes
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def delete(config_id)
|
|
34
|
+
delete_resource("/app_configurations/#{config_id}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def company(config_id)
|
|
38
|
+
fetch_resource("/app_configurations/#{config_id}/company", Resources::Company)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file endpoints/audit_events.rb
|
|
5
|
+
# @description Endpoint group for Adobe Launch Audit Event resources.
|
|
6
|
+
#
|
|
7
|
+
# Audit events record every significant action taken within Adobe Launch —
|
|
8
|
+
# creates, updates, deletes, publishes, and state transitions.
|
|
9
|
+
# LaunchGuard syncs these events into its own audit log to provide a
|
|
10
|
+
# complete cross-platform activity record.
|
|
11
|
+
#
|
|
12
|
+
# @domain Endpoints
|
|
13
|
+
# @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/audit-events/
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
module ReactorSDK
|
|
17
|
+
module Endpoints
|
|
18
|
+
class AuditEvents < BaseEndpoint
|
|
19
|
+
##
|
|
20
|
+
# Lists audit events from Adobe's current global audit events endpoint.
|
|
21
|
+
# Follows pagination automatically — returns all events.
|
|
22
|
+
#
|
|
23
|
+
# @param since [String, nil] ISO8601 timestamp — only return events after this time
|
|
24
|
+
# @param updated_at [String, nil] Optional updated_at filter
|
|
25
|
+
# @param type_of [String, nil] Optional event type filter
|
|
26
|
+
# @return [Array<ReactorSDK::Resources::AuditEvent>]
|
|
27
|
+
#
|
|
28
|
+
def list(since: nil, updated_at: nil, type_of: nil)
|
|
29
|
+
params = {}
|
|
30
|
+
params['created_at'] = "GT #{since}" if since
|
|
31
|
+
params['updated_at'] = updated_at if updated_at
|
|
32
|
+
params['type_of'] = type_of if type_of
|
|
33
|
+
|
|
34
|
+
list_resources('/audit_events', Resources::AuditEvent, params: params)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Backward-compatible wrapper for older SDK integrations.
|
|
39
|
+
#
|
|
40
|
+
# Adobe's current official Reactor OpenAPI documents only the global
|
|
41
|
+
# `/audit_events` listing endpoint, so property scoping is no longer
|
|
42
|
+
# performed through the request path.
|
|
43
|
+
#
|
|
44
|
+
# @param _property_id [String]
|
|
45
|
+
# @param since [String, nil]
|
|
46
|
+
# @return [Array<ReactorSDK::Resources::AuditEvent>]
|
|
47
|
+
#
|
|
48
|
+
def list_for_property(_property_id, since: nil)
|
|
49
|
+
list(since: since)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Retrieves a single audit event by its Adobe ID.
|
|
54
|
+
#
|
|
55
|
+
# @param audit_event_id [String] Adobe audit event ID
|
|
56
|
+
# @return [ReactorSDK::Resources::AuditEvent]
|
|
57
|
+
# @raise [ReactorSDK::ResourceNotFoundError] if the event does not exist
|
|
58
|
+
#
|
|
59
|
+
def find(audit_event_id)
|
|
60
|
+
fetch_resource("/audit_events/#{audit_event_id}", Resources::AuditEvent)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|