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::{StorageBudget, 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, Copy, Debug)]
85pub struct OperationSettings {
86 pub queued: NonZeroUsize,
87 pub lifecycle_queued: NonZeroUsize,
88 pub preparation_bytes: Option<NonZeroUsize>,
89}
90
91#[cfg(feature = "plugin")]
92#[derive(Clone, Debug)]
93pub struct PluginSettings {
94 pub configuration: Vec<u8>,
95 pub requires: Vec<harmos_domain::Requirement>,
96 pub host_operations: Vec<crate::Identifier>,
97 pub initialization_timeout: Duration,
98 pub shutdown_timeout: Duration,
99 pub encoding_memory_bytes: NonZeroUsize,
100 pub restart: PluginRestartSettings,
101}
102
103#[cfg(feature = "plugin")]
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub struct PluginRestartSettings {
106 pub retries: u32,
108 pub backoff_base: Duration,
109 pub backoff_cap: Duration,
110}
111
112#[cfg(feature = "plugin-sidecar")]
113#[derive(Clone, Debug)]
114pub struct PluginSidecarSettings {
115 pub plugin: PluginSettings,
116 pub calls: NonZeroUsize,
117}
118
119#[cfg(feature = "plugin-guest")]
120#[derive(Clone, Copy, Debug)]
121pub struct PluginGuestSettings {
122 pub component_bytes: NonZeroUsize,
123 pub memory_bytes: NonZeroUsize,
124 pub fuel: NonZeroU64,
125 pub memories: NonZeroUsize,
126 pub table_elements: NonZeroUsize,
127 pub tables: NonZeroUsize,
128 pub instances: NonZeroUsize,
129}
130
131#[cfg(feature = "plugin")]
138impl Default for PluginRestartSettings {
139 fn default() -> Self {
140 Self { retries: 0, backoff_base: Duration::from_millis(100), backoff_cap: Duration::from_secs(5) }
141 }
142}
143
144#[cfg(feature = "plugin")]
145impl PluginRestartSettings {
146 pub fn validate(&self) -> Result<()> {
147 if self.backoff_base.is_zero()
148 || self.backoff_cap < self.backoff_base
149 || std::time::Instant::now().checked_add(self.backoff_cap).is_none()
150 {
151 return Err(Error::InvalidInput {
152 field: "plugin restart backoff",
153 reason: "requires positive representable bounds with base no greater than cap",
154 });
155 }
156 Ok(())
157 }
158}
159
160#[cfg(feature = "plugin-sidecar")]
161impl Default for PluginSidecarSettings {
162 fn default() -> Self {
163 Self { plugin: PluginSettings::default(), calls: NonZeroUsize::new(4).unwrap() }
164 }
165}
166
167#[cfg(feature = "plugin")]
168impl Default for PluginSettings {
169 fn default() -> Self {
170 Self {
171 configuration: b"null".to_vec(),
172 requires: Vec::new(),
173 host_operations: Vec::new(),
174 initialization_timeout: Duration::from_secs(5),
175 shutdown_timeout: Duration::from_secs(30),
176 encoding_memory_bytes: NonZeroUsize::new(4096).unwrap(),
177 restart: PluginRestartSettings::default(),
178 }
179 }
180}
181
182#[cfg(feature = "plugin")]
183impl PluginSettings {
184 pub fn validate(&self) -> Result<()> {
185 self.restart.validate()?;
186 for timeout in [self.initialization_timeout, self.shutdown_timeout] {
187 if timeout.is_zero() || std::time::Instant::now().checked_add(timeout).is_none() {
188 return Err(Error::InvalidInput { field: "plugin lifecycle timeout", reason: "must be positive and representable" });
189 }
190 }
191 let grants: std::collections::BTreeSet<_> = self.host_operations.iter().collect();
192 if grants.len() != self.host_operations.len() {
193 return Err(Error::InvalidInput { field: "plugin host operations", reason: "grants must be unique" });
194 }
195 Ok(())
196 }
197}
198
199#[cfg(feature = "plugin-guest")]
200impl Default for PluginGuestSettings {
201 fn default() -> Self {
202 Self {
203 component_bytes: NonZeroUsize::new(64 * 1024 * 1024).unwrap(),
204 memory_bytes: NonZeroUsize::new(64 * 1024 * 1024).unwrap(),
205 fuel: NonZeroU64::new(10_000_000).unwrap(),
206 memories: NonZeroUsize::new(1).unwrap(),
207 table_elements: NonZeroUsize::new(65_536).unwrap(),
208 tables: NonZeroUsize::new(8).unwrap(),
209 instances: NonZeroUsize::new(8).unwrap(),
210 }
211 }
212}
213#[cfg(feature = "grpc")]
214impl Default for GrpcServerSettings {
215 fn default() -> Self {
216 let bytes = std::num::NonZeroUsize::new(16 * 1024 * 1024).unwrap();
217 let message = std::num::NonZeroUsize::new(4 * 1024 * 1024).unwrap();
218 let items = std::num::NonZeroUsize::new(16).unwrap();
219 Self {
220 calls: std::num::NonZeroUsize::new(64).unwrap(),
221 decode: DecodeSettings { message_bytes: message, retained_bytes: bytes },
222 encode: EncodeSettings { message_bytes: message, buffer_bytes: std::num::NonZeroUsize::new(8192).unwrap() },
223 input: crate::operations::StreamSettings { items, bytes },
224 output: crate::operations::StreamSettings { items, bytes },
225 opening_timeout: Duration::from_secs(5),
226 }
227 }
228}
229
230#[cfg(feature = "grpc")]
231impl Default for GrpcSettings {
232 fn default() -> Self {
233 Self {
234 address: ([127, 0, 0, 1], 0).into(),
235 operations: GrpcServerSettings::default(),
236 reflection: false,
237 web: false,
238 allowed_origins: Vec::new(),
239
240 tls: None,
241 connections: NonZeroUsize::new(64).unwrap(),
242 calls_per_connection: NonZeroUsize::new(32).unwrap(),
243 request_timeout: Duration::from_secs(60),
244 shutdown_timeout: Duration::from_secs(10),
245 }
246 }
247}
248
249impl OperationSettings {
250 pub(crate) fn validate(&self) -> Result<()> {
251 if self.queued.get() > tokio::sync::Semaphore::MAX_PERMITS || self.lifecycle_queued.get() > tokio::sync::Semaphore::MAX_PERMITS {
252 return Err(Error::InvalidInput { field: "operation queue capacity", reason: "capacity exceeds the semaphore claim limit" });
253 }
254 if self.preparation_bytes.is_some_and(|bytes| bytes.get() > tokio::sync::Semaphore::MAX_PERMITS || bytes.get() > u32::MAX as usize)
255 {
256 return Err(Error::InvalidInput {
257 field: "operation preparation bytes",
258 reason: "budget exceeds the semaphore byte-claim limit",
259 });
260 }
261 Ok(())
262 }
263}
264
265impl Default for OperationSettings {
266 fn default() -> Self {
267 Self {
268 queued: NonZeroUsize::new(256).expect("nonzero ordinary admission bound"),
269 lifecycle_queued: NonZeroUsize::new(256).expect("nonzero lifecycle admission bound"),
270 preparation_bytes: None,
271 }
272 }
273}
274
275