use serde::{Deserialize, Serialize};
use turnframe_core::case::CaseRef;
use turnframe_core::flow::{
InteractionRequirement, PhaseOwnership, WorkflowDefinition, WorkflowView,
};
use turnframe_core::ids::{CaseRevision, WorkflowKey, WorkflowVersion};
use turnframe_core::interaction::{InteractionKind, TextResolutionPolicy};
use turnframe_core::response::NoticeSeverity;
const CASE_ID: &str = "fingerprint";
const REVISION: CaseRevision = CaseRevision(1);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ProjectedShape {
pub state: String,
pub phase: serde_json::Value,
pub ownership: PhaseOwnership,
pub obligations: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blocking: Option<RequiredCard>,
pub notices: Vec<NoticeShape>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RequiredCard {
pub key: String,
pub kind: InteractionKind,
pub blocking: bool,
pub revision_independent: bool,
pub text_resolution: TextResolutionPolicy,
pub confirms_risk: turnframe_core::command::RiskClass,
}
impl From<&InteractionRequirement> for RequiredCard {
fn from(requirement: &InteractionRequirement) -> Self {
Self {
key: requirement.key.clone(),
kind: requirement.kind,
blocking: requirement.blocking,
revision_independent: requirement.revision_independent,
text_resolution: requirement.text_resolution.clone(),
confirms_risk: requirement.confirms_risk,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct NoticeShape {
pub code: String,
pub severity: NoticeSeverity,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ProjectionFingerprint {
pub workflow: WorkflowKey,
pub version: WorkflowVersion,
pub states: Vec<ProjectedShape>,
}
impl ProjectionFingerprint {
#[must_use]
pub fn of<W: WorkflowDefinition>(definition: &W) -> FingerprintBuilder<'_, W> {
FingerprintBuilder {
definition,
states: Vec::new(),
}
}
#[must_use]
pub fn snapshot_name(&self) -> String {
format!("{}@{}", self.workflow.as_str(), self.version.as_str())
}
}
#[derive(Debug)]
pub struct FingerprintBuilder<'a, W: WorkflowDefinition> {
definition: &'a W,
states: Vec<(String, Option<W::State>)>,
}
impl<'a, W: WorkflowDefinition> FingerprintBuilder<'a, W> {
#[must_use]
pub fn at(mut self, name: impl Into<String>, state: Option<W::State>) -> Self {
self.states.push((name.into(), state));
self
}
#[must_use]
pub fn build(self) -> ProjectionFingerprint {
let case_ref = CaseRef::new(self.definition.key(), CASE_ID, REVISION);
let states = self
.states
.iter()
.map(|(name, state)| {
let view = self.definition.project(case_ref.clone(), state.as_ref());
shape_of(name, self.definition, &view)
})
.collect();
ProjectionFingerprint {
workflow: self.definition.key(),
version: self.definition.version(),
states,
}
}
}
fn shape_of<W: WorkflowDefinition>(
name: &str,
definition: &W,
view: &WorkflowView<W::Phase, W::Obligation, W::Outcome>,
) -> ProjectedShape {
ProjectedShape {
state: name.to_owned(),
phase: serde_json::to_value(&view.phase).unwrap_or(serde_json::Value::Null),
ownership: definition.phase_ownership(&view.phase),
obligations: view
.obligations
.iter()
.map(|obligation| {
serde_json::to_string(obligation)
.unwrap_or_else(|_| String::from("<unserializable>"))
})
.collect(),
blocking: view.blocking_interaction.as_ref().map(RequiredCard::from),
notices: view
.notices
.iter()
.map(|notice| NoticeShape {
code: notice.code.clone(),
severity: notice.severity,
})
.collect(),
outcome: view
.outcome
.as_ref()
.map(|outcome| serde_json::to_value(outcome).unwrap_or(serde_json::Value::Null)),
}
}