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