turnframe_core/plan/
limits.rs1use serde::{Deserialize, Serialize};
8
9use crate::understanding::Understanding;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum PlanLimitKind {
15 Acts,
17 Questions,
19 Constraints,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
25#[error("plan exceeds {kind:?} limit: {actual} > {limit}")]
26pub struct PlanLimitError {
27 pub kind: PlanLimitKind,
29 pub limit: usize,
31 pub actual: usize,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
37#[non_exhaustive]
38pub struct PlanLimits {
39 pub max_acts: Option<usize>,
41 pub max_questions: Option<usize>,
43 pub max_constraints: Option<usize>,
45}
46
47impl PlanLimits {
48 #[must_use]
50 pub const fn conservative() -> Self {
51 Self {
52 max_acts: None,
53 max_questions: None,
54 max_constraints: None,
55 }
56 }
57
58 #[must_use]
60 pub const fn with_max_acts(mut self, max_acts: Option<usize>) -> Self {
61 self.max_acts = max_acts;
62 self
63 }
64
65 #[must_use]
67 pub const fn with_max_questions(mut self, max_questions: Option<usize>) -> Self {
68 self.max_questions = max_questions;
69 self
70 }
71
72 #[must_use]
74 pub const fn with_max_constraints(mut self, max_constraints: Option<usize>) -> Self {
75 self.max_constraints = max_constraints;
76 self
77 }
78
79 pub fn enforce(&self, understanding: &Understanding) -> Result<(), PlanLimitError> {
85 check(PlanLimitKind::Acts, self.max_acts, understanding.acts.len())?;
86 check(
87 PlanLimitKind::Questions,
88 self.max_questions,
89 understanding.questions.len(),
90 )?;
91 check(
92 PlanLimitKind::Constraints,
93 self.max_constraints,
94 understanding.constraints.len(),
95 )
96 }
97}
98
99fn check(kind: PlanLimitKind, limit: Option<usize>, actual: usize) -> Result<(), PlanLimitError> {
100 match limit {
101 Some(limit) if actual > limit => Err(PlanLimitError {
102 kind,
103 limit,
104 actual,
105 }),
106 _ => Ok(()),
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::understanding::{
114 ActAction, ActId, ActStatus, ActTarget, UnderstoodAct, UnitId, WordRange,
115 };
116
117 fn act(unit: u16) -> UnderstoodAct {
118 UnderstoodAct {
119 id: ActId::new(UnitId(unit), 1),
120 action: ActAction::Start {
121 workflow: "w".into(),
122 },
123 target: ActTarget::New {
124 workflow: "w".into(),
125 },
126 arguments: std::collections::BTreeMap::new(),
127 words: WordRange {
128 first: 0,
129 last: 0,
130 start: 0,
131 end: 1,
132 },
133 depends_on: Vec::new(),
134 status: ActStatus::Ready,
135 }
136 }
137
138 #[test]
139 fn limits_are_enforced_with_the_count_and_the_bound() {
140 let limits = PlanLimits::default().with_max_acts(Some(1));
141 let understanding = Understanding {
142 acts: vec![act(1), act(2)],
143 ..Understanding::default()
144 };
145 let error = limits.enforce(&understanding).unwrap_err();
146 assert_eq!(error.kind, PlanLimitKind::Acts);
147 assert_eq!((error.limit, error.actual), (1, 2));
148 assert!(PlanLimits::default().enforce(&understanding).is_ok());
149 }
150}