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