Skip to main content

nps_nop/
models.rs

1// Copyright 2026 INNO LOTUS PTY LTD
2// SPDX-License-Identifier: Apache-2.0
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7// ── compensation_policy constants ────────────────────────────────────────────
8
9pub mod compensation_policy {
10    pub const NONE:       &str = "none";
11    pub const ON_FAILURE: &str = "on_failure";
12    pub const ALWAYS:     &str = "always";
13}
14
15// ── aggregate_strategy constants ──────────────────────────────────────────────
16
17pub mod aggregate_strategy {
18    pub const MERGE:            &str = "merge";
19    pub const WEIGHTED_FIRST_K: &str = "weighted_first_k";
20    pub const MERGE_ALL:        &str = "merge_all";
21}
22
23// ── DagNode ───────────────────────────────────────────────────────────────────
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct DagNode {
27    pub id:     String,
28    pub action: String,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub target_nid:               Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub inputs:                   Option<serde_json::Map<String, Value>>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub depends_on:               Option<Vec<String>>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub compensate_action:        Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub compensate_params_mapping: Option<serde_json::Map<String, Value>>,
39}
40
41// ── BackoffStrategy ───────────────────────────────────────────────────────────
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BackoffStrategy {
45    Fixed,
46    Linear,
47    Exponential,
48}
49
50impl BackoffStrategy {
51    /// Compute retry delay in milliseconds.
52    pub fn compute_delay_ms(self, base_ms: u64, max_ms: u64, attempt: u32) -> u64 {
53        let raw = match self {
54            BackoffStrategy::Fixed => base_ms,
55            BackoffStrategy::Linear => base_ms * (attempt as u64 + 1),
56            BackoffStrategy::Exponential => base_ms * (1u64 << attempt),
57        };
58        raw.min(max_ms)
59    }
60}
61
62// ── TaskState ─────────────────────────────────────────────────────────────────
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum TaskState {
66    Pending,
67    Running,
68    Completed,
69    Failed,
70    Cancelled,
71    Compensating,
72    Compensated,
73}
74
75impl TaskState {
76    pub fn from_str(s: &str) -> Option<Self> {
77        match s {
78            "pending"      => Some(TaskState::Pending),
79            "running"      => Some(TaskState::Running),
80            "completed"    => Some(TaskState::Completed),
81            "failed"       => Some(TaskState::Failed),
82            "cancelled"    => Some(TaskState::Cancelled),
83            "compensating" => Some(TaskState::Compensating),
84            "compensated"  => Some(TaskState::Compensated),
85            _              => None,
86        }
87    }
88
89    pub fn is_terminal(self) -> bool {
90        matches!(self, TaskState::Completed | TaskState::Failed | TaskState::Cancelled | TaskState::Compensated)
91    }
92}
93
94// ── NopTaskStatus ─────────────────────────────────────────────────────────────
95
96#[derive(Debug, Clone)]
97pub struct NopTaskStatus {
98    raw: serde_json::Map<String, Value>,
99}
100
101impl NopTaskStatus {
102    pub fn from_dict(raw: serde_json::Map<String, Value>) -> Self {
103        NopTaskStatus { raw }
104    }
105
106    pub fn task_id(&self) -> &str {
107        self.raw
108            .get("task_id")
109            .and_then(Value::as_str)
110            .unwrap_or("")
111    }
112
113    pub fn state(&self) -> Option<TaskState> {
114        self.raw
115            .get("state")
116            .and_then(Value::as_str)
117            .and_then(TaskState::from_str)
118    }
119
120    pub fn is_terminal(&self) -> bool {
121        self.state().map(TaskState::is_terminal).unwrap_or(false)
122    }
123
124    pub fn error_code(&self) -> Option<&str> {
125        self.raw.get("error_code").and_then(Value::as_str)
126    }
127
128    pub fn error_message(&self) -> Option<&str> {
129        self.raw.get("error_message").and_then(Value::as_str)
130    }
131
132    pub fn node_results(&self) -> Option<&serde_json::Map<String, Value>> {
133        self.raw.get("node_results").and_then(Value::as_object)
134    }
135
136    pub fn raw(&self) -> &serde_json::Map<String, Value> {
137        &self.raw
138    }
139}
140
141impl std::fmt::Display for NopTaskStatus {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(
144            f,
145            "NopTaskStatus(task_id={}, state={:?})",
146            self.task_id(),
147            self.state()
148        )
149    }
150}