use thiserror::Error;
use super::CapabilityKind;
const MAX_ADAPTER_ERROR_BYTES: usize = 1_024;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("{message}")]
pub struct CapabilityAdapterError {
message: Box<str>,
}
impl CapabilityAdapterError {
pub fn new(message: impl Into<String>) -> Self {
let message = message.into();
let message = if message.is_empty() {
"capability adapter preparation failed".to_owned()
} else {
truncate_utf8(message, MAX_ADAPTER_ERROR_BYTES)
};
Self {
message: message.into_boxed_str(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum CapabilityProjectionError {
#[error("Capability projection is missing runtime value '{capability}'")]
MissingValue { capability: String },
#[error("Capability projection contains unknown runtime value '{capability}'")]
UnexpectedValue { capability: String },
#[error("Capability projection repeats runtime value '{capability}'")]
DuplicateValue { capability: String },
#[error("Capability kind '{kind}' has no supported A3S Code runtime projection")]
UnsupportedKind { kind: CapabilityKind },
#[error(
"Capability '{capability}' descriptor kind '{descriptor_kind}' does not match runtime value kind '{value_kind}'"
)]
KindMismatch {
capability: String,
descriptor_kind: CapabilityKind,
value_kind: CapabilityKind,
},
#[error(
"Capability '{capability}' publishes name '{expected}', but its runtime value publishes '{actual}'"
)]
PublicNameMismatch {
capability: String,
expected: String,
actual: String,
},
#[error(
"Capability '{capability}' descriptor surface digest {expected} does not match its runtime value digest {actual}"
)]
SurfaceDigestMismatch {
capability: String,
expected: String,
actual: String,
},
#[error(
"UI capability '{capability}' cannot bind {dependency_kind} dependency '{dependency}'"
)]
UnsupportedUiDependencyKind {
capability: String,
dependency: String,
dependency_kind: CapabilityKind,
},
#[error("Capability transaction stages unknown target value '{capability}'")]
UnknownStagedCapability { capability: String },
#[error("Capability transaction stages value '{capability}' more than once")]
DuplicateStagedCapability { capability: String },
#[error("Capability transaction is missing staged target value '{capability}'")]
MissingStagedCapability { capability: String },
#[error(
"Capability surface dependency graph contains a cycle ({blocked_count} capabilities blocked; first canonical capability '{first_blocked}')"
)]
DependencyCycle {
first_blocked: String,
blocked_count: usize,
},
#[error(
"Capability readiness graph references missing dependency '{dependency}' from '{capability}'"
)]
ReadinessDependencyMissing {
capability: String,
dependency: String,
},
#[error("Capability readiness field '{field}' exceeds its bound of {max}")]
ReadinessBoundExceeded { field: &'static str, max: usize },
#[error("Capability readiness graph violated an internal invariant: {message}")]
ReadinessGraphInvariant { message: &'static str },
#[error(
"Capability readiness plan does not match its target set (expected generation {expected_generation}, found {actual_generation}; digest mismatch: {digest_mismatch})"
)]
ReadinessPlanMismatch {
expected_generation: u64,
actual_generation: u64,
digest_mismatch: bool,
},
#[error(
"Capability transaction target generation must be {expected}, but the target set is {actual}"
)]
TargetGenerationMismatch { expected: u64, actual: u64 },
#[error(
"Capability recovery bootstrap requires the untouched empty generation zero catalog (found generation {actual_generation} with {actual_capabilities} capabilities)"
)]
BootstrapUnavailable {
actual_generation: u64,
actual_capabilities: usize,
},
#[error("Capability recovery bootstrap target generation must be greater than zero (found {actual})")]
BootstrapTargetGeneration { actual: u64 },
#[error("Capability catalog generation is exhausted")]
GenerationExhausted,
#[error("Capability adapter for '{capability}' failed to prepare: {message}")]
PrepareFailed { capability: String, message: String },
#[error("Capability transaction preparation was cancelled")]
Cancelled,
#[error("Capability transaction exceeds its effect bound of {max}")]
EffectBoundExceeded { max: usize },
#[error(
"Capability commit lost its catalog compare-and-swap race (expected generation {expected_generation} digest {expected_digest}, found generation {actual_generation} digest {actual_digest})"
)]
CommitConflict {
expected_generation: u64,
expected_digest: String,
actual_generation: u64,
actual_digest: String,
},
#[error("Capability transaction entered an invalid internal typestate")]
InvalidTransactionState,
}
fn truncate_utf8(mut value: String, max: usize) -> String {
if value.len() <= max {
return value;
}
let mut boundary = max;
while !value.is_char_boundary(boundary) {
boundary -= 1;
}
value.truncate(boundary);
value
}