1use super::document::validate_timeout;
2use super::enum_validators::*;
3use super::one_of_validators::{validate_backoff_one_of, validate_process_type_one_of};
4use super::{
5 is_valid_hostname, validate_required_hostname, validate_required_semver, ValidationResult,
6 ValidationRule,
7};
8use crate::models::map::Map;
9use crate::models::retry::OneOfRetryPolicyDefinitionOrReference;
10use crate::models::task::*;
11
12pub fn validate_task_map(
14 tasks: &Map<String, TaskDefinition>,
15 prefix: &str,
16 result: &mut ValidationResult,
17) {
18 for (name, task) in &tasks.entries {
19 validate_task(task, &format!("{}.{}", prefix, name), result);
20 }
21}
22
23pub(crate) fn validate_task(task: &TaskDefinition, prefix: &str, result: &mut ValidationResult) {
25 match task {
26 TaskDefinition::Call(call) => validate_call_task(call, prefix, result),
27 TaskDefinition::Do(do_task) => {
28 validate_common_fields(&do_task.common, prefix, result);
29 validate_task_map(&do_task.do_, &format!("{}.do", prefix), result);
30 }
31 TaskDefinition::Emit(emit) => {
32 validate_common_fields(&emit.common, prefix, result);
33 }
34 TaskDefinition::For(for_task) => {
35 validate_common_fields(&for_task.common, prefix, result);
36 if for_task.for_.each.is_empty() {
37 result.add_error(
38 &format!("{}.for.each", prefix),
39 ValidationRule::Required,
40 "'each' is required in for loop",
41 );
42 }
43 if for_task.for_.in_.is_empty() {
44 result.add_error(
45 &format!("{}.for.in", prefix),
46 ValidationRule::Required,
47 "'in' is required in for loop",
48 );
49 }
50 validate_task_map(&for_task.do_, &format!("{}.do", prefix), result);
51 }
52 TaskDefinition::Fork(fork) => {
53 validate_common_fields(&fork.common, prefix, result);
54 validate_task_map(
55 &fork.fork.branches,
56 &format!("{}.fork.branches", prefix),
57 result,
58 );
59 }
60 TaskDefinition::Listen(listen) => {
61 validate_common_fields(&listen.common, prefix, result);
62 }
63 TaskDefinition::Raise(raise) => {
64 validate_common_fields(&raise.common, prefix, result);
65 }
66 TaskDefinition::Run(run) => {
67 validate_common_fields(&run.common, prefix, result);
68 validate_run_task(&run.run, prefix, result);
69 }
70 TaskDefinition::Set(set) => {
71 validate_common_fields(&set.common, prefix, result);
72 validate_set_task(&set.set, prefix, result);
73 }
74 TaskDefinition::Switch(switch) => {
75 validate_common_fields(&switch.common, prefix, result);
76 validate_switch_task(switch, prefix, result);
77 }
78 TaskDefinition::Try(try_task) => {
79 validate_common_fields(&try_task.common, prefix, result);
80 validate_task_map(&try_task.try_, &format!("{}.try", prefix), result);
81 if let Some(OneOfRetryPolicyDefinitionOrReference::Retry(ref policy)) =
83 try_task.catch.retry
84 {
85 if let Some(ref backoff) = policy.backoff {
86 validate_backoff_one_of(
87 backoff,
88 &format!("{}.catch.retry.backoff", prefix),
89 result,
90 );
91 }
92 }
93 }
94 TaskDefinition::Wait(wait) => {
95 validate_common_fields(&wait.common, prefix, result);
96 }
97 TaskDefinition::Custom(custom) => {
98 validate_common_fields(&custom.common, prefix, result);
99 }
100 }
101}
102
103pub(crate) fn validate_run_task(
105 run: &ProcessTypeDefinition,
106 prefix: &str,
107 result: &mut ValidationResult,
108) {
109 validate_process_type_one_of(run, prefix, result);
111
112 if let Some(ref container) = run.container {
113 if let Some(ref pull_policy) = container.pull_policy {
114 validate_pull_policy(pull_policy, prefix, result);
115 }
116 if let Some(ref lifetime) = container.lifetime {
117 validate_container_lifetime(lifetime, prefix, result);
118 }
119 }
120 if let Some(ref script) = run.script {
121 validate_script_language(&script.language, prefix, result);
122 }
123 if let Some(ref workflow) = run.workflow {
124 validate_workflow_process(workflow, prefix, result);
125 }
126}
127
128pub(crate) fn validate_call_task(
130 call: &CallTaskDefinition,
131 prefix: &str,
132 result: &mut ValidationResult,
133) {
134 validate_common_fields(call.common_fields(), prefix, result);
135 match call {
136 CallTaskDefinition::HTTP(http) => {
137 if http.with.method.is_empty() {
138 result.add_error(
139 &format!("{}.with.method", prefix),
140 ValidationRule::Required,
141 "HTTP method is required",
142 );
143 } else {
144 validate_http_method(&http.with.method, prefix, result);
145 }
146 if let Some(ref output) = http.with.output {
147 validate_http_output(output, &format!("{}.with", prefix), result);
148 }
149 }
150 CallTaskDefinition::GRPC(grpc) => {
151 if grpc.with.method.is_empty() {
152 result.add_error(
153 &format!("{}.with.method", prefix),
154 ValidationRule::Required,
155 "GRPC method is required",
156 );
157 }
158 if grpc.with.service.name.is_empty() {
159 result.add_error(
160 &format!("{}.with.service.name", prefix),
161 ValidationRule::Required,
162 "GRPC service name is required",
163 );
164 }
165 if grpc.with.service.host.is_empty() {
167 result.add_error(
168 &format!("{}.with.service.host", prefix),
169 ValidationRule::Required,
170 "GRPC service host is required",
171 );
172 } else if !is_valid_hostname(&grpc.with.service.host) {
173 result.add_error(
174 &format!("{}.with.service.host", prefix),
175 ValidationRule::Hostname,
176 "GRPC service host must be a valid RFC 1123 hostname",
177 );
178 }
179 }
180 CallTaskDefinition::OpenAPI(openapi) => {
181 if openapi.with.operation_id.is_empty() {
182 result.add_error(
183 &format!("{}.with.operationId", prefix),
184 ValidationRule::Required,
185 "OpenAPI operationId is required",
186 );
187 }
188 if let Some(ref output) = openapi.with.output {
189 validate_http_output(output, &format!("{}.with", prefix), result);
190 }
191 }
192 CallTaskDefinition::AsyncAPI(asyncapi) => {
193 if let Some(ref protocol) = asyncapi.with.protocol {
194 validate_asyncapi_protocol(protocol, &format!("{}.with", prefix), result);
195 }
196 }
197 CallTaskDefinition::A2A(a2a) => {
198 if a2a.with.method.is_empty() {
199 result.add_error(
200 &format!("{}.with.method", prefix),
201 ValidationRule::Required,
202 "A2A method is required",
203 );
204 }
205 }
206 CallTaskDefinition::Function(func) => {
207 if func.call.is_empty() {
208 result.add_error(
209 &format!("{}.call", prefix),
210 ValidationRule::Required,
211 "function name is required",
212 );
213 }
214 }
215 }
216}
217
218pub(crate) fn validate_common_fields(
220 fields: &TaskDefinitionFields,
221 prefix: &str,
222 result: &mut ValidationResult,
223) {
224 if let Some(ref timeout) = fields.timeout {
225 validate_timeout(timeout, &format!("{}.timeout", prefix), result);
226 }
227}
228
229pub fn validate_set_task(set: &SetValue, prefix: &str, result: &mut ValidationResult) {
233 match set {
234 SetValue::Map(map) => {
235 if map.is_empty() {
236 result.add_error(
237 &format!("{}.set", prefix),
238 ValidationRule::Required,
239 "set task must have at least one key-value pair",
240 );
241 }
242 }
243 SetValue::Expression(expr) => {
244 if expr.is_empty() {
245 result.add_error(
246 &format!("{}.set", prefix),
247 ValidationRule::Required,
248 "set task expression must not be empty",
249 );
250 }
251 }
252 }
253}
254
255pub fn validate_workflow_process(
261 workflow: &WorkflowProcessDefinition,
262 prefix: &str,
263 result: &mut ValidationResult,
264) {
265 let p = &format!("{}.run.workflow", prefix);
266 validate_required_hostname(&workflow.namespace, &format!("{}.namespace", p), result);
267 validate_required_hostname(&workflow.name, &format!("{}.name", p), result);
268 validate_required_semver(&workflow.version, &format!("{}.version", p), result);
269}
270
271pub fn validate_switch_task(
277 switch: &SwitchTaskDefinition,
278 prefix: &str,
279 result: &mut ValidationResult,
280) {
281 if switch.switch.is_empty() {
283 result.add_error(
284 &format!("{}.switch", prefix),
285 ValidationRule::Required,
286 "switch task must have at least one case",
287 );
288 }
289
290 for (i, (name, case_def)) in switch.switch.entries.iter().enumerate() {
292 let case_prefix = format!("{}.switch[{}]", prefix, i);
293 if case_def.then.is_none() {
295 result.add_error(
296 &format!("{}.{}.then", case_prefix, name),
297 ValidationRule::Required,
298 "switch case 'then' is required",
299 );
300 }
301 }
302}