use nutype::nutype;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;
use uuid::Uuid;
#[nutype(derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Display,
TryFrom,
Into
))]
pub struct AgentId(Uuid);
impl AgentId {
pub fn generate() -> Self {
Self::new(Uuid::new_v4())
}
}
#[nutype(
validate(len_char_min = 1, len_char_max = 255),
derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct AgentName(String);
#[nutype(
validate(len_char_min = 1, len_char_max = 100),
derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct HostFunctionName(String);
#[nutype(
validate(less_or_equal = 1073741824), // 1GB max
derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Display, Default, TryFrom, Into),
default = 0,
)]
pub struct MemoryBytes(usize);
impl MemoryBytes {
pub fn zero() -> Self {
Self::default()
}
pub fn from_mb(mb: usize) -> Result<Self, MemoryBytesError> {
Self::try_new(mb * 1024 * 1024)
}
pub fn as_usize(&self) -> usize {
self.into_inner()
}
#[allow(unused_variables)]
pub fn apply_to_wasmtime_store<T>(&self, _store: &mut T) {
let _bytes = self.as_usize(); }
pub fn get_wasmtime_limit(&self) -> usize {
self.as_usize()
}
}
#[nutype(
validate(less_or_equal = 1_000_000_000), // 1 billion max
derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Display, Default, TryFrom, Into),
default = 0,
)]
pub struct CpuFuel(u64);
impl CpuFuel {
pub fn zero() -> Self {
Self::default()
}
#[must_use]
pub fn saturating_add(self, other: Self) -> Self {
let sum = self.into_inner().saturating_add(other.into_inner());
Self::try_new(sum.min(1_000_000_000))
.unwrap_or_else(|_| Self::try_new(1_000_000_000).unwrap())
}
pub fn subtract(self, amount: CpuFuelAmount) -> Result<Self, ValidationError> {
let current = self.into_inner();
let to_subtract = amount.into_inner();
if to_subtract > current {
return Err(ValidationError::ConstraintViolation {
constraint: format!(
"Insufficient fuel: tried to subtract {to_subtract} from {current}"
),
});
}
Self::try_new(current - to_subtract).map_err(|e| ValidationError::InvalidField {
field: "cpu_fuel".to_string(),
reason: e.to_string(),
})
}
#[must_use]
pub fn saturating_subtract(self, amount: CpuFuelAmount) -> Self {
let current = self.into_inner();
let to_subtract = amount.into_inner();
Self::try_new(current.saturating_sub(to_subtract)).unwrap_or_default()
}
#[allow(clippy::should_implement_trait)]
pub fn add(self, amount: CpuFuelAmount) -> Result<Self, ValidationError> {
let current = self.into_inner();
let to_add = amount.into_inner();
let sum = current.saturating_add(to_add);
if sum > 1_000_000_000 {
return Err(ValidationError::ValueOutOfRange {
value: i64::try_from(sum).unwrap_or(i64::MAX),
min: 0,
max: 1_000_000_000,
});
}
Self::try_new(sum).map_err(|e| ValidationError::InvalidField {
field: "cpu_fuel".to_string(),
reason: e.to_string(),
})
}
pub fn as_u64(&self) -> u64 {
self.into_inner()
}
}
#[nutype(
validate(less_or_equal = 10485760), // 10MB max
derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Display, TryFrom, Into),
)]
pub struct MessageSize(usize);
impl MessageSize {
pub fn from_kb(kb: usize) -> Result<Self, MessageSizeError> {
Self::try_new(kb * 1024)
}
}
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 10000),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
TryFrom,
Into,
Default
),
default = 1000
)]
pub struct MaxAgents(usize);
impl MaxAgents {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 1000),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
TryFrom,
Into,
Default
),
default = 10
)]
pub struct MaxImportFunctions(usize);
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 1000),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
TryFrom,
Into,
Default
),
default = 10
)]
pub struct MaxExports(usize);
impl MaxExports {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 0
)]
pub struct MessageCount(usize);
impl MessageCount {
pub fn zero() -> Self {
Self::default()
}
#[must_use]
pub fn increment(self) -> Self {
Self::new(self.into_inner() + 1)
}
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutionTime(Duration);
impl ExecutionTime {
pub fn from_secs(secs: u64) -> Self {
Self(Duration::from_secs(secs))
}
pub fn from_duration(duration: Duration) -> Self {
Self(duration)
}
pub fn as_duration(&self) -> Duration {
self.0
}
}
impl From<Duration> for ExecutionTime {
fn from(duration: Duration) -> Self {
Self(duration)
}
}
impl From<ExecutionTime> for Duration {
fn from(time: ExecutionTime) -> Self {
time.0
}
}
#[nutype(
validate(greater_or_equal = 0, less_or_equal = 10_485_760), // Max 10MB per agent, allowing 0
derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Display, Default, TryFrom, Into),
default = 1_048_576 // Default 1MB
)]
pub struct MaxAgentMemory(usize);
impl MaxAgentMemory {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(greater_or_equal = 0, less_or_equal = 104_857_600), // Max 100MB total, allowing 0
derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Display, Default, TryFrom, Into),
default = 104_857_600 // Default 100MB
)]
pub struct MaxTotalMemory(usize);
impl MaxTotalMemory {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 100_000),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 10_000
)]
pub struct MaxTableEntries(usize);
impl MaxTableEntries {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(len_char_min = 1, len_char_max = 100),
derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct FunctionModuleName(String);
#[nutype(
validate(len_char_min = 1, len_char_max = 1000),
derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct FunctionDescription(String);
#[nutype(
validate(len_char_min = 1, len_char_max = 100),
derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct PermissionName(String);
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 1000),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 10
)]
pub struct ConnectionPoolSize(usize);
impl ConnectionPoolSize {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(greater_or_equal = 60_000, less_or_equal = 86_400_000), // 1 min to 24 hours
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 3_600_000 // 1 hour
)]
pub struct StorageCleanupIntervalMs(u64);
impl StorageCleanupIntervalMs {
pub fn as_duration(&self) -> Duration {
Duration::from_millis(self.into_inner())
}
pub fn as_u64(&self) -> u64 {
self.into_inner()
}
}
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 100_000),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 1000
)]
pub struct RateLimitPerSecond(usize);
impl RateLimitPerSecond {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(greater_or_equal = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 0
)]
pub struct CpuFuelConsumed(u64);
impl CpuFuelConsumed {
pub fn zero() -> Self {
Self::default()
}
pub fn as_u64(&self) -> u64 {
self.into_inner()
}
#[must_use]
pub fn saturating_add(self, other: u64) -> Self {
let sum = self.into_inner().saturating_add(other);
Self::try_new(sum).unwrap_or_default()
}
#[must_use]
pub fn saturating_add_fuel(self, other: CpuFuelAmount) -> Self {
let sum = self.into_inner().saturating_add(other.into_inner());
Self::try_new(sum).unwrap_or_default()
}
}
#[nutype(
validate(greater_or_equal = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 0
)]
pub struct CpuFuelAmount(u64);
impl CpuFuelAmount {
pub fn zero() -> Self {
Self::default()
}
pub fn as_u64(&self) -> u64 {
self.into_inner()
}
pub fn from_u64(value: u64) -> Self {
Self::try_new(value).unwrap_or_default()
}
}
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 32),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct WorkerId(usize);
impl WorkerId {
pub fn as_usize(&self) -> usize {
self.into_inner()
}
pub fn from_zero_based_index(index: usize) -> Result<Self, WorkerIdError> {
Self::try_new(index + 1)
}
}
#[nutype(
validate(greater_or_equal = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 0
)]
pub struct QueueDepth(usize);
impl QueueDepth {
pub fn zero() -> Self {
Self::default()
}
pub fn as_usize(&self) -> usize {
self.into_inner()
}
#[must_use]
pub fn increment(self) -> Self {
Self::try_new(self.into_inner() + 1).unwrap_or(self)
}
#[must_use]
pub fn saturating_decrement(self) -> Self {
Self::try_new(self.into_inner().saturating_sub(1)).unwrap_or_default()
}
}
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 24),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
TryFrom,
Into
)
)]
pub struct RetryAttempt(u8);
impl RetryAttempt {
pub fn as_u8(&self) -> u8 {
self.into_inner()
}
pub fn first() -> Self {
Self::try_new(1).expect("First attempt should always be valid")
}
pub fn increment(self) -> Result<Self, RetryAttemptError> {
Self::try_new(self.into_inner() + 1)
}
pub fn is_final(&self) -> bool {
self.into_inner() == 24
}
}
#[nutype(
validate(greater_or_equal = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 0
)]
pub struct TestAgentId(u32);
impl TestAgentId {
pub fn as_u32(&self) -> u32 {
self.into_inner()
}
pub fn from_usize(value: usize) -> Self {
let max_as_usize = usize::try_from(u32::MAX).unwrap_or(usize::MAX);
let clamped = value.min(max_as_usize);
let clamped_u32 = u32::try_from(clamped).unwrap_or(u32::MAX);
Self::try_new(clamped_u32).unwrap_or_default()
}
}
#[nutype(
validate(greater_or_equal = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
Default,
TryFrom,
Into
),
default = 0
)]
pub struct TestSequence(u32);
impl TestSequence {
pub fn as_u32(&self) -> u32 {
self.into_inner()
}
#[must_use]
pub fn increment(self) -> Self {
Self::try_new(self.into_inner().saturating_add(1)).unwrap_or(self)
}
pub fn zero() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Error, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum ValidationError {
#[error("Invalid field '{field}': {reason}")]
InvalidField { field: String, reason: String },
#[error("Value out of range: {value}, expected {min}-{max}")]
ValueOutOfRange { value: i64, min: i64, max: i64 },
#[error("Invalid format for '{field}': {reason}")]
InvalidFormat { field: String, reason: String },
#[error("Missing required field: {field}")]
MissingField { field: String },
#[error("Constraint violation: {constraint}")]
ConstraintViolation { constraint: String },
}
#[derive(Debug, Clone, Error, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum ResourceCreationError {
#[error("Resource limit exceeded: {resource_type} requested {requested}, limit {limit}")]
LimitExceeded {
resource_type: String,
requested: u64,
limit: u64,
},
#[error("Resource unavailable: {resource_type}")]
Unavailable { resource_type: String },
#[error("Resource already exists: {resource_id}")]
AlreadyExists { resource_id: String },
#[error("Resource not found: {resource_id}")]
NotFound { resource_id: String },
#[error("Invalid resource configuration: {reason}")]
InvalidConfiguration { reason: String },
#[error("Resource dependency error: {dependency} required for {resource}")]
DependencyError {
resource: String,
dependency: String,
},
}