schemadoc_diff/
visitor.rs

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    // Always look into by default
18
19    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    // Specify in concrete visitor whether to visit deeper entities
70
71    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        // operation.request_body
232        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        // operation.responses
268        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                                    // response_diff.content
299                                    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                                    // response_diff.headers
312                                    // TODO
313                                }
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                // schema.properties
421                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                // schema.items
444                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                // schema.not
460                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                // schema.oneOf
485                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                // schema.anyOf
510                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                // schema.allOf
535                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                // schema.additionalProperties
560                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}