platform_provider/
config.rs1#[derive(Clone, PartialEq, Eq)]
2pub struct ProviderConfig {
3 pub name: String,
4 pub export_key: String,
5 pub base_url: String,
6 pub transport: ProviderTransport,
7 pub(crate) auth_token: Option<String>,
8 pub timeout_ms: u64,
9 pub(crate) service_release_digest: Option<String>,
10 pub(crate) module_release_digest: Option<String>,
11 pub(crate) manifest_digest: Option<String>,
12 pub(crate) contract_digests: Vec<String>,
13 pub(crate) allowed_host_function_names: Vec<String>,
14}
15
16impl std::fmt::Debug for ProviderConfig {
17 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 formatter
19 .debug_struct("ProviderConfig")
20 .field("name", &self.name)
21 .field("export_key", &self.export_key)
22 .field("base_url", &self.base_url)
23 .field("transport", &self.transport)
24 .field("auth_configured", &self.auth_token.is_some())
25 .field("timeout_ms", &self.timeout_ms)
26 .field("locked", &self.service_release_digest.is_some())
27 .finish()
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum ProviderTransport {
33 HttpJson,
34 Grpc,
35}
36
37impl ProviderConfig {
38 #[must_use]
39 pub fn new(name: impl Into<String>, base_url: impl Into<String>) -> Self {
40 let (transport, base_url) = normalize_base_url(base_url.into());
41 Self {
42 name: name.into(),
43 export_key: String::new(),
44 base_url,
45 transport,
46 auth_token: None,
47 timeout_ms: 5_000,
48 service_release_digest: None,
49 module_release_digest: None,
50 manifest_digest: None,
51 contract_digests: Vec::new(),
52 allowed_host_function_names: Vec::new(),
53 }
54 }
55
56 #[must_use]
57 pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {
58 self.auth_token = Some(token.into());
59 self
60 }
61
62 #[must_use]
63 pub fn auth_configured(&self) -> bool {
64 self.auth_token.is_some()
65 }
66
67 #[must_use]
68 pub fn with_timeout_ms(mut self, timeout_ms: u64) -> Self {
69 self.timeout_ms = timeout_ms;
70 self
71 }
72
73 #[must_use]
74 pub(crate) fn with_transport(
75 mut self,
76 transport: ProviderTransport,
77 base_url: impl Into<String>,
78 ) -> Self {
79 self.transport = transport;
80 self.base_url = base_url.into().trim_end_matches('/').to_owned();
81 self
82 }
83
84 #[must_use]
85 pub(crate) fn with_export_key(mut self, export_key: impl Into<String>) -> Self {
86 self.export_key = export_key.into();
87 self
88 }
89
90 #[must_use]
91 pub(crate) fn with_locked_contract(
92 mut self,
93 service_release_digest: impl Into<String>,
94 module_release_digest: impl Into<String>,
95 manifest_digest: impl Into<String>,
96 contract_digests: Vec<String>,
97 ) -> Self {
98 self.service_release_digest = Some(service_release_digest.into());
99 self.module_release_digest = Some(module_release_digest.into());
100 self.manifest_digest = Some(manifest_digest.into());
101 self.contract_digests = contract_digests;
102 self
103 }
104
105 #[must_use]
106 pub(crate) fn with_allowed_host_functions(
107 mut self,
108 function_names: impl IntoIterator<Item = String>,
109 ) -> Self {
110 self.allowed_host_function_names = function_names.into_iter().collect();
111 self.allowed_host_function_names.sort();
112 self.allowed_host_function_names.dedup();
113 self
114 }
115
116 #[must_use]
122 pub fn matches_module_id(&self, module_id: &str) -> bool {
123 self.name == module_id
124 || module_id
125 .rsplit_once('/')
126 .is_some_and(|(_, slug)| slug == self.name)
127 }
128}
129
130fn normalize_base_url(base_url: String) -> (ProviderTransport, String) {
131 let trimmed = base_url.trim().trim_end_matches('/');
132 match trimmed.strip_prefix("grpc://") {
133 Some(rest) => (
134 ProviderTransport::Grpc,
135 format!("http://{}", rest.trim_end_matches('/')),
136 ),
137 None => match trimmed.strip_prefix("grpcs://") {
138 Some(rest) => (
139 ProviderTransport::Grpc,
140 format!("https://{}", rest.trim_end_matches('/')),
141 ),
142 None => (ProviderTransport::HttpJson, trimmed.to_owned()),
143 },
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn defaults_to_http_json_transport() {
153 let config =
154 ProviderConfig::new("provider-crm", "http://127.0.0.1:4100/lenso/provider/v1/");
155
156 assert_eq!(config.transport, ProviderTransport::HttpJson);
157 assert_eq!(config.base_url, "http://127.0.0.1:4100/lenso/provider/v1");
158 }
159
160 #[test]
161 fn grpc_scheme_selects_grpc_transport() {
162 let config = ProviderConfig::new("provider-crm", "grpc://127.0.0.1:50051/");
163
164 assert_eq!(config.transport, ProviderTransport::Grpc);
165 assert_eq!(config.base_url, "http://127.0.0.1:50051");
166 }
167
168 #[test]
169 fn grpcs_scheme_selects_grpc_transport_with_tls_endpoint() {
170 let config = ProviderConfig::new("provider-crm", "grpcs://provider.example.test:50051/");
171
172 assert_eq!(config.transport, ProviderTransport::Grpc);
173 assert_eq!(config.base_url, "https://provider.example.test:50051");
174 }
175}