use std::fmt;
use crate::{operation_error::OperationError, operation_wire::*};
use sim_kernel::{CapabilityName, ContentId, Datum, Symbol};
pub(crate) const INTENT_TAG: &str = "intent-v1";
pub(crate) const GRANT_TAG: &str = "grant-v1";
pub(crate) const ATTEMPT_TAG: &str = "attempt-v1";
pub(crate) const DISPATCH_TAG: &str = "dispatch-v1";
pub(crate) const RECEIPT_TAG: &str = "performer-receipt-v1";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReplayPolicy {
Idempotent,
ExactlyOnce,
}
impl ReplayPolicy {
pub(crate) fn datum(self) -> Datum {
Datum::Symbol(Symbol::qualified(
"operation",
match self {
Self::Idempotent => "idempotent",
Self::ExactlyOnce => "exactly-once",
},
))
}
pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
match datum {
Datum::Symbol(value) if *value == Symbol::qualified("operation", "idempotent") => {
Ok(Self::Idempotent)
}
Datum::Symbol(value) if *value == Symbol::qualified("operation", "exactly-once") => {
Ok(Self::ExactlyOnce)
}
_ => Err(OperationError::NonCanonical("replay policy")),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OperationId(pub(crate) ContentId);
pub type OperationIntentId = OperationId;
impl OperationId {
pub const fn content_id(&self) -> &ContentId {
&self.0
}
}
impl fmt::Display for OperationId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
render_id(&self.0, formatter)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationIntent {
pub(crate) id: OperationId,
pub(crate) operation: String,
pub(crate) target: Datum,
pub(crate) intended_result: Datum,
pub(crate) replay_policy: ReplayPolicy,
}
impl OperationIntent {
pub fn new(
operation: impl Into<String>,
target: Datum,
intended_result: Datum,
replay_policy: ReplayPolicy,
) -> Result<Self, OperationError> {
let operation = operation.into();
if operation.is_empty() {
return Err(OperationError::EmptyOperation);
}
let datum = intent_datum(&operation, &target, &intended_result, replay_policy);
Ok(Self {
id: OperationId(content_id(&datum)?),
operation,
target,
intended_result,
replay_policy,
})
}
pub const fn id(&self) -> &OperationId {
&self.id
}
pub fn operation(&self) -> &str {
&self.operation
}
pub const fn target(&self) -> &Datum {
&self.target
}
pub const fn intended_result(&self) -> &Datum {
&self.intended_result
}
pub const fn replay_policy(&self) -> ReplayPolicy {
self.replay_policy
}
pub fn canonical_datum(&self) -> Datum {
intent_datum(
&self.operation,
&self.target,
&self.intended_result,
self.replay_policy,
)
}
pub(crate) fn verify(&self) -> Result<(), OperationError> {
if content_id(&self.canonical_datum())? != self.id.0 {
return Err(OperationError::ContradictoryIntent);
}
Ok(())
}
pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, INTENT_TAG, 4)?;
let operation = string_field(fields, "operation")?.to_owned();
let target = field(fields, "target")?.clone();
let intended_result = field(fields, "intended-result")?.clone();
let replay_policy = ReplayPolicy::from_datum(field(fields, "replay-policy")?)?;
let value = Self::new(operation, target, intended_result, replay_policy)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("operation intent"));
}
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OperationGrantId(pub(crate) ContentId);
impl OperationGrantId {
pub const fn content_id(&self) -> &ContentId {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationGrant {
pub(crate) id: OperationGrantId,
pub(crate) operation: OperationId,
pub(crate) capability: CapabilityName,
pub(crate) authority: Datum,
}
impl OperationGrant {
pub fn new(
operation: OperationId,
capability: CapabilityName,
authority: Datum,
) -> Result<Self, OperationError> {
let datum = grant_datum(&operation, &capability, &authority);
Ok(Self {
id: OperationGrantId(content_id(&datum)?),
operation,
capability,
authority,
})
}
pub const fn id(&self) -> &OperationGrantId {
&self.id
}
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn capability(&self) -> &CapabilityName {
&self.capability
}
pub const fn authority(&self) -> &Datum {
&self.authority
}
pub fn canonical_datum(&self) -> Datum {
grant_datum(&self.operation, &self.capability, &self.authority)
}
pub(crate) fn verify(&self) -> Result<(), OperationError> {
if content_id(&self.canonical_datum())? != self.id.0 {
return Err(OperationError::NonCanonical("operation grant"));
}
Ok(())
}
pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, GRANT_TAG, 3)?;
let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
let capability = CapabilityName::new(string_field(fields, "capability")?);
let authority = field(fields, "authority")?.clone();
let value = Self::new(operation, capability, authority)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("operation grant"));
}
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OperationAttemptId(pub(crate) ContentId);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationAttempt {
pub(crate) id: OperationAttemptId,
pub(crate) operation: OperationId,
pub(crate) ordinal: u64,
}
impl OperationAttempt {
pub fn new(operation: OperationId, ordinal: u64) -> Result<Self, OperationError> {
let datum = attempt_datum(&operation, ordinal);
Ok(Self {
id: OperationAttemptId(content_id(&datum)?),
operation,
ordinal,
})
}
pub const fn id(&self) -> &OperationAttemptId {
&self.id
}
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn ordinal(&self) -> u64 {
self.ordinal
}
pub fn canonical_datum(&self) -> Datum {
attempt_datum(&self.operation, self.ordinal)
}
pub(crate) fn verify(&self) -> Result<(), OperationError> {
if content_id(&self.canonical_datum())? != self.id.0 {
return Err(OperationError::NonCanonical("operation attempt"));
}
Ok(())
}
pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, ATTEMPT_TAG, 2)?;
let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
let ordinal = u64_field(fields, "ordinal")?;
let value = Self::new(operation, ordinal)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("operation attempt"));
}
Ok(value)
}
}
impl OperationAttemptId {
pub const fn content_id(&self) -> &ContentId {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DispatchId(pub(crate) ContentId);
impl DispatchId {
pub const fn content_id(&self) -> &ContentId {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationDispatch {
pub(crate) id: DispatchId,
pub(crate) operation: OperationId,
pub(crate) grant: OperationGrantId,
pub(crate) attempt: OperationAttemptId,
}
impl OperationDispatch {
pub(crate) fn new(
operation: OperationId,
grant: OperationGrantId,
attempt: OperationAttemptId,
) -> Result<Self, OperationError> {
let datum = dispatch_datum(&operation, &grant, &attempt);
Ok(Self {
id: DispatchId(content_id(&datum)?),
operation,
grant,
attempt,
})
}
pub const fn id(&self) -> &DispatchId {
&self.id
}
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn grant(&self) -> &OperationGrantId {
&self.grant
}
pub const fn attempt(&self) -> &OperationAttemptId {
&self.attempt
}
pub fn canonical_datum(&self) -> Datum {
dispatch_datum(&self.operation, &self.grant, &self.attempt)
}
pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, DISPATCH_TAG, 3)?;
let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
let grant = OperationGrantId(id_from_datum(field(fields, "grant")?)?);
let attempt = OperationAttemptId(id_from_datum(field(fields, "attempt")?)?);
let value = Self::new(operation, grant, attempt)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("operation dispatch"));
}
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct PerformerReceiptId(pub(crate) ContentId);
impl PerformerReceiptId {
pub const fn content_id(&self) -> &ContentId {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PerformerReceipt {
pub(crate) id: PerformerReceiptId,
pub(crate) dispatch: DispatchId,
pub(crate) raw: Datum,
}
impl PerformerReceipt {
pub(crate) fn new(dispatch: DispatchId, raw: Datum) -> Result<Self, OperationError> {
let datum = receipt_datum(&dispatch, &raw);
Ok(Self {
id: PerformerReceiptId(content_id(&datum)?),
dispatch,
raw,
})
}
pub const fn id(&self) -> &PerformerReceiptId {
&self.id
}
pub const fn dispatch(&self) -> &DispatchId {
&self.dispatch
}
pub const fn raw(&self) -> &Datum {
&self.raw
}
pub fn canonical_datum(&self) -> Datum {
receipt_datum(&self.dispatch, &self.raw)
}
pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, RECEIPT_TAG, 2)?;
let dispatch = DispatchId(id_from_datum(field(fields, "dispatch")?)?);
let raw = field(fields, "raw")?.clone();
let value = Self::new(dispatch, raw)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("performer receipt"));
}
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DurableOperationState {
IntentPersisted {
intent: OperationIntentId,
},
Dispatched {
intent: OperationIntentId,
dispatch: DispatchId,
},
ReceiptPersisted {
dispatch: DispatchId,
receipt: PerformerReceiptId,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PerformerResponse {
Receipt(Datum),
AcknowledgementMissing,
}
pub trait OperationPerformer {
fn perform(&mut self, dispatch: &OperationDispatch) -> PerformerResponse;
}