1use crate::core::{DiffResult, EitherDiff, MapDiff, VecDiff};
2use crate::path_pointer::{PathPointer, PathPointerScope};
3use std::cell::RefCell;
4
5use crate::schema_diff::{
6 deref_parameter_diff, deref_request_body_diff, deref_response_diff,
7 deref_schema_diff, HttpSchemaDiff, MayBeRefDiff, MediaTypeDiff,
8 OperationDiff, ParameterDiff, PathDiff, RequestBodyDiff, ResponseDiff,
9 SchemaDiff,
10};
11use crate::schema_diff_utils::PathsMapPathResolver;
12
13#[allow(unused_variables)]
14pub trait DiffVisitor<'s> {
15 fn visit_root(&self) {}
16
17 fn visit_paths(
20 &self,
21 pointer: &PathPointer,
22 paths_diff_result: &'s DiffResult<
23 MapDiff<MayBeRefDiff<PathDiff>, PathsMapPathResolver>,
24 >,
25 ) -> bool {
26 true
27 }
28 fn visit_path(
29 &self,
30 pointer: &PathPointer,
31 path: &str,
32 path_diff_result: &'s DiffResult<PathDiff>,
33 ) -> bool {
34 true
35 }
36
37 fn visit_schema_ref(
38 &self,
39 pointer: &PathPointer,
40 may_be_ref: &'s DiffResult<MayBeRefDiff<SchemaDiff>>,
41 ) -> bool {
42 true
43 }
44
45 fn visit_response_ref(
46 &self,
47 pointer: &PathPointer,
48 may_be_ref: &'s DiffResult<MayBeRefDiff<ResponseDiff>>,
49 ) -> bool {
50 true
51 }
52
53 fn visit_parameter_ref(
54 &self,
55 pointer: &PathPointer,
56 may_be_ref: &'s DiffResult<MayBeRefDiff<ParameterDiff>>,
57 ) -> bool {
58 true
59 }
60
61 fn visit_request_body_ref(
62 &self,
63 pointer: &PathPointer,
64 may_be_ref: &'s DiffResult<MayBeRefDiff<RequestBodyDiff>>,
65 ) -> bool {
66 true
67 }
68
69 fn visit_operation(
72 &self,
73 pointer: &PathPointer,
74 method: &str,
75 operation_diff_result: &'s DiffResult<OperationDiff>,
76 ) -> bool {
77 false
78 }
79
80 fn visit_request_body(
81 &self,
82 pointer: &PathPointer,
83 request_body_diff_result: &'s DiffResult<RequestBodyDiff>,
84 ) -> bool {
85 false
86 }
87
88 fn visit_responses(
89 &self,
90 pointer: &PathPointer,
91 responses_diff_result: &'s DiffResult<
92 MapDiff<MayBeRefDiff<ResponseDiff>>,
93 >,
94 ) -> bool {
95 false
96 }
97
98 fn visit_media_types(
99 &self,
100 pointer: &PathPointer,
101 media_types_diff_result: &'s DiffResult<MapDiff<MediaTypeDiff>>,
102 ) -> bool {
103 false
104 }
105 fn visit_media_type(
106 &self,
107 pointer: &PathPointer,
108 media_type_diff_result: &'s DiffResult<MediaTypeDiff>,
109 ) -> bool {
110 false
111 }
112
113 fn visit_parameters(
114 &self,
115 pointer: &PathPointer,
116 parameters_diff_result: &'s DiffResult<
117 VecDiff<MayBeRefDiff<ParameterDiff>>,
118 >,
119 ) -> bool {
120 false
121 }
122
123 fn visit_parameter(
124 &self,
125 pointer: &PathPointer,
126 parameter_diff_result: &'s DiffResult<ParameterDiff>,
127 ) -> bool {
128 false
129 }
130
131 fn visit_schema(
132 &self,
133 pointer: &PathPointer,
134 schema_diff_result: &'s DiffResult<SchemaDiff>,
135 ) -> bool {
136 false
137 }
138}
139
140pub fn dispatch_visitor<'s, T: DiffVisitor<'s>>(
141 root: &'s HttpSchemaDiff,
142 visitor: &T,
143) {
144 visitor.visit_root();
145
146 let pointer = PathPointer::new(
147 &root.paths,
148 Some("paths"),
149 Some(PathPointerScope::Paths),
150 );
151
152 dispatch_paths(root, &pointer, &root.paths, visitor);
153}
154
155pub fn dispatch_paths<'s, T>(
156 root: &'s HttpSchemaDiff,
157 pointer: &PathPointer,
158 paths_diff_result: &'s DiffResult<
159 MapDiff<MayBeRefDiff<PathDiff>, PathsMapPathResolver>,
160 >,
161 visitor: &T,
162) where
163 T: DiffVisitor<'s>,
164{
165 if !visitor.visit_paths(pointer, paths_diff_result) {
166 return;
167 }
168
169 if let Some(paths) = paths_diff_result.get() {
170 for (path, may_be_path_diff_result) in paths.iter() {
171 let pointer = pointer.add_context(may_be_path_diff_result);
172 if let Some(MayBeRefDiff::Value(path_diff_result)) =
173 may_be_path_diff_result.get()
174 {
175 let pointer = pointer.add(
176 &**path_diff_result,
177 path,
178 Some(PathPointerScope::Path),
179 );
180 dispatch_path(root, &pointer, path, path_diff_result, visitor)
181 }
182 }
183 }
184}
185
186pub fn dispatch_path<'s, T: DiffVisitor<'s>>(
187 root: &'s HttpSchemaDiff,
188 pointer: &PathPointer,
189 path: &'s str,
190 path_diff_result: &'s DiffResult<PathDiff>,
191 visitor: &T,
192) {
193 if !visitor.visit_path(pointer, path, path_diff_result) {
194 return;
195 }
196
197 if let Some(path) = path_diff_result.get() {
198 dispatch_operation(root, pointer, "get", &path.get, visitor);
199 dispatch_operation(root, pointer, "post", &path.post, visitor);
200 dispatch_operation(root, pointer, "put", &path.put, visitor);
201 dispatch_operation(root, pointer, "patch", &path.patch, visitor);
202 dispatch_operation(root, pointer, "delete", &path.delete, visitor);
203 dispatch_operation(root, pointer, "head", &path.head, visitor);
204 dispatch_operation(root, pointer, "options", &path.options, visitor);
205 dispatch_operation(root, pointer, "trace", &path.trace, visitor);
206 }
207}
208
209pub fn dispatch_operation<'s, T: DiffVisitor<'s>>(
210 root: &'s HttpSchemaDiff,
211 pointer: &PathPointer,
212 method: &'s str,
213 operation_diff_result: &'s DiffResult<OperationDiff>,
214 visitor: &T,
215) {
216 if operation_diff_result.is_none() {
217 return;
218 }
219
220 let pointer = pointer.add(
221 operation_diff_result,
222 method,
223 Some(PathPointerScope::Operation),
224 );
225
226 if !visitor.visit_operation(&pointer, method, operation_diff_result) {
227 return;
228 }
229
230 if let Some(operation) = operation_diff_result.get() {
231 let p = pointer.add_context(&operation.request_body);
233 if visitor.visit_request_body_ref(&p, &operation.request_body) {
234 if let Some(request_body) = operation.request_body.get() {
235 if let Some(request_body_diff_result) =
236 deref_request_body_diff(root, request_body)
237 {
238 let pointer = p.add(
239 request_body_diff_result,
240 "requestBody",
241 Some(PathPointerScope::RequestBody),
242 );
243 if visitor
244 .visit_request_body(&pointer, request_body_diff_result)
245 {
246 if let Some(request_body) =
247 request_body_diff_result.get()
248 {
249 let pointer = pointer.add(
250 &request_body.content,
251 "content",
252 None,
253 );
254 dispatch_media_types(
255 root,
256 &pointer,
257 &request_body.content,
258 visitor,
259 5,
260 );
261 }
262 }
263 }
264 }
265 }
266
267 let p = pointer.add(
269 &operation.responses,
270 "responses",
271 Some(PathPointerScope::Responses),
272 );
273 if visitor.visit_responses(&p, &operation.responses) {
274 if let Some(responses) = operation.responses.get() {
275 for (code, response_diff_result) in responses.iter() {
276 let pointer = p.add(
277 response_diff_result,
278 code,
279 Some(PathPointerScope::ResponseCode),
280 );
281 if visitor
282 .visit_response_ref(&pointer, response_diff_result)
283 {
284 if let Some(response_diff_result_ref) =
285 response_diff_result.get()
286 {
287 if let Some(response_diff_result) =
288 deref_response_diff(
289 root,
290 response_diff_result_ref,
291 )
292 {
293 let pointer =
294 pointer.add_context(response_diff_result);
295 if let Some(response_diff) =
296 response_diff_result.get()
297 {
298 let p = pointer.add(
300 &response_diff.content,
301 "content",
302 None,
303 );
304 dispatch_media_types(
305 root,
306 &p,
307 &response_diff.content,
308 visitor,
309 5,
310 );
311 }
314 }
315 }
316 }
317 }
318 }
319 }
320
321 let p = pointer.add(
322 &operation.parameters,
323 "parameters",
324 Some(PathPointerScope::Parameters),
325 );
326 if visitor.visit_parameters(&p, &operation.parameters) {
327 if let Some(parameters) = operation.parameters.get() {
328 for (idx, may_be_parameter_diff_result) in
329 parameters.iter().enumerate()
330 {
331 let pointer = p.add(
332 may_be_parameter_diff_result,
333 idx.to_string(),
334 None,
335 );
336 if visitor.visit_parameter_ref(
337 &pointer,
338 may_be_parameter_diff_result,
339 ) {
340 if let Some(may_be_parameter) =
341 may_be_parameter_diff_result.get()
342 {
343 if let Some(parameter_diff) =
344 deref_parameter_diff(root, may_be_parameter)
345 {
346 let pointer =
347 pointer.add_context(parameter_diff);
348 visitor
349 .visit_parameter(&pointer, parameter_diff);
350 }
351 }
352 }
353 }
354 }
355 }
356 }
357}
358
359pub fn dispatch_media_types<'s, T: DiffVisitor<'s>>(
360 root: &'s HttpSchemaDiff,
361 pointer: &PathPointer,
362 media_types_diff_result: &'s DiffResult<MapDiff<MediaTypeDiff>>,
363 visitor: &T,
364 depth: usize,
365) {
366 if !visitor.visit_media_types(pointer, media_types_diff_result) {
367 return;
368 }
369
370 if let Some(media_types) = media_types_diff_result.get() {
371 for (mime_type, media_type_diff_result) in media_types.iter() {
372 let p = pointer.add(
373 media_type_diff_result,
374 mime_type,
375 Some(PathPointerScope::MediaType),
376 );
377
378 if !visitor.visit_media_type(&p, media_type_diff_result) {
379 continue;
380 }
381
382 if let Some(media_type) = media_type_diff_result.get() {
383 let p = p.add(
384 &media_type.schema,
385 "schema",
386 Some(PathPointerScope::Schema),
387 );
388 dispatch_schema(root, &p, &media_type.schema, visitor, depth);
389 }
390 }
391 }
392}
393
394pub fn dispatch_schema<'s, T: DiffVisitor<'s>>(
395 root: &'s HttpSchemaDiff,
396 pointer: &PathPointer,
397 may_be_schema_diff_result: &'s DiffResult<MayBeRefDiff<SchemaDiff>>,
398 visitor: &T,
399 depth: usize,
400) {
401 if depth == 0 {
402 return;
403 }
404
405 if !visitor.visit_schema_ref(pointer, may_be_schema_diff_result) {
406 return;
407 }
408
409 if let Some(may_be_schema) = may_be_schema_diff_result.get() {
410 if let Some(schema_diff_result) =
411 deref_schema_diff(root, may_be_schema)
412 {
413 let pointer = pointer.add_context(schema_diff_result);
414
415 if !visitor.visit_schema(&pointer, schema_diff_result) {
416 return;
417 }
418
419 if let Some(schema) = schema_diff_result.get() {
420 if let Some(properties) = schema.properties.get() {
422 let pointer = pointer.add(
423 &schema.properties,
424 "properties",
425 Some(PathPointerScope::SchemaProperties),
426 );
427 for (name, property) in properties.iter() {
428 let pointer = pointer.add(
429 property,
430 name,
431 Some(PathPointerScope::SchemaProperty),
432 );
433 dispatch_schema(
434 root,
435 &pointer,
436 property,
437 visitor,
438 depth - 1,
439 )
440 }
441 }
442
443 if !schema.items.is_none() {
445 let items_pointer = pointer.add(
446 &*schema.items,
447 "items",
448 Some(PathPointerScope::SchemaItems),
449 );
450 dispatch_schema(
451 root,
452 &items_pointer,
453 &schema.items,
454 visitor,
455 depth - 1,
456 );
457 }
458
459 if let Some(may_be_schema_vec) = schema.not.get() {
461 let pointer = pointer.add(
462 &schema.not,
463 "not",
464 Some(PathPointerScope::SchemaNot),
465 );
466 for (idx, may_be_schema_diff_result) in
467 may_be_schema_vec.iter().enumerate()
468 {
469 let pointer = pointer.add(
470 may_be_schema_diff_result,
471 idx.to_string(),
472 None,
473 );
474 dispatch_schema(
475 root,
476 &pointer,
477 may_be_schema_diff_result,
478 visitor,
479 depth - 1,
480 );
481 }
482 }
483
484 if let Some(may_be_schema_vec) = schema.one_of.get() {
486 let pointer = pointer.add(
487 &schema.one_of,
488 "oneOf",
489 Some(PathPointerScope::SchemaOneOf),
490 );
491 for (idx, may_be_schema_diff_result) in
492 may_be_schema_vec.iter().enumerate()
493 {
494 let pointer = pointer.add(
495 may_be_schema_diff_result,
496 idx.to_string(),
497 None,
498 );
499 dispatch_schema(
500 root,
501 &pointer,
502 may_be_schema_diff_result,
503 visitor,
504 depth - 1,
505 );
506 }
507 }
508
509 if let Some(may_be_schema_vec) = schema.any_of.get() {
511 let pointer = pointer.add(
512 &schema.any_of,
513 "anyOf",
514 Some(PathPointerScope::SchemaAnyOf),
515 );
516 for (idx, may_be_schema_diff_result) in
517 may_be_schema_vec.iter().enumerate()
518 {
519 let pointer = pointer.add(
520 may_be_schema_diff_result,
521 idx.to_string(),
522 None,
523 );
524 dispatch_schema(
525 root,
526 &pointer,
527 may_be_schema_diff_result,
528 visitor,
529 depth - 1,
530 );
531 }
532 }
533
534 if let Some(may_be_schema_vec) = schema.all_of.get() {
536 let pointer = pointer.add(
537 &schema.all_of,
538 "allOf",
539 Some(PathPointerScope::SchemaAllOf),
540 );
541 for (idx, may_be_schema_diff_result) in
542 may_be_schema_vec.iter().enumerate()
543 {
544 let pointer = pointer.add(
545 may_be_schema_diff_result,
546 idx.to_string(),
547 None,
548 );
549 dispatch_schema(
550 root,
551 &pointer,
552 may_be_schema_diff_result,
553 visitor,
554 depth - 1,
555 );
556 }
557 }
558
559 if let Some(either_schema) = schema.additional_properties.get()
561 {
562 let pointer = pointer.add(
563 &schema.additional_properties,
564 "additionalProperties",
565 Some(PathPointerScope::SchemaAdditionalProperties),
566 );
567 if let EitherDiff::Right(may_be_schema_diff_result) =
568 either_schema
569 {
570 let pointer =
571 pointer.add_context(&**may_be_schema_diff_result);
572 dispatch_schema(
573 root,
574 &pointer,
575 may_be_schema_diff_result,
576 visitor,
577 depth - 1,
578 );
579 }
580 if let EitherDiff::ToRight(may_be_schema_diff_result) =
581 either_schema
582 {
583 let pointer =
584 pointer.add_context(&**may_be_schema_diff_result);
585 dispatch_schema(
586 root,
587 &pointer,
588 may_be_schema_diff_result,
589 visitor,
590 depth - 1,
591 );
592 }
593 }
594 }
595 }
596 }
597}
598
599pub struct MergedVisitor<'a, 's> {
600 visitors: &'a [&'a dyn DiffVisitor<'s>],
601 config: RefCell<Vec<Option<PathPointer>>>,
602}
603
604impl<'a, 's> MergedVisitor<'a, 's> {
605 pub fn new(visitors: &'s [&'a dyn DiffVisitor<'s>]) -> Self {
606 let config = RefCell::new(vec![None; visitors.len()]);
607
608 Self { config, visitors }
609 }
610
611 #[inline(always)]
612 fn visit<C>(&self, pointer: &PathPointer, visit: C) -> bool
613 where
614 C: Fn(&&'a dyn DiffVisitor<'s>) -> bool,
615 {
616 let mut config = self.config.borrow_mut();
617 let mut result = false;
618
619 for (idx, visitor) in self.visitors.iter().enumerate() {
620 let skip = config[idx]
621 .as_ref()
622 .map(|stopper| pointer.startswith(stopper))
623 .unwrap_or(false);
624 if skip {
625 continue;
626 }
627
628 let proceed = visit(visitor);
629 if !proceed {
630 config[idx] = Some(pointer.clone());
631 }
632
633 result |= proceed;
634 }
635 result
636 }
637}
638
639impl<'a, 's> DiffVisitor<'s> for MergedVisitor<'a, 's> {
640 fn visit_root(&self) {
641 self.visitors.iter().for_each(|v| v.visit_root());
642 }
643
644 fn visit_paths(
645 &self,
646 pointer: &PathPointer,
647 paths_diff_result: &'s DiffResult<
648 MapDiff<MayBeRefDiff<PathDiff>, PathsMapPathResolver>,
649 >,
650 ) -> bool {
651 self.visit(pointer, |v| v.visit_paths(pointer, paths_diff_result))
652 }
653
654 fn visit_path(
655 &self,
656 pointer: &PathPointer,
657 path: &str,
658 path_diff_result: &'s DiffResult<PathDiff>,
659 ) -> bool {
660 self.visit(pointer, |v| v.visit_path(pointer, path, path_diff_result))
661 }
662
663 fn visit_operation(
664 &self,
665 pointer: &PathPointer,
666 method: &str,
667 operation_diff_result: &'s DiffResult<OperationDiff>,
668 ) -> bool {
669 self.visit(pointer, |v| {
670 v.visit_operation(pointer, method, operation_diff_result)
671 })
672 }
673
674 fn visit_request_body(
675 &self,
676 pointer: &PathPointer,
677 request_body_diff_result: &'s DiffResult<RequestBodyDiff>,
678 ) -> bool {
679 self.visit(pointer, |v| {
680 v.visit_request_body(pointer, request_body_diff_result)
681 })
682 }
683
684 fn visit_media_types(
685 &self,
686 pointer: &PathPointer,
687 media_types_diff_result: &'s DiffResult<MapDiff<MediaTypeDiff>>,
688 ) -> bool {
689 self.visit(pointer, |v| {
690 v.visit_media_types(pointer, media_types_diff_result)
691 })
692 }
693
694 fn visit_media_type(
695 &self,
696 pointer: &PathPointer,
697 media_type_diff_result: &'s DiffResult<MediaTypeDiff>,
698 ) -> bool {
699 self.visit(pointer, |v| {
700 v.visit_media_type(pointer, media_type_diff_result)
701 })
702 }
703
704 fn visit_parameters(
705 &self,
706 pointer: &PathPointer,
707 parameters_diff_result: &'s DiffResult<
708 VecDiff<MayBeRefDiff<ParameterDiff>>,
709 >,
710 ) -> bool {
711 self.visit(pointer, |v| {
712 v.visit_parameters(pointer, parameters_diff_result)
713 })
714 }
715
716 fn visit_parameter(
717 &self,
718 pointer: &PathPointer,
719 parameter_diff_result: &'s DiffResult<ParameterDiff>,
720 ) -> bool {
721 self.visit(pointer, |v| {
722 v.visit_parameter(pointer, parameter_diff_result)
723 })
724 }
725
726 fn visit_schema(
727 &self,
728 pointer: &PathPointer,
729 schema_diff_result: &'s DiffResult<SchemaDiff>,
730 ) -> bool {
731 self.visit(pointer, |v| v.visit_schema(pointer, schema_diff_result))
732 }
733}
734
735#[cfg(test)]
736mod test {
737 use crate::core::{DiffResult, MapDiff};
738 use crate::get_schema_diff;
739 use crate::path_pointer::{PathPointer, PathPointerScope};
740 use crate::schema::HttpSchema;
741 use crate::schema_diff::{
742 HttpSchemaDiff, MayBeRefDiff, OperationDiff, PathDiff,
743 RequestBodyDiff, ResponseDiff,
744 };
745 use crate::schema_diff_utils::PathsMapPathResolver;
746 use crate::schemas::openapi303::schema::OpenApi303;
747 use crate::visitor::{dispatch_visitor, DiffVisitor};
748
749 #[test]
750 fn test_pointer_level_values() {
751 let src_schema: HttpSchema = serde_json::from_str::<OpenApi303>(
752 include_str!("../data/visitor-pointer-test.json"),
753 )
754 .unwrap()
755 .into();
756
757 let tgt_schema: HttpSchema = serde_json::from_str::<OpenApi303>(
758 include_str!("../data/visitor-pointer-test.json"),
759 )
760 .unwrap()
761 .into();
762
763 let diff = get_schema_diff(src_schema, tgt_schema);
764
765 struct PointerLevelVisitor<'s>(&'s HttpSchemaDiff);
766
767 impl<'s> DiffVisitor<'s> for PointerLevelVisitor<'s> {
768 fn visit_paths(
769 &self,
770 pointer: &PathPointer,
771 _: &'s DiffResult<
772 MapDiff<MayBeRefDiff<PathDiff>, PathsMapPathResolver>,
773 >,
774 ) -> bool {
775 let component = pointer.get(PathPointerScope::Paths).unwrap();
776 assert_eq!(component.path, Some("paths".to_string()));
777 true
778 }
779
780 fn visit_path(
781 &self,
782 pointer: &PathPointer,
783 _: &str,
784 _: &'s DiffResult<PathDiff>,
785 ) -> bool {
786 let component = pointer.get(PathPointerScope::Path).unwrap();
787 assert_eq!(
788 component.path,
789 Some("/{entity_type}/{id}/change_tags".to_string())
790 );
791 true
792 }
793
794 fn visit_operation(
795 &self,
796 pointer: &PathPointer,
797 _: &str,
798 _: &'s DiffResult<OperationDiff>,
799 ) -> bool {
800 let component =
801 pointer.get(PathPointerScope::Operation).unwrap();
802 assert_eq!(component.path, Some("post".to_string()));
803 true
804 }
805
806 fn visit_request_body(
807 &self,
808 pointer: &PathPointer,
809 _: &'s DiffResult<RequestBodyDiff>,
810 ) -> bool {
811 let component =
812 pointer.get(PathPointerScope::RequestBody).unwrap();
813 assert_eq!(component.path, Some("requestBody".to_string()));
814 false
815 }
816
817 fn visit_responses(
818 &self,
819 pointer: &PathPointer,
820 _: &'s DiffResult<MapDiff<MayBeRefDiff<ResponseDiff>>>,
821 ) -> bool {
822 let component =
823 pointer.get(PathPointerScope::Responses).unwrap();
824 assert_eq!(component.path, Some("responses".to_string()));
825 false
826 }
827 }
828
829 dispatch_visitor(
830 diff.get().unwrap(),
831 &PointerLevelVisitor(&diff.get().unwrap()),
832 );
833 }
834}