use crate::descriptor::HookPhase;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaCollision {
pub schema: String,
pub version: u32,
pub role: String,
pub first_module: String,
pub first_encoding: String,
pub second_module: String,
pub second_encoding: String,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum HostError {
DuplicateModuleId {
id: String,
},
DuplicateOperation {
operation: String,
module: String,
},
EffectConflict {
operation: String,
module: String,
},
DuplicateReceiptNamespace {
namespace: String,
module: String,
},
DuplicateJobKind {
kind: String,
module: String,
},
ModuleHashMismatch {
module: String,
},
ModuleCoherence {
module: String,
detail: String,
},
CanonicalEncoding {
detail: String,
},
SchemaInvalid {
schema: String,
detail: String,
},
SchemaCollision(Box<SchemaCollision>),
SchemaReferenceMissing {
module: String,
operation: Option<String>,
reference: String,
role: String,
},
SchemaReferenceAmbiguous {
module: String,
operation: Option<String>,
reference: String,
role: String,
versions: Vec<u32>,
},
SchemaValidation {
schema: String,
role: String,
detail: String,
},
EmptyHost,
Build(syncbat::BuildError),
SubscriptionInvalidId {
id: String,
detail: String,
},
SubscriptionInvalidProjectionId {
id: String,
detail: String,
},
SubscriptionDuplicateWithinModule {
module: String,
id: String,
},
DuplicateSubscriptionId {
id: String,
module: String,
},
SubscriptionReservedCategory {
category: u8,
},
SubscriptionPayloadSchemaMissing {
module: String,
subscription: String,
reference: String,
role: String,
},
EventPayloadBindingInvalid {
kind: u16,
detail: String,
},
EventPayloadBindingDuplicateWithinModule {
module: String,
kind: u16,
},
DuplicateEventPayloadBinding {
kind: u16,
module: String,
},
EventPayloadBindingConflict {
kind: u16,
first_module: String,
first_schema_ref: String,
second_module: String,
second_schema_ref: String,
},
EventPayloadBindingSchemaMissing {
module: String,
kind: u16,
reference: String,
},
}
impl HostError {
pub(crate) fn coherence(module: impl Into<String>, detail: impl Into<String>) -> Self {
Self::ModuleCoherence {
module: module.into(),
detail: detail.into(),
}
}
pub(crate) fn hook_order_collision(
module: impl Into<String>,
phase: HookPhase,
order: u32,
) -> Self {
Self::coherence(
module,
format!("two {phase} hooks share order {order} (ambiguous ordering)"),
)
}
}
impl std::fmt::Display for HostError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DuplicateModuleId { .. }
| Self::DuplicateOperation { .. }
| Self::EffectConflict { .. }
| Self::DuplicateReceiptNamespace { .. }
| Self::DuplicateJobKind { .. }
| Self::DuplicateSubscriptionId { .. }
| Self::ModuleHashMismatch { .. }
| Self::ModuleCoherence { .. }
| Self::EmptyHost
| Self::Build(_) => fmt_host_wiring_error(self, f),
Self::CanonicalEncoding { detail } => write!(f, "canonical encoding failed: {detail}"),
Self::SchemaInvalid { .. }
| Self::SchemaCollision(_)
| Self::SchemaReferenceMissing { .. }
| Self::SchemaReferenceAmbiguous { .. }
| Self::SchemaValidation { .. }
| Self::SubscriptionInvalidId { .. }
| Self::SubscriptionInvalidProjectionId { .. }
| Self::SubscriptionDuplicateWithinModule { .. }
| Self::SubscriptionReservedCategory { .. }
| Self::SubscriptionPayloadSchemaMissing { .. }
| Self::EventPayloadBindingInvalid { .. }
| Self::EventPayloadBindingDuplicateWithinModule { .. }
| Self::DuplicateEventPayloadBinding { .. }
| Self::EventPayloadBindingConflict { .. }
| Self::EventPayloadBindingSchemaMissing { .. } => fmt_schema_error(self, f),
}
}
}
fn fmt_host_wiring_error(error: &HostError, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match error {
HostError::DuplicateModuleId { id } => write!(f, "module id {id:?} is already mounted"),
HostError::DuplicateOperation { operation, module } => write!(
f,
"operation {operation:?} re-declared by module {module:?} is already mounted"
),
HostError::EffectConflict { operation, module } => write!(
f,
"operation {operation:?} re-declared by module {module:?} has conflicting effect authority"
),
HostError::DuplicateReceiptNamespace { namespace, module } => write!(
f,
"receipt namespace {namespace:?} re-claimed by module {module:?} is already mounted"
),
HostError::DuplicateJobKind { kind, module } => write!(
f,
"supervised-job kind {kind:?} re-declared by module {module:?} is already mounted"
),
HostError::DuplicateSubscriptionId { id, module } => write!(
f,
"subscription id {id:?} re-declared by module {module:?} is already mounted"
),
HostError::ModuleHashMismatch { module } => write!(
f,
"module {module:?} manifest hash does not match its declared parts"
),
HostError::ModuleCoherence { module, detail } => {
write!(f, "module {module:?} is incoherent: {detail}")
}
HostError::EmptyHost => write!(f, "cannot build a host with no mounted modules"),
HostError::Build(error) => write!(f, "lowering into the syncbat runtime failed: {error}"),
HostError::SubscriptionInvalidId { .. }
| HostError::SubscriptionInvalidProjectionId { .. }
| HostError::SubscriptionDuplicateWithinModule { .. }
| HostError::SubscriptionReservedCategory { .. }
| HostError::SubscriptionPayloadSchemaMissing { .. }
| HostError::EventPayloadBindingInvalid { .. }
| HostError::EventPayloadBindingDuplicateWithinModule { .. }
| HostError::DuplicateEventPayloadBinding { .. }
| HostError::EventPayloadBindingConflict { .. }
| HostError::EventPayloadBindingSchemaMissing { .. }
| HostError::SchemaInvalid { .. }
| HostError::SchemaCollision(_)
| HostError::SchemaReferenceMissing { .. }
| HostError::SchemaReferenceAmbiguous { .. }
| HostError::SchemaValidation { .. } => fmt_schema_error(error, f),
HostError::CanonicalEncoding { detail } => write!(f, "canonical encoding failed: {detail}"),
}
}
fn fmt_schema_error(error: &HostError, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match error {
HostError::SchemaInvalid { schema, detail } => {
write!(f, "schema {schema:?} is invalid: {detail}")
}
HostError::SchemaCollision(collision) => fmt_schema_collision(collision, f),
HostError::SchemaReferenceMissing {
module,
operation,
reference,
role,
} => fmt_schema_reference_missing(module, operation.as_deref(), reference, role, f),
HostError::SchemaReferenceAmbiguous {
module,
operation,
reference,
role,
versions,
} => fmt_schema_reference_ambiguous(
module,
operation.as_deref(),
reference,
role,
versions,
f,
),
HostError::SchemaValidation {
schema,
role,
detail,
} => write!(
f,
"schema validation failed for {schema:?} ({role}): {detail}"
),
HostError::SubscriptionInvalidId { .. }
| HostError::SubscriptionInvalidProjectionId { .. }
| HostError::SubscriptionDuplicateWithinModule { .. }
| HostError::DuplicateSubscriptionId { .. }
| HostError::SubscriptionReservedCategory { .. }
| HostError::SubscriptionPayloadSchemaMissing { .. } => fmt_subscription_error(error, f),
HostError::EventPayloadBindingInvalid { .. }
| HostError::EventPayloadBindingDuplicateWithinModule { .. }
| HostError::DuplicateEventPayloadBinding { .. }
| HostError::EventPayloadBindingConflict { .. }
| HostError::EventPayloadBindingSchemaMissing { .. } => {
fmt_event_payload_binding_error(error, f)
}
HostError::CanonicalEncoding { detail } => write!(f, "canonical encoding failed: {detail}"),
HostError::DuplicateModuleId { .. }
| HostError::DuplicateOperation { .. }
| HostError::EffectConflict { .. }
| HostError::DuplicateReceiptNamespace { .. }
| HostError::DuplicateJobKind { .. }
| HostError::ModuleHashMismatch { .. }
| HostError::ModuleCoherence { .. }
| HostError::EmptyHost
| HostError::Build(_) => fmt_host_wiring_error(error, f),
}
}
fn fmt_subscription_error(error: &HostError, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match error {
HostError::SubscriptionInvalidId { id, detail } => {
write!(f, "subscription id {id:?} is invalid: {detail}")
}
HostError::SubscriptionInvalidProjectionId { id, detail } => {
write!(f, "projection id {id:?} is invalid: {detail}")
}
HostError::SubscriptionDuplicateWithinModule { module, id } => write!(
f,
"module {module:?} declares subscription id {id:?} twice"
),
HostError::DuplicateSubscriptionId { id, module } => write!(
f,
"subscription id {id:?} re-declared by module {module:?} is already mounted"
),
HostError::SubscriptionReservedCategory { category } => write!(
f,
"subscription event category 0x{category:02x} is reserved or out of range"
),
HostError::SubscriptionPayloadSchemaMissing {
module,
subscription,
reference,
role,
} => write!(
f,
"module {module:?} subscription {subscription:?} references missing schema {reference:?} for role {role}"
),
HostError::SchemaInvalid { .. }
| HostError::SchemaCollision(_)
| HostError::SchemaReferenceMissing { .. }
| HostError::SchemaReferenceAmbiguous { .. }
| HostError::SchemaValidation { .. }
| HostError::EventPayloadBindingInvalid { .. }
| HostError::EventPayloadBindingDuplicateWithinModule { .. }
| HostError::DuplicateEventPayloadBinding { .. }
| HostError::EventPayloadBindingConflict { .. }
| HostError::EventPayloadBindingSchemaMissing { .. }
| HostError::CanonicalEncoding { .. }
| HostError::DuplicateModuleId { .. }
| HostError::DuplicateOperation { .. }
| HostError::EffectConflict { .. }
| HostError::DuplicateReceiptNamespace { .. }
| HostError::DuplicateJobKind { .. }
| HostError::ModuleHashMismatch { .. }
| HostError::ModuleCoherence { .. }
| HostError::EmptyHost
| HostError::Build(_) => fmt_schema_error(error, f),
}
}
fn fmt_event_payload_binding_error(
error: &HostError,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match error {
HostError::EventPayloadBindingInvalid { kind, detail } => write!(
f,
"event payload binding for kind 0x{kind:04x} is invalid: {detail}"
),
HostError::EventPayloadBindingDuplicateWithinModule { module, kind } => {
write!(f, "module {module:?} binds event kind 0x{kind:04x} twice")
}
HostError::DuplicateEventPayloadBinding { kind, module } => write!(
f,
"event kind 0x{kind:04x} re-bound by module {module:?} is already mounted"
),
HostError::EventPayloadBindingConflict {
kind,
first_module,
first_schema_ref,
second_module,
second_schema_ref,
} => write!(
f,
"event kind 0x{kind:04x} bound to conflicting payload schemas: \
module {first_module:?} => {first_schema_ref:?}, \
module {second_module:?} => {second_schema_ref:?}"
),
HostError::EventPayloadBindingSchemaMissing {
module,
kind,
reference,
} => write!(
f,
"module {module:?} binds event kind 0x{kind:04x} to missing schema {reference:?}"
),
HostError::SchemaInvalid { .. }
| HostError::SchemaCollision(_)
| HostError::SchemaReferenceMissing { .. }
| HostError::SchemaReferenceAmbiguous { .. }
| HostError::SchemaValidation { .. }
| HostError::SubscriptionInvalidId { .. }
| HostError::SubscriptionInvalidProjectionId { .. }
| HostError::SubscriptionDuplicateWithinModule { .. }
| HostError::DuplicateSubscriptionId { .. }
| HostError::SubscriptionReservedCategory { .. }
| HostError::SubscriptionPayloadSchemaMissing { .. }
| HostError::CanonicalEncoding { .. }
| HostError::DuplicateModuleId { .. }
| HostError::DuplicateOperation { .. }
| HostError::EffectConflict { .. }
| HostError::DuplicateReceiptNamespace { .. }
| HostError::DuplicateJobKind { .. }
| HostError::ModuleHashMismatch { .. }
| HostError::ModuleCoherence { .. }
| HostError::EmptyHost
| HostError::Build(_) => fmt_schema_error(error, f),
}
}
fn fmt_schema_collision(
collision: &SchemaCollision,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let SchemaCollision {
schema,
version,
role,
first_module,
first_encoding,
second_module,
second_encoding,
} = collision;
write!(
f,
"schema {schema:?} v{version} ({role}) declared with conflicting encodings: \
module {first_module:?} => {first_encoding}, \
module {second_module:?} => {second_encoding}"
)
}
fn fmt_schema_reference_missing(
module: &str,
operation: Option<&str>,
reference: &str,
role: &str,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(
f,
"module {module:?} references missing schema {reference:?} for role {role}"
)?;
if let Some(operation) = operation {
write!(f, " on operation {operation:?}")?;
}
Ok(())
}
fn fmt_schema_reference_ambiguous(
module: &str,
operation: Option<&str>,
reference: &str,
role: &str,
versions: &[u32],
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(
f,
"module {module:?} references ambiguous schema {reference:?} for role {role}; versions: {versions:?}"
)?;
if let Some(operation) = operation {
write!(f, " on operation {operation:?}")?;
}
Ok(())
}
impl std::error::Error for HostError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Build(error) => Some(error),
Self::DuplicateModuleId { .. }
| Self::DuplicateOperation { .. }
| Self::EffectConflict { .. }
| Self::DuplicateReceiptNamespace { .. }
| Self::DuplicateJobKind { .. }
| Self::DuplicateSubscriptionId { .. }
| Self::ModuleHashMismatch { .. }
| Self::ModuleCoherence { .. }
| Self::CanonicalEncoding { .. }
| Self::SchemaInvalid { .. }
| Self::SchemaCollision(_)
| Self::SchemaReferenceMissing { .. }
| Self::SchemaReferenceAmbiguous { .. }
| Self::SchemaValidation { .. }
| Self::SubscriptionInvalidId { .. }
| Self::SubscriptionInvalidProjectionId { .. }
| Self::SubscriptionDuplicateWithinModule { .. }
| Self::SubscriptionReservedCategory { .. }
| Self::SubscriptionPayloadSchemaMissing { .. }
| Self::EventPayloadBindingInvalid { .. }
| Self::EventPayloadBindingDuplicateWithinModule { .. }
| Self::DuplicateEventPayloadBinding { .. }
| Self::EventPayloadBindingConflict { .. }
| Self::EventPayloadBindingSchemaMissing { .. }
| Self::EmptyHost => None,
}
}
}
impl From<syncbat::BuildError> for HostError {
fn from(error: syncbat::BuildError) -> Self {
Self::Build(error)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HookFailure {
pub phase: HookPhase,
pub module: String,
pub hook: String,
pub detail: String,
}
impl std::fmt::Display for HookFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{phase} hook {hook:?} of module {module:?} failed: {detail}",
phase = self.phase,
hook = self.hook,
module = self.module,
detail = self.detail,
)
}
}
impl std::error::Error for HookFailure {}
#[derive(Debug)]
#[non_exhaustive]
pub enum HostRuntimeError {
StartupHook(HookFailure),
ShutdownHook(HookFailure),
UnknownJobKind {
kind: String,
},
Spawn(batpak::store::SpawnError),
}
impl std::fmt::Display for HostRuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StartupHook(failure) => write!(f, "host startup failed: {failure}"),
Self::ShutdownHook(failure) => write!(f, "host shutdown failed: {failure}"),
Self::UnknownJobKind { kind } => {
write!(f, "no mounted module declares supervised-job kind {kind:?}")
}
Self::Spawn(error) => write!(f, "supervisor could not spawn the job: {error}"),
}
}
}
impl std::error::Error for HostRuntimeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::StartupHook(failure) | Self::ShutdownHook(failure) => Some(failure),
Self::Spawn(error) => Some(error),
Self::UnknownJobKind { .. } => None,
}
}
}
impl From<batpak::store::SpawnError> for HostRuntimeError {
fn from(error: batpak::store::SpawnError) -> Self {
Self::Spawn(error)
}
}