1pub use harmos_domain::config::*;
17
18#[cfg(feature = "plugin-guest")]
22use std::num::NonZeroU64;
23use std::num::NonZeroUsize;
24#[cfg(any(feature = "grpc", feature = "plugin"))]
25use std::time::Duration;
26
27pub use crate::storage::config::{StorageRequestSettings, StorageResponseSettings, StorageSettings};
31pub use crate::storage::state::StorageStateSettings;
32use crate::{Error, Result};
33
34#[cfg(feature = "grpc")]
43#[derive(Clone, Copy, Debug)]
44pub struct DecodeSettings {
45 pub message_bytes: NonZeroUsize,
46 pub retained_bytes: NonZeroUsize,
47}
48
49#[cfg(feature = "grpc")]
50#[derive(Clone, Copy, Debug)]
51pub struct EncodeSettings {
52 pub message_bytes: NonZeroUsize,
53 pub buffer_bytes: NonZeroUsize,
54}
55
56#[cfg(feature = "grpc")]
57#[derive(Clone, Copy, Debug)]
58pub struct GrpcServerSettings {
59 pub calls: std::num::NonZeroUsize,
60 pub decode: DecodeSettings,
61 pub encode: EncodeSettings,
62 pub input: crate::operations::StreamSettings,
63 pub output: crate::operations::StreamSettings,
64 pub opening_timeout: Duration,
65}
66
67#[cfg(feature = "grpc")]
68#[derive(Clone, Debug)]
69pub struct GrpcSettings {
70 pub address: std::net::SocketAddr,
71 pub operations: GrpcServerSettings,
72 pub reflection: bool,
73 pub web: bool,
74 pub allowed_origins: Vec<crate::grpc::Origin>,
77 pub tls: Option<tonic::transport::ServerTlsConfig>,
78 pub connections: NonZeroUsize,
79 pub calls_per_connection: NonZeroUsize,
80 pub request_timeout: Duration,
81 pub shutdown_timeout: Duration,
82}
83
84#[derive(Clone, Debug)]
85pub struct ManagedStorageSettings {
86 pub replay: crate::storage::ReadLimits,
87 pub capture: crate::storage::ReadLimits,
88 pub snapshots: Option<crate::state::SnapshotPolicy>,
89 pub journal_tail: usize,
90}
91
92#[derive(Clone, Copy, Debug)]
93pub struct OperationSettings {
94 pub queued: NonZeroUsize,
95 pub lifecycle_queued: NonZeroUsize,
96 pub preparation_bytes: Option<NonZeroUsize>,
97}
98
99#[cfg(feature = "plugin")]
100#[derive(Clone, Debug)]
101pub struct PluginSettings {
102 pub configuration: Vec<u8>,
103 pub requires: Vec<harmos_domain::Requirement>,
104 pub host_operations: Vec<crate::Identifier>,
105 pub initialization_timeout: Duration,
106 pub shutdown_timeout: Duration,
107 pub encoding_memory_bytes: NonZeroUsize,
108 pub restart: PluginRestartSettings,
109}
110
111#[cfg(feature = "plugin")]
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub struct PluginRestartSettings {
114 pub retries: u32,
116 pub backoff_base: Duration,
117 pub backoff_cap: Duration,
118}
119
120#[cfg(feature = "plugin-sidecar")]
121#[derive(Clone, Debug)]
122pub struct PluginSidecarSettings {
123 pub plugin: PluginSettings,
124 pub calls: NonZeroUsize,
125}
126
127#[cfg(feature = "plugin-guest")]
128#[derive(Clone, Copy, Debug)]
129pub struct PluginGuestSettings {
130 pub component_bytes: NonZeroUsize,
131 pub memory_bytes: NonZeroUsize,
132 pub fuel: NonZeroU64,
133 pub memories: NonZeroUsize,
134 pub table_elements: NonZeroUsize,
135 pub tables: NonZeroUsize,
136 pub instances: NonZeroUsize,
137}
138
139#[cfg(feature = "plugin")]
146impl Default for PluginRestartSettings {
147 fn default() -> Self {
148 Self {
149 retries: 0,
150 backoff_base: Duration::from_millis(100),
151 backoff_cap: Duration::from_secs(5),
152 }
153 }
154}
155
156#[cfg(feature = "plugin")]
157impl PluginRestartSettings {
158 pub fn validate(&self) -> Result<()> {
159 if self.backoff_base.is_zero()
160 || self.backoff_cap < self.backoff_base
161 || std::time::Instant::now().checked_add(self.backoff_cap).is_none()
162 {
163 return Err(Error::InvalidInput {
164 field: "plugin restart backoff",
165 reason: "requires positive representable bounds with base no greater than cap",
166 });
167 }
168 Ok(())
169 }
170}
171
172#[cfg(feature = "plugin-sidecar")]
173impl Default for PluginSidecarSettings {
174 fn default() -> Self {
175 Self {
176 plugin: PluginSettings::default(),
177 calls: NonZeroUsize::new(4).unwrap(),
178 }
179 }
180}
181
182#[cfg(feature = "plugin")]
183impl Default for PluginSettings {
184 fn default() -> Self {
185 Self {
186 configuration: b"null".to_vec(),
187 requires: Vec::new(),
188 host_operations: Vec::new(),
189 initialization_timeout: Duration::from_secs(5),
190 shutdown_timeout: Duration::from_secs(30),
191 encoding_memory_bytes: NonZeroUsize::new(4096).unwrap(),
192 restart: PluginRestartSettings::default(),
193 }
194 }
195}
196
197#[cfg(feature = "plugin")]
198impl PluginSettings {
199 pub fn validate(&self) -> Result<()> {
200 self.restart.validate()?;
201 for timeout in [self.initialization_timeout, self.shutdown_timeout] {
202 if timeout.is_zero() || std::time::Instant::now().checked_add(timeout).is_none() {
203 return Err(Error::InvalidInput {
204 field: "plugin lifecycle timeout",
205 reason: "must be positive and representable",
206 });
207 }
208 }
209 let grants: std::collections::BTreeSet<_> = self.host_operations.iter().collect();
210 if grants.len() != self.host_operations.len() {
211 return Err(Error::InvalidInput {
212 field: "plugin host operations",
213 reason: "grants must be unique",
214 });
215 }
216 Ok(())
217 }
218}
219
220#[cfg(feature = "plugin-guest")]
221impl Default for PluginGuestSettings {
222 fn default() -> Self {
223 Self {
224 component_bytes: NonZeroUsize::new(64 * 1024 * 1024).unwrap(),
225 memory_bytes: NonZeroUsize::new(64 * 1024 * 1024).unwrap(),
226 fuel: NonZeroU64::new(10_000_000).unwrap(),
227 memories: NonZeroUsize::new(1).unwrap(),
228 table_elements: NonZeroUsize::new(65_536).unwrap(),
229 tables: NonZeroUsize::new(8).unwrap(),
230 instances: NonZeroUsize::new(8).unwrap(),
231 }
232 }
233}
234#[cfg(feature = "grpc")]
235impl Default for GrpcServerSettings {
236 fn default() -> Self {
237 let bytes = std::num::NonZeroUsize::new(16 * 1024 * 1024).unwrap();
238 let message = std::num::NonZeroUsize::new(4 * 1024 * 1024).unwrap();
239 let items = std::num::NonZeroUsize::new(16).unwrap();
240 Self {
241 calls: std::num::NonZeroUsize::new(64).unwrap(),
242 decode: DecodeSettings {
243 message_bytes: message,
244 retained_bytes: bytes,
245 },
246 encode: EncodeSettings {
247 message_bytes: message,
248 buffer_bytes: std::num::NonZeroUsize::new(8192).unwrap(),
249 },
250 input: crate::operations::StreamSettings { items, bytes },
251 output: crate::operations::StreamSettings { items, bytes },
252 opening_timeout: Duration::from_secs(5),
253 }
254 }
255}
256
257#[cfg(feature = "grpc")]
258impl Default for GrpcSettings {
259 fn default() -> Self {
260 Self {
261 address: ([127, 0, 0, 1], 0).into(),
262 operations: GrpcServerSettings::default(),
263 reflection: false,
264 web: false,
265 allowed_origins: Vec::new(),
266
267 tls: None,
268 connections: NonZeroUsize::new(64).unwrap(),
269 calls_per_connection: NonZeroUsize::new(32).unwrap(),
270 request_timeout: Duration::from_secs(60),
271 shutdown_timeout: Duration::from_secs(10),
272 }
273 }
274}
275
276impl OperationSettings {
277 pub(crate) fn validate(&self) -> Result<()> {
278 if self.queued.get() > tokio::sync::Semaphore::MAX_PERMITS || self.lifecycle_queued.get() > tokio::sync::Semaphore::MAX_PERMITS {
279 return Err(Error::InvalidInput {
280 field: "operation queue capacity",
281 reason: "capacity exceeds the semaphore claim limit",
282 });
283 }
284 if self.preparation_bytes.is_some_and(|bytes| bytes.get() > tokio::sync::Semaphore::MAX_PERMITS || bytes.get() > u32::MAX as usize)
285 {
286 return Err(Error::InvalidInput {
287 field: "operation preparation bytes",
288 reason: "budget exceeds the semaphore byte-claim limit",
289 });
290 }
291 Ok(())
292 }
293}
294
295impl Default for OperationSettings {
296 fn default() -> Self {
297 Self {
298 queued: NonZeroUsize::new(256).expect("nonzero ordinary admission bound"),
299 lifecycle_queued: NonZeroUsize::new(256).expect("nonzero lifecycle admission bound"),
300 preparation_bytes: None,
301 }
302 }
303}
304
305