1use crate::conditions::{evaluate_condition, ConditionContext};
7use crate::templating::expand_tokens as core_expand_tokens;
8use serde_json::Value;
9
10pub mod loader;
11pub mod matcher;
12pub mod models;
13pub mod patcher;
14
15pub use matcher::*;
17pub use models::{OverrideMode, OverrideRule, Overrides, PatchOp};
18pub use patcher::*;
19
20impl Overrides {
21 pub fn rules(&self) -> &[OverrideRule] {
23 &self.rules
24 }
25
26 pub fn apply(&self, operation_id: &str, tags: &[String], path: &str, body: &mut Value) {
28 self.apply_with_context(operation_id, tags, path, body, &ConditionContext::new())
29 }
30
31 pub fn apply_with_context(
33 &self,
34 operation_id: &str,
35 tags: &[String],
36 path: &str,
37 body: &mut Value,
38 context: &ConditionContext,
39 ) {
40 for r in &self.rules {
41 if !matcher::matches_target(r, operation_id, tags, path, &self.regex_cache) {
42 continue;
43 }
44
45 if let Some(ref condition) = r.when {
47 match evaluate_condition(condition, context) {
48 Ok(true) => {
49 }
51 Ok(false) => {
52 continue;
54 }
55 Err(e) => {
56 tracing::warn!("Failed to evaluate condition '{}': {}", condition, e);
58 continue;
59 }
60 }
61 }
62
63 match r.mode {
65 OverrideMode::Replace => {
66 for op in &r.patch {
67 let _ = patcher::apply_patch(body, op);
68 }
69 }
70 OverrideMode::Merge => {
71 for op in &r.patch {
72 let _ = patcher::apply_merge_patch(body, op);
73 }
74 }
75 }
76
77 if r.post_templating {
79 *body = core_expand_tokens(body);
80 }
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use serde_json::json;
89
90 #[test]
91 fn test_overrides_apply_basic() {
92 let overrides = Overrides {
93 rules: vec![OverrideRule {
94 targets: vec!["operation:test_op".to_string()],
95 mode: OverrideMode::Replace,
96 patch: vec![PatchOp::Replace {
97 path: "/value".to_string(),
98 value: json!("replaced"),
99 }],
100 when: None,
101 post_templating: false,
102 }],
103 regex_cache: Default::default(),
104 };
105
106 let mut body = json!({"value": "original"});
107 overrides.apply("test_op", &[], "/test", &mut body);
108
109 assert_eq!(body["value"], "replaced");
110 }
111
112 #[test]
113 fn test_overrides_apply_no_match() {
114 let overrides = Overrides {
115 rules: vec![OverrideRule {
116 targets: vec!["operation:other_op".to_string()],
117 mode: OverrideMode::Replace,
118 patch: vec![PatchOp::Replace {
119 path: "/value".to_string(),
120 value: json!("replaced"),
121 }],
122 when: None,
123 post_templating: false,
124 }],
125 regex_cache: Default::default(),
126 };
127
128 let mut body = json!({"value": "original"});
129 overrides.apply("test_op", &[], "/test", &mut body);
130
131 assert_eq!(body["value"], "original");
132 }
133
134 #[test]
135 fn test_overrides_apply_with_tag() {
136 let overrides = Overrides {
137 rules: vec![OverrideRule {
138 targets: vec!["tag:test_tag".to_string()],
139 mode: OverrideMode::Replace,
140 patch: vec![PatchOp::Replace {
141 path: "/status".to_string(),
142 value: json!("tagged"),
143 }],
144 when: None,
145 post_templating: false,
146 }],
147 regex_cache: Default::default(),
148 };
149
150 let mut body = json!({"status": "normal"});
151 overrides.apply("any_op", &["test_tag".to_string()], "/test", &mut body);
152
153 assert_eq!(body["status"], "tagged");
154 }
155
156 #[test]
157 fn test_overrides_apply_merge_mode() {
158 let overrides = Overrides {
159 rules: vec![OverrideRule {
160 targets: vec!["operation:test_op".to_string()],
161 mode: OverrideMode::Merge,
162 patch: vec![PatchOp::Add {
163 path: "/extra".to_string(),
164 value: json!("added"),
165 }],
166 when: None,
167 post_templating: false,
168 }],
169 regex_cache: Default::default(),
170 };
171
172 let mut body = json!({"value": "original"});
173 overrides.apply("test_op", &[], "/test", &mut body);
174
175 assert_eq!(body["value"], "original");
176 assert_eq!(body["extra"], "added");
177 }
178
179 #[test]
180 fn test_overrides_apply_with_context() {
181 let overrides = Overrides {
183 rules: vec![OverrideRule {
184 targets: vec!["operation:test_op".to_string()],
185 mode: OverrideMode::Replace,
186 patch: vec![PatchOp::Replace {
187 path: "/value".to_string(),
188 value: json!("replaced"),
189 }],
190 when: None, post_templating: false,
192 }],
193 regex_cache: Default::default(),
194 };
195
196 let mut body = json!({"value": "original"});
197 let context = ConditionContext::new();
198 overrides.apply_with_context("test_op", &[], "/test", &mut body, &context);
199
200 assert_eq!(body["value"], "replaced");
201 }
202
203 #[test]
204 fn test_overrides_apply_multiple_patches() {
205 let overrides = Overrides {
206 rules: vec![OverrideRule {
207 targets: vec!["operation:test_op".to_string()],
208 mode: OverrideMode::Replace,
209 patch: vec![
210 PatchOp::Add {
211 path: "/field1".to_string(),
212 value: json!("value1"),
213 },
214 PatchOp::Add {
215 path: "/field2".to_string(),
216 value: json!("value2"),
217 },
218 ],
219 when: None,
220 post_templating: false,
221 }],
222 regex_cache: Default::default(),
223 };
224
225 let mut body = json!({"existing": "value"});
226 overrides.apply("test_op", &[], "/test", &mut body);
227
228 assert_eq!(body["field1"], "value1");
229 assert_eq!(body["field2"], "value2");
230 }
231
232 #[test]
233 fn test_overrides_apply_multiple_rules() {
234 let overrides = Overrides {
235 rules: vec![
236 OverrideRule {
237 targets: vec!["operation:test_op".to_string()],
238 mode: OverrideMode::Replace,
239 patch: vec![PatchOp::Add {
240 path: "/first".to_string(),
241 value: json!("first_rule"),
242 }],
243 when: None,
244 post_templating: false,
245 },
246 OverrideRule {
247 targets: vec!["operation:test_op".to_string()],
248 mode: OverrideMode::Replace,
249 patch: vec![PatchOp::Add {
250 path: "/second".to_string(),
251 value: json!("second_rule"),
252 }],
253 when: None,
254 post_templating: false,
255 },
256 ],
257 regex_cache: Default::default(),
258 };
259
260 let mut body = json!({"existing": "value"});
261 overrides.apply("test_op", &[], "/test", &mut body);
262
263 assert_eq!(body["first"], "first_rule");
264 assert_eq!(body["second"], "second_rule");
265 }
266
267 #[test]
268 fn test_overrides_replace_root_object() {
269 let overrides = Overrides {
270 rules: vec![OverrideRule {
271 targets: vec!["operation:testReplace".to_string()],
272 mode: OverrideMode::Replace,
273 patch: vec![PatchOp::Replace {
274 path: "".to_string(),
275 value: json!({"id": "1234", "kind": "replaced"}),
276 }],
277 when: None,
278 post_templating: false,
279 }],
280 regex_cache: Default::default(),
281 };
282
283 let mut body = json!({"existing": true});
284 overrides.apply("testReplace", &[], "/test-replace", &mut body);
285
286 assert_eq!(body["kind"], "replaced");
287 assert_eq!(body["id"], "1234");
288 assert!(body.get("existing").is_none());
289 }
290}