use thiserror::Error;
use crate::definition::InteractionDefinition;
use crate::error::ProtocolError;
use crate::ids::InstanceId;
use crate::instance::{InteractionInstance, LifecycleState};
#[derive(Debug)]
pub struct HostMint {
_private: (),
}
impl HostMint {
#[must_use]
pub fn assert_host_authority() -> Self {
Self { _private: () }
}
}
#[derive(Debug, Error)]
pub enum LifecycleError {
#[error("the offer binds definition `{offered}`, but the host presented `{presented}`")]
DefinitionMismatch {
offered: String,
presented: String,
},
#[error("the offer binds revision {offered}, but the definition is at {presented}")]
RevisionMismatch {
offered: u64,
presented: u64,
},
#[error("{state:?} is terminal; no further transition is possible")]
AlreadyTerminal {
state: LifecycleState,
},
#[error("cannot move from {from:?} to {to:?}")]
IllegalTransition {
from: LifecycleState,
to: LifecycleState,
},
#[error(transparent)]
Protocol(#[from] ProtocolError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lifecycle {
instance: InstanceId,
state: LifecycleState,
}
impl Lifecycle {
#[must_use]
pub fn instance(&self) -> &InstanceId {
&self.instance
}
#[must_use]
pub fn state(&self) -> LifecycleState {
self.state
}
#[must_use]
pub fn is_terminal(&self) -> bool {
terminal(self.state)
}
pub fn transition(&self, to: LifecycleState) -> Result<Self, LifecycleError> {
if terminal(self.state) {
return Err(LifecycleError::AlreadyTerminal { state: self.state });
}
if !terminal(to) {
return Err(LifecycleError::IllegalTransition {
from: self.state,
to,
});
}
Ok(Self {
instance: self.instance,
state: to,
})
}
pub fn expire(&self) -> Result<Self, LifecycleError> {
self.transition(LifecycleState::Expired)
}
#[must_use]
pub fn has_elapsed(instance: &InteractionInstance, now_tick: i64) -> bool {
let deadline = instance
.provenance
.minted_tick
.saturating_add(instance.ttl_ticks);
now_tick >= deadline
}
}
fn terminal(state: LifecycleState) -> bool {
match state {
LifecycleState::Draft | LifecycleState::Published => false,
LifecycleState::Answered
| LifecycleState::Cancelled
| LifecycleState::Expired
| LifecycleState::Unsupported => true,
}
}
pub fn publish(
_mint: &HostMint,
instance: &InteractionInstance,
definition: &InteractionDefinition,
) -> Result<Lifecycle, LifecycleError> {
let presented = definition.definition_id()?;
if instance.definition != presented {
return Err(LifecycleError::DefinitionMismatch {
offered: instance.definition.to_string(),
presented: presented.to_string(),
});
}
if instance.revision != definition.revision {
return Err(LifecycleError::RevisionMismatch {
offered: instance.revision.get(),
presented: definition.revision.get(),
});
}
Ok(Lifecycle {
instance: instance.instance_id()?,
state: LifecycleState::Published,
})
}