overturo 0.7.2
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/LICENSE +201 -0
- data/README.md +395 -0
- data/lib/overturo/api_operations/create.rb +14 -0
- data/lib/overturo/api_operations/delete.rb +14 -0
- data/lib/overturo/api_operations/list.rb +14 -0
- data/lib/overturo/api_operations/nested_resource.rb +52 -0
- data/lib/overturo/api_operations/retrieve.rb +14 -0
- data/lib/overturo/api_operations/update.rb +14 -0
- data/lib/overturo/api_resource.rb +107 -0
- data/lib/overturo/client.rb +231 -0
- data/lib/overturo/configuration.rb +24 -0
- data/lib/overturo/endpoints.rb +27 -0
- data/lib/overturo/errors.rb +120 -0
- data/lib/overturo/http_client.rb +166 -0
- data/lib/overturo/list_object.rb +73 -0
- data/lib/overturo/overturo_object.rb +87 -0
- data/lib/overturo/resources/agree/agreements.rb +20 -0
- data/lib/overturo/resources/agree/collaborations.rb +20 -0
- data/lib/overturo/resources/agree/quorum_requests.rb +22 -0
- data/lib/overturo/resources/comply/anchors.rb +25 -0
- data/lib/overturo/resources/comply/clean_rooms.rb +46 -0
- data/lib/overturo/resources/comply/evidence_packages.rb +15 -0
- data/lib/overturo/resources/comply/residency_posture.rb +31 -0
- data/lib/overturo/resources/comply/transfer_register.rb +28 -0
- data/lib/overturo/resources/connect/applications.rb +20 -0
- data/lib/overturo/resources/connect/connections.rb +20 -0
- data/lib/overturo/resources/connect/flows.rb +77 -0
- data/lib/overturo/resources/connect/meeting_consents.rb +60 -0
- data/lib/overturo/resources/connect/portability.rb +45 -0
- data/lib/overturo/resources/connect/webhooks.rb +42 -0
- data/lib/overturo/resources/consent/consent_records.rb +57 -0
- data/lib/overturo/resources/consent/consent_sessions.rb +49 -0
- data/lib/overturo/resources/consent/decisions.rb +54 -0
- data/lib/overturo/resources/consent/entitlements.rb +18 -0
- data/lib/overturo/resources/consent/trust_levels.rb +18 -0
- data/lib/overturo/resources/core/accounts.rb +18 -0
- data/lib/overturo/resources/core/me.rb +20 -0
- data/lib/overturo/resources/delegate/agent_identities.rb +23 -0
- data/lib/overturo/resources/delegate/authorization_receipts.rb +38 -0
- data/lib/overturo/resources/delegate/delegations.rb +34 -0
- data/lib/overturo/resources/delegate/disclosure_receipts.rb +24 -0
- data/lib/overturo/resources/delegate/security_event_streams.rb +22 -0
- data/lib/overturo/resources/protect/data_licenses.rb +19 -0
- data/lib/overturo/resources/protect/data_requests.rb +17 -0
- data/lib/overturo/resources/protect/policies.rb +23 -0
- data/lib/overturo/resources/protect/transfers.rb +19 -0
- data/lib/overturo/resources/verify/credentials.rb +23 -0
- data/lib/overturo/resources/verify/presentations.rb +18 -0
- data/lib/overturo/resources/verify/trust_scores.rb +17 -0
- data/lib/overturo/resources/verify/verification_sessions.rb +16 -0
- data/lib/overturo/resources/verify/vouches.rb +29 -0
- data/lib/overturo/version.rb +5 -0
- data/lib/overturo.rb +85 -0
- metadata +99 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
module ApiOperations
|
|
5
|
+
module NestedResource
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
# `list_key` names the JSON key the API wraps the collection in; the
|
|
12
|
+
# generated method is always `list_<plural name>` (`list_method` overrides).
|
|
13
|
+
def nested_resource(name, path:, object_key: nil, list_key: nil, list_method: nil, operations: [])
|
|
14
|
+
{ create: [:post, "<resource>/{parent_id}/#{path}"], retrieve: [:get, "<resource>/{parent_id}/#{path}/{id}"],
|
|
15
|
+
list: [:get, "<resource>/{parent_id}/#{path}"], update: [:patch, "<resource>/{parent_id}/#{path}/{id}"],
|
|
16
|
+
delete: [:delete, "<resource>/{parent_id}/#{path}/{id}"] }.each { |op, (m, t)| declare_endpoint(m, t) if operations.include?(op) }
|
|
17
|
+
operations.each do |op|
|
|
18
|
+
case op
|
|
19
|
+
when :create
|
|
20
|
+
define_method(:"create_#{name}") do |parent_id, params = {}|
|
|
21
|
+
response = http_client.post("#{resource_path}/#{parent_id}/#{path}", body: params)
|
|
22
|
+
unwrap(response, object_key || name.to_s)
|
|
23
|
+
end
|
|
24
|
+
when :retrieve
|
|
25
|
+
define_method(:"retrieve_#{name}") do |parent_id, id|
|
|
26
|
+
response = http_client.get("#{resource_path}/#{parent_id}/#{path}/#{id}")
|
|
27
|
+
unwrap(response, object_key || name.to_s)
|
|
28
|
+
end
|
|
29
|
+
when :list
|
|
30
|
+
define_method(:"list_#{list_method || "#{name}s"}") do |parent_id, params = {}|
|
|
31
|
+
lk = list_key || "#{name}s"
|
|
32
|
+
nested_path = "#{resource_path}/#{parent_id}/#{path}"
|
|
33
|
+
response = http_client.get(nested_path, params: params)
|
|
34
|
+
unwrap_list(response, path: nested_path, key: lk, params: params)
|
|
35
|
+
end
|
|
36
|
+
when :update
|
|
37
|
+
define_method(:"update_#{name}") do |parent_id, id, params = {}|
|
|
38
|
+
response = http_client.patch("#{resource_path}/#{parent_id}/#{path}/#{id}", body: params)
|
|
39
|
+
unwrap(response, object_key || name.to_s)
|
|
40
|
+
end
|
|
41
|
+
when :delete
|
|
42
|
+
define_method(:"delete_#{name}") do |parent_id, id|
|
|
43
|
+
response = http_client.delete("#{resource_path}/#{parent_id}/#{path}/#{id}")
|
|
44
|
+
unwrap(response, object_key || name.to_s)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
module ApiOperations
|
|
5
|
+
module Retrieve
|
|
6
|
+
def self.included(base) = base.declare_endpoint(:get, "<resource>/{id}")
|
|
7
|
+
|
|
8
|
+
def retrieve(id)
|
|
9
|
+
response = http_client.get("#{resource_path}/#{id}")
|
|
10
|
+
unwrap(response)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
module ApiOperations
|
|
5
|
+
module Update
|
|
6
|
+
def self.included(base) = base.declare_endpoint(:patch, "<resource>/{id}")
|
|
7
|
+
|
|
8
|
+
def update(id, params = {})
|
|
9
|
+
response = http_client.patch("#{resource_path}/#{id}", body: params)
|
|
10
|
+
unwrap(response)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
class ApiResource
|
|
5
|
+
attr_reader :http_client
|
|
6
|
+
|
|
7
|
+
def initialize(http_client)
|
|
8
|
+
@http_client = http_client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def resource_path
|
|
12
|
+
self.class::RESOURCE_PATH
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def object_key
|
|
16
|
+
self.class::OBJECT_KEY
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def list_key
|
|
20
|
+
self.class::LIST_KEY
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Member-action transport: the verb the API declares for the action.
|
|
24
|
+
def member_request(method, path, params)
|
|
25
|
+
case method
|
|
26
|
+
when :post then http_client.post(path, body: params)
|
|
27
|
+
when :patch then http_client.patch(path, body: params)
|
|
28
|
+
else http_client.get(path, params: params)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def unwrap(response, key = object_key)
|
|
33
|
+
data = response.is_a?(Hash) && response.key?(key) ? response[key] : response
|
|
34
|
+
OverturoObject.new(data)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def unwrap_list(response, path: resource_path, key: list_key, params: {})
|
|
38
|
+
items = if response.is_a?(Array) then response # a bare collection (the accounts API renders one)
|
|
39
|
+
elsif response.is_a?(Hash) && response.key?(key) then response[key]
|
|
40
|
+
else
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
pagination = response.is_a?(Hash) ? (response["pagination"] || {}) : {}
|
|
44
|
+
wrapped = items.map { |item| OverturoObject.new(item) }
|
|
45
|
+
|
|
46
|
+
ListObject.new(
|
|
47
|
+
data: wrapped,
|
|
48
|
+
pagination: pagination,
|
|
49
|
+
http_client: http_client,
|
|
50
|
+
resource_path: path,
|
|
51
|
+
list_key: key,
|
|
52
|
+
params: params
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
# 193 D12 — every endpoint this class can construct, as [METHOD, template].
|
|
58
|
+
# Macros record templates with a <resource> token because `include
|
|
59
|
+
# ApiOperations::*` precedes RESOURCE_PATH in most classes; the token is
|
|
60
|
+
# expanded lazily here. Explicit http_client calls declare themselves.
|
|
61
|
+
def declare_endpoint(method, template)
|
|
62
|
+
(@declared_templates ||= []) << [method.to_s.upcase, template]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def declared_endpoints
|
|
66
|
+
(@declared_templates || []).map { |m, t| [m, t.sub("<resource>", self::RESOURCE_PATH)] }.uniq
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def custom_action(name, method: :post, path: nil, returns: :object)
|
|
70
|
+
action_path = path || name.to_s
|
|
71
|
+
declare_endpoint(method, "<resource>/{id}/#{action_path}")
|
|
72
|
+
|
|
73
|
+
case returns
|
|
74
|
+
when :object
|
|
75
|
+
define_method(name) do |id, params = {}|
|
|
76
|
+
unwrap(member_request(method, "#{resource_path}/#{id}/#{action_path}", params))
|
|
77
|
+
end
|
|
78
|
+
when :raw
|
|
79
|
+
define_method(name) do |id, params = {}|
|
|
80
|
+
OverturoObject.new(member_request(method, "#{resource_path}/#{id}/#{action_path}", params))
|
|
81
|
+
end
|
|
82
|
+
when :list
|
|
83
|
+
raise ArgumentError, "custom_action #{name}: returns: :list is GET-only (declared #{method})" unless method == :get
|
|
84
|
+
|
|
85
|
+
define_method(name) do |id, params = {}|
|
|
86
|
+
lk = path || name.to_s
|
|
87
|
+
response = http_client.get("#{resource_path}/#{id}/#{lk}", params: params)
|
|
88
|
+
unwrap_list(response, path: "#{resource_path}/#{id}/#{lk}", key: lk, params: params)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def collection_action(name, method: :post, path: nil)
|
|
94
|
+
action_path = path || name.to_s
|
|
95
|
+
declare_endpoint(method, "<resource>/#{action_path}")
|
|
96
|
+
define_method(name) do |params = {}|
|
|
97
|
+
response = if method == :post
|
|
98
|
+
http_client.post("#{resource_path}/#{action_path}", body: params)
|
|
99
|
+
else
|
|
100
|
+
http_client.get("#{resource_path}/#{action_path}", params: params)
|
|
101
|
+
end
|
|
102
|
+
unwrap(response)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :config
|
|
6
|
+
|
|
7
|
+
def initialize(api_key: nil, base_url: nil, api_version: nil,
|
|
8
|
+
open_timeout: nil, read_timeout: nil, write_timeout: nil,
|
|
9
|
+
max_retries: nil, logger: nil)
|
|
10
|
+
@config = Configuration.new
|
|
11
|
+
@config.api_key = api_key if api_key
|
|
12
|
+
@config.base_url = base_url if base_url
|
|
13
|
+
@config.api_version = api_version if api_version
|
|
14
|
+
@config.open_timeout = open_timeout if open_timeout
|
|
15
|
+
@config.read_timeout = read_timeout if read_timeout
|
|
16
|
+
@config.write_timeout = write_timeout if write_timeout
|
|
17
|
+
@config.max_retries = max_retries if max_retries
|
|
18
|
+
@config.logger = logger if logger
|
|
19
|
+
|
|
20
|
+
@config.validate!
|
|
21
|
+
@http_client = HttpClient.new(@config)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def connect
|
|
25
|
+
@connect ||= ConnectSurface.new(@http_client)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def consent
|
|
29
|
+
@consent ||= ConsentSurface.new(@http_client)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def verify
|
|
33
|
+
@verify ||= VerifySurface.new(@http_client)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def protect
|
|
37
|
+
@protect ||= ProtectSurface.new(@http_client)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def agree
|
|
41
|
+
@agree ||= AgreeSurface.new(@http_client)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def delegate_ns
|
|
45
|
+
@delegate_ns ||= DelegateSurface.new(@http_client)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def comply
|
|
49
|
+
@comply ||= ComplySurface.new(@http_client)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def core
|
|
53
|
+
@core ||= CoreSurface.new(@http_client)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class SurfaceProxy
|
|
58
|
+
def initialize(http_client)
|
|
59
|
+
@http_client = http_client
|
|
60
|
+
@resources = {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def resource(klass)
|
|
66
|
+
@resources[klass] ||= klass.new(@http_client)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class CoreSurface < SurfaceProxy
|
|
71
|
+
def me
|
|
72
|
+
resource(Resources::Core::Me)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def accounts
|
|
76
|
+
resource(Resources::Core::Accounts)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class ConnectSurface < SurfaceProxy
|
|
81
|
+
def applications
|
|
82
|
+
resource(Resources::Connect::Applications)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def webhooks
|
|
86
|
+
resource(Resources::Connect::Webhooks)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def connections
|
|
90
|
+
resource(Resources::Connect::Connections)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def flows
|
|
94
|
+
resource(Resources::Connect::Flows)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Data portability (GDPR Art. 20).
|
|
98
|
+
def portability
|
|
99
|
+
resource(Resources::Connect::Portability)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Meetings — the unit of record for meeting consent (window, platform,
|
|
103
|
+
# cancellation, participants with their decisions).
|
|
104
|
+
def meeting_consents
|
|
105
|
+
resource(Resources::Connect::MeetingConsents)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class ConsentSurface < SurfaceProxy
|
|
110
|
+
def consent_sessions
|
|
111
|
+
resource(Resources::Consent::ConsentSessions)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# 189 — pre-flight disclosure discovery (publishable-key-scoped).
|
|
115
|
+
def decisions
|
|
116
|
+
resource(Resources::Consent::Decisions)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 201 — consent records captured outside Overturo's surfaces.
|
|
120
|
+
def consent_records
|
|
121
|
+
resource(Resources::Consent::ConsentRecords)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def entitlements
|
|
125
|
+
resource(Resources::Consent::Entitlements)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def trust_levels
|
|
129
|
+
resource(Resources::Consent::TrustLevels)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class VerifySurface < SurfaceProxy
|
|
134
|
+
def credentials
|
|
135
|
+
resource(Resources::Verify::Credentials)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def verification_sessions
|
|
139
|
+
resource(Resources::Verify::VerificationSessions)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def presentations
|
|
143
|
+
resource(Resources::Verify::Presentations)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def trust_scores
|
|
147
|
+
resource(Resources::Verify::TrustScores)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def vouches
|
|
151
|
+
resource(Resources::Verify::Vouches)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
class ProtectSurface < SurfaceProxy
|
|
156
|
+
def policies
|
|
157
|
+
resource(Resources::Protect::Policies)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def data_requests
|
|
161
|
+
resource(Resources::Protect::DataRequests)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def transfers
|
|
165
|
+
resource(Resources::Protect::Transfers)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def data_licenses
|
|
169
|
+
resource(Resources::Protect::DataLicenses)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class AgreeSurface < SurfaceProxy
|
|
174
|
+
def agreements
|
|
175
|
+
resource(Resources::Agree::Agreements)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def quorum_requests
|
|
179
|
+
resource(Resources::Agree::QuorumRequests)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def collaborations
|
|
183
|
+
resource(Resources::Agree::Collaborations)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class DelegateSurface < SurfaceProxy
|
|
188
|
+
def delegations
|
|
189
|
+
resource(Resources::Delegate::Delegations)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def agent_identities
|
|
193
|
+
resource(Resources::Delegate::AgentIdentities)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def security_event_streams
|
|
197
|
+
resource(Resources::Delegate::SecurityEventStreams)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def authorization_receipts
|
|
201
|
+
resource(Resources::Delegate::AuthorizationReceipts)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def disclosure_receipts
|
|
205
|
+
resource(Resources::Delegate::DisclosureReceipts)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class ComplySurface < SurfaceProxy
|
|
210
|
+
def anchors
|
|
211
|
+
resource(Resources::Comply::Anchors)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def evidence_packages
|
|
215
|
+
resource(Resources::Comply::EvidencePackages)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def clean_rooms
|
|
219
|
+
resource(Resources::Comply::CleanRooms)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Cross-border transfer register and residency posture.
|
|
223
|
+
def transfer_register
|
|
224
|
+
resource(Resources::Comply::TransferRegister)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def residency_posture
|
|
228
|
+
resource(Resources::Comply::ResidencyPosture)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :api_key, :base_url, :api_version,
|
|
6
|
+
:open_timeout, :read_timeout, :write_timeout,
|
|
7
|
+
:max_retries, :logger
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@api_key = nil
|
|
11
|
+
@base_url = "https://overturo.com"
|
|
12
|
+
@api_version = "v1"
|
|
13
|
+
@open_timeout = 30
|
|
14
|
+
@read_timeout = 80
|
|
15
|
+
@write_timeout = 30
|
|
16
|
+
@max_retries = 2
|
|
17
|
+
@logger = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def validate!
|
|
21
|
+
raise AuthenticationError, "No API key provided. Set your API key using Overturo::Client.new(api_key: \"sk_live_...\")" unless api_key && !api_key.empty?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
# The machine-readable manifest of every endpoint this gem can call, derived
|
|
5
|
+
# from the resource classes' declared endpoints. `endpoints.json` at the gem
|
|
6
|
+
# root is the committed copy (rake endpoints:write); spec/overturo/endpoints_spec.rb
|
|
7
|
+
# keeps the two equal.
|
|
8
|
+
module Endpoints
|
|
9
|
+
def self.resource_classes
|
|
10
|
+
Overturo::Resources.constants.sort.flat_map do |surface|
|
|
11
|
+
mod = Overturo::Resources.const_get(surface)
|
|
12
|
+
mod.constants.sort.map { |c| mod.const_get(c) }.select { |k| k.is_a?(Class) && k < Overturo::ApiResource }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.manifest
|
|
17
|
+
entries = resource_classes.flat_map do |klass|
|
|
18
|
+
klass.declared_endpoints.map { |m, t| { "method" => m, "path" => "/api/v1#{t}", "resource" => klass.name } }
|
|
19
|
+
end
|
|
20
|
+
entries.uniq.sort_by { |e| [e["path"], e["method"]] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.document
|
|
24
|
+
{ "sdk" => "overturo-ruby", "endpoints" => manifest }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overturo
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :http_status, :http_body, :json_body
|
|
6
|
+
|
|
7
|
+
def initialize(message = nil, http_status: nil, http_body: nil, json_body: nil)
|
|
8
|
+
@http_status = http_status
|
|
9
|
+
@http_body = http_body
|
|
10
|
+
@json_body = json_body
|
|
11
|
+
super(message)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Machine code from a structured error body, across both authority
|
|
15
|
+
# 422 shapes: the receipts read uses {"error" => "<code>",
|
|
16
|
+
# "reason" => <message>}, the disclosure mint uses {"error" =>
|
|
17
|
+
# <message>, "code" => "<code>"}. `code` wins when present; the two
|
|
18
|
+
# shapes are the server's contract, surfaced as-is.
|
|
19
|
+
# The API's error envelope is `{error: {code, message}}`; some endpoints
|
|
20
|
+
# still answer `{error: "<code or message>", code?: "<code>"}`.
|
|
21
|
+
def error_code
|
|
22
|
+
return nil unless json_body.is_a?(Hash)
|
|
23
|
+
|
|
24
|
+
error = json_body["error"]
|
|
25
|
+
return error["code"] if error.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
json_body["code"] || error
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 401
|
|
32
|
+
class AuthenticationError < Error; end
|
|
33
|
+
# 403
|
|
34
|
+
class ForbiddenError < Error; end
|
|
35
|
+
# 404
|
|
36
|
+
class NotFoundError < Error; end
|
|
37
|
+
# 400, 422
|
|
38
|
+
class InvalidRequestError < Error; end
|
|
39
|
+
# 429
|
|
40
|
+
class RateLimitError < Error; end
|
|
41
|
+
# 500+
|
|
42
|
+
class ApiError < Error; end
|
|
43
|
+
# Network errors
|
|
44
|
+
class ConnectionError < Error; end
|
|
45
|
+
# Timeout
|
|
46
|
+
class TimeoutError < Error; end
|
|
47
|
+
|
|
48
|
+
# @api private
|
|
49
|
+
ERROR_MAP = {
|
|
50
|
+
400 => InvalidRequestError,
|
|
51
|
+
401 => AuthenticationError,
|
|
52
|
+
403 => ForbiddenError,
|
|
53
|
+
404 => NotFoundError,
|
|
54
|
+
422 => InvalidRequestError,
|
|
55
|
+
429 => RateLimitError
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# ── OAP denial classes ──────────────────────────────────────────────
|
|
59
|
+
# Raised by OAP-protocol failures (authorize, escalate, complete).
|
|
60
|
+
# Carry the structured envelope fields so callers can branch on
|
|
61
|
+
# `denial_category` or rescue the per-category subclass directly.
|
|
62
|
+
class OAPDenied < Error
|
|
63
|
+
attr_reader :reason_code, :failed_bound, :denial_category, :cascade_step, :detail
|
|
64
|
+
|
|
65
|
+
def initialize(message = nil, reason_code: nil, failed_bound: nil,
|
|
66
|
+
denial_category: nil, cascade_step: nil, detail: nil,
|
|
67
|
+
http_status: nil, http_body: nil, json_body: nil)
|
|
68
|
+
@reason_code = reason_code
|
|
69
|
+
@failed_bound = failed_bound
|
|
70
|
+
@denial_category = denial_category
|
|
71
|
+
@cascade_step = cascade_step
|
|
72
|
+
@detail = detail
|
|
73
|
+
super(message, http_status: http_status, http_body: http_body, json_body: json_body)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Build the most-specific subclass for an OAP error envelope.
|
|
77
|
+
# Reason-code dispatch takes precedence over category dispatch so
|
|
78
|
+
# the most specific subclass wins (sequence denials are
|
|
79
|
+
# trajectory_denied but get their own subclass).
|
|
80
|
+
# @param envelope [Hash] the parsed JSON body (root, not the inner `error`)
|
|
81
|
+
# @param status [Integer, nil] HTTP status for transport-level matching
|
|
82
|
+
def self.from_envelope(envelope, status: nil)
|
|
83
|
+
err = (envelope || {})["error"] || {}
|
|
84
|
+
target = REASON_CODE_TO_CLASS[err["reason_code"]] ||
|
|
85
|
+
CATEGORY_TO_CLASS.fetch(err["denial_category"], OAPDenied)
|
|
86
|
+
target.new(
|
|
87
|
+
err["message"],
|
|
88
|
+
reason_code: err["reason_code"],
|
|
89
|
+
failed_bound: err["failed_bound"],
|
|
90
|
+
denial_category: err["denial_category"],
|
|
91
|
+
cascade_step: err["cascade_step"],
|
|
92
|
+
detail: err["detail"],
|
|
93
|
+
http_status: status,
|
|
94
|
+
json_body: envelope
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# denial_category == "authorization_denied" — OAuth/DPoP layer failed.
|
|
100
|
+
class OAPAuthorizationDenied < OAPDenied; end
|
|
101
|
+
# denial_category == "intent_denied" — request outside grant bounds.
|
|
102
|
+
class OAPIntentDenied < OAPDenied; end
|
|
103
|
+
# denial_category == "trajectory_denied" — execution history blocked the action.
|
|
104
|
+
class OAPTrajectoryDenied < OAPDenied; end
|
|
105
|
+
# sequence_bounds violation
|
|
106
|
+
# (sequence_prohibited / sequence_missing_predecessor). A trajectory
|
|
107
|
+
# denial with a structural-ordering flavour.
|
|
108
|
+
class OAPSequenceDenied < OAPTrajectoryDenied; end
|
|
109
|
+
|
|
110
|
+
OAPDenied::CATEGORY_TO_CLASS = {
|
|
111
|
+
"authorization_denied" => OAPAuthorizationDenied,
|
|
112
|
+
"intent_denied" => OAPIntentDenied,
|
|
113
|
+
"trajectory_denied" => OAPTrajectoryDenied
|
|
114
|
+
}.freeze
|
|
115
|
+
|
|
116
|
+
OAPDenied::REASON_CODE_TO_CLASS = {
|
|
117
|
+
"sequence_prohibited" => OAPSequenceDenied,
|
|
118
|
+
"sequence_missing_predecessor" => OAPSequenceDenied
|
|
119
|
+
}.freeze
|
|
120
|
+
end
|