swf_core/validation/
mod.rs1use crate::models::expression::is_strict_expr;
2use regex::Regex;
3use std::sync::LazyLock;
4
5mod authentication;
6mod document;
7mod enum_validators;
8mod one_of_validators;
9mod task;
10#[cfg(test)]
11mod tests;
12
13pub use authentication::{
15 validate_auth_policy, validate_basic_auth, validate_bearer_auth, validate_digest_auth,
16 validate_oauth2_auth, validate_oidc_auth,
17};
18pub use document::validate_workflow;
19pub use enum_validators::{
20 validate_asyncapi_protocol, validate_container_cleanup, validate_container_lifetime,
21 validate_extension_task_type, validate_http_method, validate_http_output,
22 validate_oauth2_client_auth_method, validate_oauth2_grant_type,
23 validate_oauth2_request_encoding, validate_pull_policy, validate_script_language,
24};
25pub use one_of_validators::{
26 validate_auth_policy_one_of, validate_backoff_one_of, validate_process_type_one_of,
27 validate_schedule_one_of, validate_schema_one_of,
28};
29pub use task::{
30 validate_set_task, validate_switch_task, validate_task_map, validate_workflow_process,
31};
32
33#[derive(Debug, Clone, PartialEq)]
35pub struct ValidationError {
36 pub field: String,
38 pub rule: ValidationRule,
40 pub message: String,
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub enum ValidationRule {
47 Required,
48 Semver,
49 Hostname,
50 Uri,
51 Iso8601Duration,
52 MutualExclusion,
53 InvalidValue,
54 Custom(String),
55}
56
57#[derive(Debug, Clone, PartialEq)]
59pub struct ValidationResult {
60 pub errors: Vec<ValidationError>,
62}
63
64impl ValidationResult {
65 pub fn new() -> Self {
67 Self { errors: Vec::new() }
68 }
69
70 pub fn add_error(&mut self, field: &str, rule: ValidationRule, message: &str) {
72 self.errors.push(ValidationError {
73 field: field.to_string(),
74 rule,
75 message: message.to_string(),
76 });
77 }
78
79 pub fn is_valid(&self) -> bool {
81 self.errors.is_empty()
82 }
83
84 pub fn merge_with_prefix(&mut self, prefix: &str, other: ValidationResult) {
86 for error in other.errors {
87 self.errors.push(ValidationError {
88 field: format!("{}.{}", prefix, error.field),
89 rule: error.rule,
90 message: error.message,
91 });
92 }
93 }
94}
95
96impl Default for ValidationResult {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102static SEMVER_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
103 Regex::new(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$").expect("static semver regex is valid")
104});
105
106static HOSTNAME_RFC1123_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
107 Regex::new(r"^(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z]{2,63}|[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$").expect("static hostname regex is valid")
108});
109
110static URI_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
112 Regex::new(r"^[A-Za-z][A-Za-z0-9+\-.]*://[^{}\s]+$").expect("static URI regex is valid")
113});
114
115static URI_TEMPLATE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
116 Regex::new(r"^[A-Za-z][A-Za-z0-9+\-.]*://.*\{.*}.*$")
117 .expect("static URI template regex is valid")
118});
119
120static JSON_POINTER_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
122 Regex::new(r"^(/([^/~]|~[01])*)*$").expect("static JSON pointer regex is valid")
123});
124
125pub fn is_valid_semver(version: &str) -> bool {
127 SEMVER_PATTERN.is_match(version)
128}
129
130pub fn is_valid_hostname(hostname: &str) -> bool {
132 HOSTNAME_RFC1123_PATTERN.is_match(hostname)
133}
134
135pub fn validate_required_hostname(value: &str, field: &str, result: &mut ValidationResult) {
138 if value.is_empty() {
139 result.add_error(
140 field,
141 ValidationRule::Required,
142 &format!("{} is required", field),
143 );
144 } else if !is_valid_hostname(value) {
145 result.add_error(
146 field,
147 ValidationRule::Hostname,
148 &format!("{} must be a valid RFC 1123 hostname", field),
149 );
150 }
151}
152
153pub fn validate_required_semver(value: &str, field: &str, result: &mut ValidationResult) {
156 if value.is_empty() {
157 result.add_error(
158 field,
159 ValidationRule::Required,
160 &format!("{} is required", field),
161 );
162 } else if !is_valid_semver(value) {
163 result.add_error(
164 field,
165 ValidationRule::Semver,
166 &format!("{} must be a valid semantic version", field),
167 );
168 }
169}
170
171pub fn is_non_empty_string(value: &str) -> bool {
175 !value.is_empty()
176}
177
178pub fn is_uri_or_runtime_expr(value: &str) -> bool {
182 if value.is_empty() {
183 return false;
184 }
185 if is_strict_expr(value) {
187 return true;
188 }
189 URI_PATTERN.is_match(value) || URI_TEMPLATE_PATTERN.is_match(value)
191}
192
193pub fn is_json_pointer_or_runtime_expr(value: &str) -> bool {
198 if value.is_empty() {
199 return false;
200 }
201 if is_strict_expr(value) {
203 return true;
204 }
205 JSON_POINTER_PATTERN.is_match(value)
207}
208
209pub fn is_valid_uri(value: &str) -> bool {
212 URI_PATTERN.is_match(value)
213}
214
215pub fn is_valid_uri_template(value: &str) -> bool {
218 URI_TEMPLATE_PATTERN.is_match(value)
219}
220
221pub fn is_valid_json_pointer(value: &str) -> bool {
224 JSON_POINTER_PATTERN.is_match(value)
225}