libbrat_workflow/
schema.rs1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct WorkflowTemplate {
10 pub name: String,
12
13 #[serde(default = "default_version")]
15 pub version: u32,
16
17 #[serde(default)]
19 pub description: Option<String>,
20
21 #[serde(rename = "type")]
23 pub workflow_type: WorkflowType,
24
25 #[serde(default)]
27 pub inputs: HashMap<String, InputSpec>,
28
29 #[serde(default)]
31 pub steps: Vec<StepSpec>,
32
33 #[serde(default)]
35 pub legs: Vec<LegSpec>,
36
37 #[serde(default)]
39 pub synthesis: Option<SynthesisSpec>,
40}
41
42fn default_version() -> u32 {
43 1
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "lowercase")]
49pub enum WorkflowType {
50 Workflow,
52 Convoy,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct InputSpec {
59 #[serde(default)]
61 pub description: Option<String>,
62
63 #[serde(default)]
65 pub required: bool,
66
67 #[serde(default)]
69 pub default: Option<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct StepSpec {
75 pub id: String,
77
78 pub title: String,
80
81 #[serde(default)]
83 pub body: String,
84
85 #[serde(default)]
87 pub needs: Vec<String>,
88
89 #[serde(default)]
91 pub labels: Vec<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct LegSpec {
97 pub id: String,
99
100 pub title: String,
102
103 #[serde(default)]
105 pub body: String,
106
107 #[serde(default)]
109 pub labels: Vec<String>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct SynthesisSpec {
115 pub title: String,
117
118 #[serde(default)]
120 pub body: String,
121
122 #[serde(default)]
124 pub depends_on: Vec<String>,
125
126 #[serde(default)]
128 pub labels: Vec<String>,
129}
130
131impl WorkflowTemplate {
132 pub fn validate(&self) -> Result<(), String> {
134 match self.workflow_type {
136 WorkflowType::Workflow => {
137 if self.steps.is_empty() {
138 return Err("workflow type requires at least one step".to_string());
139 }
140 if !self.legs.is_empty() {
141 return Err("workflow type should not have legs".to_string());
142 }
143 }
144 WorkflowType::Convoy => {
145 if self.legs.is_empty() {
146 return Err("convoy type requires at least one leg".to_string());
147 }
148 if !self.steps.is_empty() {
149 return Err("convoy type should not have steps".to_string());
150 }
151 }
152 }
153
154 let mut ids = std::collections::HashSet::new();
156 for step in &self.steps {
157 if !ids.insert(&step.id) {
158 return Err(format!("duplicate step id: {}", step.id));
159 }
160 }
161 for leg in &self.legs {
162 if !ids.insert(&leg.id) {
163 return Err(format!("duplicate leg id: {}", leg.id));
164 }
165 }
166
167 for step in &self.steps {
169 for dep in &step.needs {
170 if !self.steps.iter().any(|s| &s.id == dep) {
171 return Err(format!(
172 "step '{}' depends on unknown step '{}'",
173 step.id, dep
174 ));
175 }
176 if dep == &step.id {
177 return Err(format!("step '{}' cannot depend on itself", step.id));
178 }
179 }
180 }
181
182 if let Some(ref synthesis) = self.synthesis {
184 for dep in &synthesis.depends_on {
185 if !self.legs.iter().any(|l| &l.id == dep) {
186 return Err(format!(
187 "synthesis depends on unknown leg '{}'",
188 dep
189 ));
190 }
191 }
192 }
193
194 Ok(())
195 }
196}