apimock_config/workspace/
validate.rs1use std::path::{Path, PathBuf};
19
20use apimock_routing::RuleSet;
21
22use crate::view::{
23 Diagnostic, NodeValidation, Severity, ValidationIssue, ValidationReport,
24};
25
26use super::Workspace;
27use super::id_index::NodeAddress;
28
29impl Workspace {
30 pub(super) fn collect_diagnostics(&self) -> Vec<Diagnostic> {
34 let mut out: Vec<Diagnostic> = Vec::new();
35 for (rs_idx, rule_set) in self.config.service.rule_sets.iter().enumerate() {
36 for (rule_idx, rule) in rule_set.rules.iter().enumerate() {
37 let nv = respond_node_validation(&rule.respond, rule_set, rule_idx, rs_idx);
38 if nv.ok {
39 continue;
40 }
41 let resp_id = self.ids.id_for(NodeAddress::Respond {
42 rule_set: rs_idx,
43 rule: rule_idx,
44 });
45 for issue in nv.issues {
46 out.push(Diagnostic {
47 node_id: resp_id,
48 file: Some(PathBuf::from(rule_set.file_path.as_str())),
49 severity: issue.severity,
50 message: issue.message,
51 });
52 }
53 }
54 }
55
56 if !Path::new(self.config.service.fallback_respond_dir.as_str()).exists() {
58 out.push(Diagnostic {
59 node_id: self.ids.id_for(NodeAddress::FallbackRespondDir),
60 file: Some(self.root_path.clone()),
61 severity: Severity::Error,
62 message: format!(
63 "fallback_respond_dir does not exist: {}",
64 self.config.service.fallback_respond_dir
65 ),
66 });
67 }
68
69 out
70 }
71
72 pub fn validate(&self) -> ValidationReport {
80 let diagnostics = self.collect_diagnostics();
81 let is_valid = !diagnostics
82 .iter()
83 .any(|d| matches!(d.severity, Severity::Error));
84 ValidationReport {
85 diagnostics,
86 is_valid,
87 }
88 }
89}
90
91pub(super) fn respond_node_validation(
93 respond: &apimock_routing::Respond,
94 rule_set: &RuleSet,
95 rule_idx: usize,
96 rs_idx: usize,
97) -> NodeValidation {
98 let mut issues: Vec<ValidationIssue> = Vec::new();
102
103 let any = respond.file_path.is_some() || respond.text.is_some() || respond.status.is_some();
104 if !any {
105 issues.push(ValidationIssue {
106 severity: Severity::Error,
107 message: "response requires at least one of file_path, text, or status".to_owned(),
108 });
109 }
110 if respond.file_path.is_some() && respond.text.is_some() {
111 issues.push(ValidationIssue {
112 severity: Severity::Error,
113 message: "file_path and text cannot both be set".to_owned(),
114 });
115 }
116 if respond.file_path.is_some() && respond.status.is_some() {
117 issues.push(ValidationIssue {
118 severity: Severity::Error,
119 message: "status cannot be combined with file_path (only with text)".to_owned(),
120 });
121 }
122
123 if let Some(file_path) = respond.file_path.as_ref() {
128 let dir_prefix = rule_set.dir_prefix();
129 let p = Path::new(dir_prefix.as_str()).join(file_path);
130 if !p.exists() {
131 issues.push(ValidationIssue {
132 severity: Severity::Error,
133 message: format!(
134 "file not found: {} (rule #{} in rule set #{})",
135 p.to_string_lossy(),
136 rule_idx + 1,
137 rs_idx + 1,
138 ),
139 });
140 }
141 }
142
143 NodeValidation {
144 ok: issues.is_empty(),
145 issues,
146 }
147}