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,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file rate_limiter.rb
|
|
5
|
+
# @description Token bucket rate limiter for Reactor API requests.
|
|
6
|
+
#
|
|
7
|
+
# Adobe enforces a limit of 120 requests per minute per access token.
|
|
8
|
+
# This class implements a token bucket algorithm — the bucket starts
|
|
9
|
+
# full and refills proportionally over time. When empty, the caller
|
|
10
|
+
# blocks until a token is available.
|
|
11
|
+
#
|
|
12
|
+
# One RateLimiter instance is created per ReactorSDK::Client so that
|
|
13
|
+
# different client instances do not share a limit.
|
|
14
|
+
#
|
|
15
|
+
# @domain Infrastructure
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
module ReactorSDK
|
|
19
|
+
class RateLimiter
|
|
20
|
+
# Adobe's documented rate limit per access token
|
|
21
|
+
MAX_REQUESTS_PER_MINUTE = 120
|
|
22
|
+
|
|
23
|
+
# The window over which the limit applies
|
|
24
|
+
INTERVAL_SECONDS = 60.0
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Initializes a full token bucket.
|
|
28
|
+
# The first MAX_REQUESTS_PER_MINUTE calls proceed without any delay.
|
|
29
|
+
#
|
|
30
|
+
def initialize
|
|
31
|
+
@mutex = Mutex.new
|
|
32
|
+
@tokens = MAX_REQUESTS_PER_MINUTE
|
|
33
|
+
@last_tick = Time.now.utc
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Acquires one token from the bucket, blocking if the bucket is empty.
|
|
38
|
+
# Refills the bucket based on elapsed time before checking availability.
|
|
39
|
+
#
|
|
40
|
+
# @return [void]
|
|
41
|
+
#
|
|
42
|
+
def acquire
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
refill
|
|
45
|
+
if @tokens >= 1
|
|
46
|
+
@tokens -= 1
|
|
47
|
+
else
|
|
48
|
+
sleep(seconds_per_token)
|
|
49
|
+
refill
|
|
50
|
+
@tokens -= 1
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Returns the number of tokens currently available.
|
|
57
|
+
# Useful for monitoring and testing — not used internally by acquire.
|
|
58
|
+
#
|
|
59
|
+
# @return [Integer] Current token count
|
|
60
|
+
#
|
|
61
|
+
def available_tokens
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
refill
|
|
64
|
+
@tokens.floor
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Adds tokens proportional to time elapsed since the last tick.
|
|
72
|
+
# Never exceeds MAX_REQUESTS_PER_MINUTE.
|
|
73
|
+
#
|
|
74
|
+
# @sideeffect Updates @tokens and @last_tick
|
|
75
|
+
#
|
|
76
|
+
def refill
|
|
77
|
+
now = Time.now.utc
|
|
78
|
+
elapsed = now - @last_tick
|
|
79
|
+
new_tokens = (elapsed / INTERVAL_SECONDS) * MAX_REQUESTS_PER_MINUTE
|
|
80
|
+
|
|
81
|
+
return unless new_tokens >= 1
|
|
82
|
+
|
|
83
|
+
@tokens = [@tokens + new_tokens.floor, MAX_REQUESTS_PER_MINUTE].min
|
|
84
|
+
@last_tick = now
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Calculates how long to wait for a single token to become available.
|
|
89
|
+
#
|
|
90
|
+
# @return [Float] Seconds to sleep
|
|
91
|
+
#
|
|
92
|
+
def seconds_per_token
|
|
93
|
+
INTERVAL_SECONDS / MAX_REQUESTS_PER_MINUTE
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module ReferenceExtractor
|
|
5
|
+
DATA_ELEMENT_TOKEN_PATTERN = /%([^%\r\n]+)%/
|
|
6
|
+
GET_VAR_PATTERN = /_satellite\.getVar\(\s*(['"])(.*?)\1\s*\)/
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def extract_data_element_names(resource)
|
|
11
|
+
names = Set.new
|
|
12
|
+
|
|
13
|
+
collect_string_references(resource.parsed_settings, names) if resource.respond_to?(:parsed_settings)
|
|
14
|
+
|
|
15
|
+
raw_settings = resource.respond_to?(:settings) ? resource.settings : nil
|
|
16
|
+
collect_string_references(raw_settings, names) if raw_settings.is_a?(String)
|
|
17
|
+
|
|
18
|
+
names.to_a
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def collect_string_references(value, names)
|
|
22
|
+
case value
|
|
23
|
+
when String
|
|
24
|
+
value.scan(DATA_ELEMENT_TOKEN_PATTERN) { |match| names << match.first }
|
|
25
|
+
value.scan(GET_VAR_PATTERN) { |match| names << match.last }
|
|
26
|
+
when Array
|
|
27
|
+
value.each { |item| collect_string_references(item, names) }
|
|
28
|
+
when Hash
|
|
29
|
+
value.each_value { |item| collect_string_references(item, names) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
private_class_method :collect_string_references
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module ResourceMetadata
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def summary(resource)
|
|
8
|
+
decorate!(
|
|
9
|
+
{
|
|
10
|
+
'id' => resource.id,
|
|
11
|
+
'type' => resource.type
|
|
12
|
+
},
|
|
13
|
+
resource
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def base_payload(resource, kind:, attributes:, launch_raw:)
|
|
18
|
+
decorate!(
|
|
19
|
+
{
|
|
20
|
+
'kind' => kind,
|
|
21
|
+
'id' => resource.id,
|
|
22
|
+
'attributes' => attributes,
|
|
23
|
+
'launch_raw' => launch_raw
|
|
24
|
+
},
|
|
25
|
+
resource,
|
|
26
|
+
include_position: false,
|
|
27
|
+
include_revision: false
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delegate_descriptor(resource)
|
|
32
|
+
return '' unless resource.respond_to?(:delegate_descriptor_id)
|
|
33
|
+
|
|
34
|
+
resource.delegate_descriptor_id.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def revision_id_for(resource)
|
|
38
|
+
return resource.revision_id if resource.respond_to?(:revision_id)
|
|
39
|
+
return resource.relationship_id('latest_revision') if resource.respond_to?(:relationship_id)
|
|
40
|
+
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def decorate!(payload, resource, include_position: true, include_revision: true)
|
|
45
|
+
add_name(payload, resource)
|
|
46
|
+
add_delegate_descriptor(payload, resource)
|
|
47
|
+
add_position_fields(payload, resource) if include_position
|
|
48
|
+
add_revision_id(payload, resource) if include_revision
|
|
49
|
+
|
|
50
|
+
payload
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_name(payload, resource)
|
|
54
|
+
payload['name'] = resource.name if resource.respond_to?(:name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_delegate_descriptor(payload, resource)
|
|
58
|
+
return unless resource.respond_to?(:delegate_descriptor_id)
|
|
59
|
+
|
|
60
|
+
payload['delegate_descriptor_id'] = resource.delegate_descriptor_id
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_position_fields(payload, resource)
|
|
64
|
+
payload['order'] = resource.order if resource.respond_to?(:order) && !resource.order.nil?
|
|
65
|
+
payload['rule_order'] = resource.rule_order if resource.respond_to?(:rule_order) && !resource.rule_order.nil?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_revision_id(payload, resource)
|
|
69
|
+
revision_id = revision_id_for(resource)
|
|
70
|
+
payload['revision_id'] = revision_id if revision_id
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module ResourceNormalizer
|
|
5
|
+
CUSTOM_CODE_PATTERN = /::custom-code\z/i
|
|
6
|
+
ADOBE_ALLOY_PATTERN = /\Aadobe-alloy::/i
|
|
7
|
+
ADOBE_ANALYTICS_PATTERN = /\Aadobe-analytics::/i
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def normalize_resource(resource)
|
|
12
|
+
configuration, status = normalize_configuration(resource)
|
|
13
|
+
|
|
14
|
+
ReactorSDK::ResourceMetadata.base_payload(
|
|
15
|
+
resource,
|
|
16
|
+
kind: kind_for(resource),
|
|
17
|
+
attributes: resource.attributes.except('settings'),
|
|
18
|
+
launch_raw: resource.to_h
|
|
19
|
+
).merge(
|
|
20
|
+
'configuration' => configuration,
|
|
21
|
+
'normalization_status' => status
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def summary(resource)
|
|
26
|
+
ReactorSDK::ResourceMetadata.summary(resource)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_json(payload)
|
|
30
|
+
JSON.pretty_generate(payload)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def kind_for(resource)
|
|
34
|
+
case resource.type
|
|
35
|
+
when 'data_elements' then 'data_element'
|
|
36
|
+
when 'rule_components' then 'rule_component'
|
|
37
|
+
else
|
|
38
|
+
resource.type.sub(/s\z/, '')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def normalize_configuration(resource)
|
|
43
|
+
return [nil, 'none'] unless resource.respond_to?(:settings)
|
|
44
|
+
|
|
45
|
+
parsed = resource.respond_to?(:parsed_settings) ? resource.parsed_settings : {}
|
|
46
|
+
return [nil, 'none'] if parsed.empty? && blank_settings?(resource.settings)
|
|
47
|
+
|
|
48
|
+
normalize_delegate_configuration(
|
|
49
|
+
ReactorSDK::ResourceMetadata.delegate_descriptor(resource),
|
|
50
|
+
parsed,
|
|
51
|
+
resource
|
|
52
|
+
)
|
|
53
|
+
rescue JSON::ParserError
|
|
54
|
+
[resource.settings, 'raw']
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def normalize_custom_code(parsed, resource)
|
|
58
|
+
language = parsed['language']
|
|
59
|
+
language = 'javascript' if language.nil? && resource.type == 'data_elements'
|
|
60
|
+
|
|
61
|
+
normalized = {}
|
|
62
|
+
normalized['language'] = language if language
|
|
63
|
+
normalized['source'] = parsed['source'] if parsed.key?('source')
|
|
64
|
+
|
|
65
|
+
parsed.each do |key, value|
|
|
66
|
+
next if %w[language source].include?(key)
|
|
67
|
+
|
|
68
|
+
normalized[key] = value
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
normalized
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def blank_settings?(settings)
|
|
75
|
+
settings.nil? || settings == ''
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def normalize_delegate_configuration(delegate, parsed, resource)
|
|
79
|
+
return [normalize_custom_code(parsed, resource), 'normalized'] if delegate.match?(CUSTOM_CODE_PATTERN)
|
|
80
|
+
return [parsed, 'normalized'] if delegate.match?(ADOBE_ALLOY_PATTERN)
|
|
81
|
+
return [parsed, 'normalized'] if delegate.match?(ADOBE_ANALYTICS_PATTERN)
|
|
82
|
+
return [parsed, 'parsed'] unless parsed.empty?
|
|
83
|
+
|
|
84
|
+
[resource.settings, 'raw']
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private_class_method :kind_for, :normalize_configuration, :normalize_custom_code, :blank_settings?,
|
|
88
|
+
:normalize_delegate_configuration
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class AppConfiguration < BaseResource
|
|
6
|
+
attribute :name
|
|
7
|
+
attribute :app_id
|
|
8
|
+
attribute :platform
|
|
9
|
+
attribute :messaging_service
|
|
10
|
+
attribute :key_type
|
|
11
|
+
attribute :push_credential, default: {}
|
|
12
|
+
attribute :created_at
|
|
13
|
+
attribute :updated_at
|
|
14
|
+
|
|
15
|
+
def inspect
|
|
16
|
+
"#<ReactorSDK::Resources::AppConfiguration id=#{id.inspect} name=#{name.inspect}>"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file resources/audit_event.rb
|
|
5
|
+
# @description Represents an Adobe Launch Audit Event resource.
|
|
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 Resources
|
|
13
|
+
# @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/audit-events/
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
module ReactorSDK
|
|
17
|
+
module Resources
|
|
18
|
+
class AuditEvent < BaseResource
|
|
19
|
+
# @return [String] The type of action that occurred
|
|
20
|
+
attribute :type_of
|
|
21
|
+
|
|
22
|
+
# @return [String] Human-readable name of the affected resource
|
|
23
|
+
attribute :entity_display_name
|
|
24
|
+
|
|
25
|
+
# @return [String] ISO8601 timestamp when the event occurred
|
|
26
|
+
attribute :created_at
|
|
27
|
+
|
|
28
|
+
# @return [Hash, nil] Snapshot of the resource before the action
|
|
29
|
+
attribute :previous_attributes
|
|
30
|
+
|
|
31
|
+
# @return [Hash, nil] Snapshot of the resource after the action
|
|
32
|
+
attribute :updated_attributes
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# @return [String] Human-readable representation
|
|
36
|
+
#
|
|
37
|
+
def inspect
|
|
38
|
+
'#<ReactorSDK::Resources::AuditEvent ' \
|
|
39
|
+
"id=#{id.inspect} " \
|
|
40
|
+
"type=#{type_of.inspect} " \
|
|
41
|
+
"entity=#{entity_display_name.inspect}>"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file resources/base_resource.rb
|
|
5
|
+
# @description Base class for all Adobe Launch resource objects.
|
|
6
|
+
#
|
|
7
|
+
# Provides a class-level `attribute` macro that subclasses use to declare
|
|
8
|
+
# which JSON:API attributes should be exposed as first-class Ruby methods.
|
|
9
|
+
#
|
|
10
|
+
# Instead of accessing raw hash values like:
|
|
11
|
+
# property.attributes["name"]
|
|
12
|
+
# property["platform"]
|
|
13
|
+
#
|
|
14
|
+
# Subclasses declare their fields and callers use clean dot notation:
|
|
15
|
+
# property.name
|
|
16
|
+
# property.platform
|
|
17
|
+
# property.enabled? # boolean fields get a ? alias automatically
|
|
18
|
+
#
|
|
19
|
+
# Usage in a subclass:
|
|
20
|
+
# class Property < BaseResource
|
|
21
|
+
# attribute :name
|
|
22
|
+
# attribute :platform
|
|
23
|
+
# attribute :enabled, as: :boolean
|
|
24
|
+
# attribute :domains, default: []
|
|
25
|
+
# attribute :created_at
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# @domain Resources
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
module ReactorSDK
|
|
32
|
+
module Resources
|
|
33
|
+
class BaseResource
|
|
34
|
+
# @return [String] Adobe resource ID (e.g. "PR1234abcd...")
|
|
35
|
+
attr_reader :id
|
|
36
|
+
|
|
37
|
+
# @return [String] JSON:API resource type (e.g. "rules", "properties")
|
|
38
|
+
attr_reader :type
|
|
39
|
+
|
|
40
|
+
# @return [Hash] Full raw attributes hash from the JSON:API response
|
|
41
|
+
# Available for accessing fields not declared with the attribute macro
|
|
42
|
+
attr_reader :attributes
|
|
43
|
+
|
|
44
|
+
# @return [Hash] Meta hash from the JSON:API response
|
|
45
|
+
attr_reader :meta
|
|
46
|
+
|
|
47
|
+
# @return [Hash] Relationships hash from the JSON:API response
|
|
48
|
+
attr_reader :relationships
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Class-level macro for declaring typed attribute readers.
|
|
52
|
+
# Called in subclass bodies to define which JSON:API attributes
|
|
53
|
+
# are exposed as Ruby methods on the resource object.
|
|
54
|
+
#
|
|
55
|
+
# @param name [Symbol] Attribute name — must match the JSON:API field name
|
|
56
|
+
# @param as [Symbol] Type cast — :boolean adds a ? method alias (optional)
|
|
57
|
+
# @param default [Object] Default value when the attribute is nil (optional)
|
|
58
|
+
#
|
|
59
|
+
# @example Declare a plain string attribute
|
|
60
|
+
# attribute :name
|
|
61
|
+
#
|
|
62
|
+
# @example Declare a boolean attribute (adds enabled? alias)
|
|
63
|
+
# attribute :enabled, as: :boolean
|
|
64
|
+
#
|
|
65
|
+
# @example Declare an array attribute with a default
|
|
66
|
+
# attribute :domains, default: []
|
|
67
|
+
#
|
|
68
|
+
def self.attribute(name, as: nil, default: nil)
|
|
69
|
+
# Define the reader method that pulls from the attributes hash
|
|
70
|
+
define_method(name) do
|
|
71
|
+
value = @attributes[name.to_s]
|
|
72
|
+
value.nil? ? default : value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Boolean fields get a ? alias automatically
|
|
76
|
+
# e.g. attribute :enabled, as: :boolean
|
|
77
|
+
# generates both enabled and enabled?
|
|
78
|
+
define_method(:"#{name}?") { !!public_send(name) } if as == :boolean
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# @param id [String] Adobe resource ID
|
|
83
|
+
# @param type [String] JSON:API resource type
|
|
84
|
+
# @param attributes [Hash] Resource attribute values from the API response
|
|
85
|
+
# @param meta [Hash] Optional metadata from the API response
|
|
86
|
+
# @param relationships [Hash] Optional relationships from the API response
|
|
87
|
+
#
|
|
88
|
+
def initialize(id:, type:, attributes: {}, meta: {}, relationships: {})
|
|
89
|
+
@id = id
|
|
90
|
+
@type = type
|
|
91
|
+
@attributes = attributes
|
|
92
|
+
@meta = meta
|
|
93
|
+
@relationships = relationships
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Provides fallback access to any attribute by name.
|
|
98
|
+
# Use this for attributes not declared with the attribute macro,
|
|
99
|
+
# or for dynamic access patterns.
|
|
100
|
+
#
|
|
101
|
+
# @param key [String, Symbol] Attribute name
|
|
102
|
+
# @return [Object, nil] Attribute value or nil if not present
|
|
103
|
+
#
|
|
104
|
+
def [](key)
|
|
105
|
+
@attributes[key.to_s]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Returns the raw relationship object for a given relationship name.
|
|
110
|
+
#
|
|
111
|
+
# @param name [String, Symbol]
|
|
112
|
+
# @return [Hash, nil]
|
|
113
|
+
#
|
|
114
|
+
def relationship_data(name)
|
|
115
|
+
@relationships[name.to_s]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
# Returns the related resource ID when the relationship contains a single linkage.
|
|
120
|
+
#
|
|
121
|
+
# @param name [String, Symbol]
|
|
122
|
+
# @return [String, nil]
|
|
123
|
+
#
|
|
124
|
+
def relationship_id(name)
|
|
125
|
+
relationship_data(name)&.dig('data', 'id')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Returns related resource IDs when the relationship contains collection linkage.
|
|
130
|
+
#
|
|
131
|
+
# @param name [String, Symbol]
|
|
132
|
+
# @return [Array<String>]
|
|
133
|
+
#
|
|
134
|
+
def relationship_ids(name)
|
|
135
|
+
data = relationship_data(name)&.fetch('data', nil)
|
|
136
|
+
return [] unless data.is_a?(Array)
|
|
137
|
+
|
|
138
|
+
data.filter_map { |item| item['id'] }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# Returns a readable string representation for debugging.
|
|
143
|
+
# Subclasses override this to include their most useful fields.
|
|
144
|
+
#
|
|
145
|
+
# @return [String]
|
|
146
|
+
#
|
|
147
|
+
def inspect
|
|
148
|
+
"#<#{self.class.name} id=#{@id.inspect} type=#{@type.inspect}>"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# Two resources are equal if they represent the same Adobe resource —
|
|
153
|
+
# same id and same type, regardless of which attributes were loaded.
|
|
154
|
+
#
|
|
155
|
+
# @param other [Object] Object to compare with
|
|
156
|
+
# @return [Boolean]
|
|
157
|
+
#
|
|
158
|
+
def ==(other)
|
|
159
|
+
other.is_a?(BaseResource) &&
|
|
160
|
+
other.id == id &&
|
|
161
|
+
other.type == type
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
##
|
|
165
|
+
# Returns a plain hash representation of the resource.
|
|
166
|
+
# Useful for serialisation, logging, and debugging.
|
|
167
|
+
#
|
|
168
|
+
# @return [Hash]
|
|
169
|
+
#
|
|
170
|
+
def to_h
|
|
171
|
+
{
|
|
172
|
+
id: @id,
|
|
173
|
+
type: @type,
|
|
174
|
+
attributes: @attributes,
|
|
175
|
+
meta: @meta,
|
|
176
|
+
relationships: @relationships
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file resources/build.rb
|
|
5
|
+
# @description Represents an Adobe Launch Build resource.
|
|
6
|
+
#
|
|
7
|
+
# A build is the compiled output of a library — the JavaScript bundle
|
|
8
|
+
# that gets deployed to an environment. Builds are created by triggering
|
|
9
|
+
# a library build and move through statuses: pending -> processing ->
|
|
10
|
+
# succeeded or failed.
|
|
11
|
+
#
|
|
12
|
+
# @domain Resources
|
|
13
|
+
# @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/builds/
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
module ReactorSDK
|
|
17
|
+
module Resources
|
|
18
|
+
class Build < BaseResource
|
|
19
|
+
# @return [String] Current build status
|
|
20
|
+
# One of: "pending", "processing", "succeeded", "failed", "rejected"
|
|
21
|
+
attribute :status
|
|
22
|
+
|
|
23
|
+
# @return [String] ISO8601 timestamp when the build was created
|
|
24
|
+
attribute :created_at
|
|
25
|
+
|
|
26
|
+
# @return [String] ISO8601 timestamp when the build was last updated
|
|
27
|
+
attribute :updated_at
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Returns true if the build completed successfully.
|
|
31
|
+
#
|
|
32
|
+
# @return [Boolean]
|
|
33
|
+
#
|
|
34
|
+
def succeeded?
|
|
35
|
+
status == 'succeeded'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Returns true if the build is still in progress.
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
#
|
|
43
|
+
def pending?
|
|
44
|
+
%w[pending processing].include?(status)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Returns true if the build failed.
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
#
|
|
52
|
+
def failed?
|
|
53
|
+
status == 'failed'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# @return [String] Human-readable representation
|
|
58
|
+
#
|
|
59
|
+
def inspect
|
|
60
|
+
"#<ReactorSDK::Resources::Build id=#{id.inspect} status=#{status.inspect}>"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class Callback < BaseResource
|
|
6
|
+
attribute :url
|
|
7
|
+
attribute :subscriptions, default: []
|
|
8
|
+
attribute :created_at
|
|
9
|
+
attribute :updated_at
|
|
10
|
+
|
|
11
|
+
def inspect
|
|
12
|
+
"#<ReactorSDK::Resources::Callback id=#{id.inspect} url=#{url.inspect}>"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file resources/company.rb
|
|
5
|
+
# @description Represents an Adobe Launch Company resource.
|
|
6
|
+
#
|
|
7
|
+
# A Company maps directly to an Adobe IMS Organisation. It is the
|
|
8
|
+
# top-level container — all properties belong to a company.
|
|
9
|
+
# Most orgs have exactly one company.
|
|
10
|
+
#
|
|
11
|
+
# @domain Resources
|
|
12
|
+
# @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/companies/
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
module ReactorSDK
|
|
16
|
+
module Resources
|
|
17
|
+
class Company < BaseResource
|
|
18
|
+
# @return [String] Display name of the company
|
|
19
|
+
attribute :name
|
|
20
|
+
|
|
21
|
+
# @return [String] Adobe IMS organisation ID
|
|
22
|
+
attribute :org_id
|
|
23
|
+
|
|
24
|
+
# @return [String] ISO8601 timestamp when the company was created
|
|
25
|
+
attribute :created_at
|
|
26
|
+
|
|
27
|
+
# @return [String] ISO8601 timestamp when the company was last updated
|
|
28
|
+
attribute :updated_at
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# @return [String] Human-readable representation
|
|
32
|
+
#
|
|
33
|
+
def inspect
|
|
34
|
+
"#<ReactorSDK::Resources::Company id=#{id.inspect} name=#{name.inspect}>"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class ComprehensiveDataElement < ComprehensiveResource
|
|
6
|
+
attr_reader :referenced_data_elements, :impacted_rules
|
|
7
|
+
|
|
8
|
+
def initialize(resource:, referenced_data_elements:, impacted_rules:)
|
|
9
|
+
super(resource: resource)
|
|
10
|
+
@referenced_data_elements = Array(referenced_data_elements)
|
|
11
|
+
@impacted_rules = Array(impacted_rules)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def associated_records
|
|
15
|
+
(@referenced_data_elements + @impacted_rules).uniq
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def normalized_payload
|
|
19
|
+
payload = normalized_resource_payload
|
|
20
|
+
payload['associations'] = {
|
|
21
|
+
'referenced_data_elements' => summaries_for(@referenced_data_elements),
|
|
22
|
+
'impacted_rules' => summaries_for(@impacted_rules)
|
|
23
|
+
}
|
|
24
|
+
payload
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|