use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
use super::{NodeId, WorkflowError, WorkflowResult};
const SCOPE_SEPARATOR: char = '.';
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub(crate) struct ScopeInstanceId(u64);
impl ScopeInstanceId {
pub(crate) const ROOT: Self = Self::new(0);
pub(crate) const fn new(ordinal: u64) -> Self {
Self(ordinal)
}
}
impl fmt::Display for ScopeInstanceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "s{}", self.0)
}
}
impl FromStr for ScopeInstanceId {
type Err = WorkflowError;
fn from_str(value: &str) -> WorkflowResult<Self> {
let invalid = || WorkflowError::InvalidId {
kind: "scope instance ID",
value: value.to_owned(),
grammar: "s followed by a canonical unsigned decimal u64",
};
let ordinal = value
.strip_prefix('s')
.ok_or_else(invalid)?
.parse::<u64>()
.map_err(|_| invalid())?;
let id = Self(ordinal);
if id.to_string() != value {
return Err(invalid());
}
Ok(id)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub(crate) struct TaskInstanceId {
scope: ScopeInstanceId,
definition: NodeId,
}
impl TaskInstanceId {
pub(crate) fn new(scope: ScopeInstanceId, definition: NodeId) -> Self {
Self { scope, definition }
}
pub(crate) fn root(definition: NodeId) -> Self {
Self::new(ScopeInstanceId::ROOT, definition)
}
pub(crate) fn scope(&self) -> ScopeInstanceId {
self.scope
}
pub(crate) fn definition(&self) -> &NodeId {
&self.definition
}
}
impl fmt::Display for TaskInstanceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.scope == ScopeInstanceId::ROOT {
f.write_str(self.definition.as_str())
} else {
write!(
f,
"{}{SCOPE_SEPARATOR}{}",
self.scope,
self.definition.as_str()
)
}
}
}
impl FromStr for TaskInstanceId {
type Err = WorkflowError;
fn from_str(value: &str) -> WorkflowResult<Self> {
let Some((scope, definition)) = value.split_once(SCOPE_SEPARATOR) else {
return Ok(Self::root(NodeId::new(value)?));
};
let scope: ScopeInstanceId = scope.parse()?;
if scope == ScopeInstanceId::ROOT {
return Err(WorkflowError::InvalidId {
kind: "task instance ID",
value: value.to_owned(),
grammar: "a bare node ID for root tasks, or s<ordinal>.<node ID> for other scopes",
});
}
Ok(Self::new(scope, NodeId::new(definition)?))
}
}
macro_rules! string_identity {
($id:ty) => {
impl TryFrom<String> for $id {
type Error = WorkflowError;
fn try_from(value: String) -> WorkflowResult<Self> {
value.parse()
}
}
impl From<$id> for String {
fn from(value: $id) -> Self {
value.to_string()
}
}
};
}
string_identity!(ScopeInstanceId);
string_identity!(TaskInstanceId);