forest_admin_datasource_rpc 1.16.10 → 1.17.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3eddd5bbee08cd80c2df623bc125cfd1c93b5995c7cba4f64b0b4a53a93f7708
|
|
4
|
+
data.tar.gz: 8a57971769da080a8e5a3b030cb7da212665a47054d7fb558d60bf8d0d09b688
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b4b009428c7020b88a55b2673e4f2fc34d11712cb7c710868a3e745f7b1d527d2f2df61e9fbd7a4bf1b70873dc2d5a6358fc85e575f4013f69e9d97b4d3d5ea9
|
|
7
|
+
data.tar.gz: f5052e0d5d2e38572dc15730404ab4ef6d2bdfa75b8733aadffa77566e3c7585a3ec8e779d077be6aa106c78ab762b65dae2adb7d245cc7d5569b4c580f4c5a0
|
|
@@ -5,20 +5,17 @@ require 'time'
|
|
|
5
5
|
|
|
6
6
|
module ForestAdminDatasourceRpc
|
|
7
7
|
module Utils
|
|
8
|
+
# Response wrapper for schema requests that need ETag
|
|
9
|
+
class SchemaResponse
|
|
10
|
+
attr_reader :body, :etag
|
|
11
|
+
|
|
12
|
+
def initialize(body, etag = nil)
|
|
13
|
+
@body = body
|
|
14
|
+
@etag = etag
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
8
18
|
class RpcClient
|
|
9
|
-
# RpcClient handles HTTP communication with the RPC Agent.
|
|
10
|
-
#
|
|
11
|
-
# Error Handling:
|
|
12
|
-
# When the RPC agent returns an error, this client automatically maps HTTP status codes
|
|
13
|
-
# to appropriate Forest Admin exception types. This ensures business errors from the
|
|
14
|
-
# RPC agent are properly propagated to the datasource_rpc.
|
|
15
|
-
#
|
|
16
|
-
# To add support for a new error type:
|
|
17
|
-
# 1. Add the status code and exception class to ERROR_STATUS_MAP
|
|
18
|
-
# 2. (Optional) Add a default message to generate_default_message method
|
|
19
|
-
# 3. Tests will automatically cover the new mapping
|
|
20
|
-
|
|
21
|
-
# Map HTTP status codes to Forest Admin exception classes
|
|
22
19
|
ERROR_STATUS_MAP = {
|
|
23
20
|
400 => ForestAdminAgent::Http::Exceptions::ValidationError,
|
|
24
21
|
401 => ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient,
|
|
@@ -28,12 +25,37 @@ module ForestAdminDatasourceRpc
|
|
|
28
25
|
422 => ForestAdminAgent::Http::Exceptions::UnprocessableError
|
|
29
26
|
}.freeze
|
|
30
27
|
|
|
28
|
+
DEFAULT_ERROR_MESSAGES = {
|
|
29
|
+
400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden',
|
|
30
|
+
404 => 'Not Found', 409 => 'Conflict', 422 => 'Unprocessable Entity'
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
HTTP_NOT_MODIFIED = 304
|
|
34
|
+
NotModified = Class.new
|
|
35
|
+
|
|
31
36
|
def initialize(api_url, auth_secret)
|
|
32
37
|
@api_url = api_url
|
|
33
38
|
@auth_secret = auth_secret
|
|
34
39
|
end
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
# rubocop:disable Metrics/ParameterLists
|
|
42
|
+
def call_rpc(endpoint, caller: nil, method: :get, payload: nil, symbolize_keys: false, if_none_match: nil)
|
|
43
|
+
response = make_request(endpoint, caller: caller, method: method, payload: payload,
|
|
44
|
+
symbolize_keys: symbolize_keys, if_none_match: if_none_match)
|
|
45
|
+
handle_response(response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# rubocop:enable Metrics/ParameterLists
|
|
49
|
+
|
|
50
|
+
def fetch_schema(endpoint, if_none_match: nil)
|
|
51
|
+
response = make_request(endpoint, method: :get, symbolize_keys: true, if_none_match: if_none_match)
|
|
52
|
+
handle_response_with_etag(response)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# rubocop:disable Metrics/ParameterLists
|
|
58
|
+
def make_request(endpoint, caller: nil, method: :get, payload: nil, symbolize_keys: false, if_none_match: nil)
|
|
37
59
|
client = Faraday.new(url: @api_url) do |faraday|
|
|
38
60
|
faraday.request :json
|
|
39
61
|
faraday.response :json, parser_options: { symbolize_names: symbolize_keys }
|
|
@@ -51,22 +73,33 @@ module ForestAdminDatasourceRpc
|
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
headers['forest_caller'] = caller.to_json if caller
|
|
76
|
+
headers['If-None-Match'] = %("#{if_none_match}") if if_none_match
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
handle_response(response)
|
|
78
|
+
client.send(method, endpoint, payload, headers)
|
|
58
79
|
end
|
|
59
|
-
|
|
60
|
-
private
|
|
80
|
+
# rubocop:enable Metrics/ParameterLists
|
|
61
81
|
|
|
62
82
|
def generate_signature(timestamp)
|
|
63
83
|
OpenSSL::HMAC.hexdigest('SHA256', @auth_secret, timestamp)
|
|
64
84
|
end
|
|
65
85
|
|
|
66
86
|
def handle_response(response)
|
|
67
|
-
|
|
87
|
+
return response.body if response.success?
|
|
88
|
+
return NotModified if response.status == HTTP_NOT_MODIFIED
|
|
89
|
+
|
|
90
|
+
raise_appropriate_error(response)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def handle_response_with_etag(response)
|
|
94
|
+
return SchemaResponse.new(response.body, extract_etag(response)) if response.success?
|
|
95
|
+
return NotModified if response.status == HTTP_NOT_MODIFIED
|
|
68
96
|
|
|
69
|
-
response
|
|
97
|
+
raise_appropriate_error(response)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def extract_etag(response)
|
|
101
|
+
etag = response.headers['ETag'] || response.headers['etag']
|
|
102
|
+
etag&.gsub(/\A"?|"?\z/, '')
|
|
70
103
|
end
|
|
71
104
|
|
|
72
105
|
def raise_appropriate_error(response)
|
|
@@ -89,37 +122,18 @@ module ForestAdminDatasourceRpc
|
|
|
89
122
|
end
|
|
90
123
|
|
|
91
124
|
def generate_default_message(status, url)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
401 => "Unauthorized: #{url}",
|
|
95
|
-
403 => "Forbidden: #{url}",
|
|
96
|
-
404 => "Not Found: #{url}",
|
|
97
|
-
409 => "Conflict: #{url}",
|
|
98
|
-
422 => "Unprocessable Entity: #{url}"
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
default_messages[status] || "Unknown error (#{url})"
|
|
125
|
+
prefix = DEFAULT_ERROR_MESSAGES[status] || 'Unknown error'
|
|
126
|
+
"#{prefix}: #{url}"
|
|
102
127
|
end
|
|
103
128
|
|
|
104
129
|
def parse_error_body(response)
|
|
105
130
|
body = response.body
|
|
106
|
-
|
|
107
|
-
# If body is already a hash (Faraday parsed it as JSON)
|
|
108
131
|
return symbolize_error_keys(body) if body.is_a?(Hash)
|
|
132
|
+
return { message: 'Unknown error' } unless body.is_a?(String) && !body.empty?
|
|
109
133
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
parsed = JSON.parse(body)
|
|
114
|
-
return symbolize_error_keys(parsed)
|
|
115
|
-
rescue JSON::ParserError
|
|
116
|
-
# If parsing fails, return the body as the message
|
|
117
|
-
return { message: body }
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Fallback for empty or unexpected body types
|
|
122
|
-
{ message: 'Unknown error' }
|
|
134
|
+
symbolize_error_keys(JSON.parse(body))
|
|
135
|
+
rescue JSON::ParserError
|
|
136
|
+
{ message: body }
|
|
123
137
|
end
|
|
124
138
|
|
|
125
139
|
def symbolize_error_keys(hash)
|
|
@@ -13,10 +13,15 @@ module ForestAdminDatasourceRpc
|
|
|
13
13
|
auth_secret = options[:auth_secret] || ForestAdminAgent::Facades::Container.cache(:auth_secret)
|
|
14
14
|
ForestAdminAgent::Facades::Container.logger.log('Info', "Getting schema from RPC agent on #{uri}.")
|
|
15
15
|
|
|
16
|
+
schema = nil
|
|
17
|
+
cached_etag = nil
|
|
18
|
+
|
|
16
19
|
begin
|
|
17
20
|
rpc_client = Utils::RpcClient.new(uri, auth_secret)
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
response = rpc_client.fetch_schema('/forest/rpc-schema')
|
|
22
|
+
schema = response.body
|
|
23
|
+
# Use the ETag header for conditional requests
|
|
24
|
+
cached_etag = response.etag
|
|
20
25
|
rescue Faraday::ConnectionFailed => e
|
|
21
26
|
ForestAdminAgent::Facades::Container.logger.log(
|
|
22
27
|
'Error',
|
|
@@ -45,11 +50,17 @@ module ForestAdminDatasourceRpc
|
|
|
45
50
|
else
|
|
46
51
|
sse = Utils::SseClient.new("#{uri}/forest/sse", auth_secret) do
|
|
47
52
|
ForestAdminAgent::Facades::Container.logger.log('Info', 'RPC server stopped, checking schema...')
|
|
48
|
-
new_schema = rpc_client.call_rpc('/forest/rpc-schema', method: :get, symbolize_keys: true)
|
|
49
53
|
|
|
50
|
-
if
|
|
51
|
-
|
|
54
|
+
# Send If-None-Match header to check if schema has changed (304 optimization)
|
|
55
|
+
result = rpc_client.fetch_schema('/forest/rpc-schema', if_none_match: cached_etag)
|
|
56
|
+
|
|
57
|
+
# If we get NotModified, schema hasn't changed
|
|
58
|
+
if result == Utils::RpcClient::NotModified
|
|
59
|
+
ForestAdminAgent::Facades::Container.logger.log('Debug', '[RPCDatasource] Schema has not changed (304)')
|
|
52
60
|
else
|
|
61
|
+
# Schema has changed, update the cached ETag and schema, then reload
|
|
62
|
+
cached_etag = result.etag
|
|
63
|
+
ForestAdminAgent::Facades::Container.logger.log('Info', '[RPCDatasource] Schema changed, reloading agent...')
|
|
53
64
|
ForestAdminAgent::Builder::AgentFactory.instance.reload!
|
|
54
65
|
end
|
|
55
66
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_admin_datasource_rpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthieu
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2025-12-
|
|
12
|
+
date: 2025-12-11 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: base64
|