schemadoc_diff/checker/
added_required_body_property_check.rs

1use std::cell::RefCell;
2
3use crate::checker::{ValidationIssue, ValidationIssuer};
4use crate::core::{DiffResult, MapDiff};
5use crate::path_pointer::PathPointer;
6use crate::schema_diff::{
7    MediaTypeDiff, OperationDiff, RequestBodyDiff, SchemaDiff,
8};
9
10use crate::visitor::DiffVisitor;
11
12pub struct AddedRequiredBodyPropertyCheck {
13    pointers: RefCell<Vec<PathPointer>>,
14}
15
16impl<'s> DiffVisitor<'s> for AddedRequiredBodyPropertyCheck {
17    fn visit_operation(
18        &self,
19        pointer: &PathPointer,
20        _: &str,
21        _: &'s DiffResult<OperationDiff>,
22    ) -> bool {
23        pointer.is_updated()
24    }
25
26    fn visit_request_body(
27        &self,
28        pointer: &PathPointer,
29        request_body_diff_result: &'s DiffResult<RequestBodyDiff>,
30    ) -> bool {
31        if !pointer.is_updated() {
32            return false;
33        }
34        // Continue only if request body is required
35        if let Some(request_body_diff) = request_body_diff_result.get() {
36            request_body_diff
37                .required
38                .get()
39                .map(|v| *v)
40                .unwrap_or(false)
41        } else {
42            false
43        }
44    }
45
46    fn visit_media_types(
47        &self,
48        pointer: &PathPointer,
49        _: &'s DiffResult<MapDiff<MediaTypeDiff>>,
50    ) -> bool {
51        pointer.is_updated()
52    }
53
54    fn visit_media_type(
55        &self,
56        pointer: &PathPointer,
57        _: &'s DiffResult<MediaTypeDiff>,
58    ) -> bool {
59        pointer.is_updated()
60    }
61
62    fn visit_schema(
63        &self,
64        pointer: &PathPointer,
65        _schema_diff_result: &'s DiffResult<SchemaDiff>,
66    ) -> bool {
67        if pointer.is_added() {
68            self.pointers.borrow_mut().push(pointer.clone());
69            return false;
70        }
71
72        pointer.is_updated()
73    }
74}
75
76impl Default for AddedRequiredBodyPropertyCheck {
77    fn default() -> Self {
78        AddedRequiredBodyPropertyCheck {
79            pointers: RefCell::new(vec![]),
80        }
81    }
82}
83
84impl<'s> ValidationIssuer<'s> for AddedRequiredBodyPropertyCheck {
85    fn id(&self) -> &'static str {
86        "added-required-body-property"
87    }
88
89    fn visitor(&self) -> &dyn DiffVisitor<'s> {
90        self
91    }
92
93    fn issues(&self) -> Option<Vec<ValidationIssue>> {
94        let pointers = std::mem::take(&mut *self.pointers.borrow_mut());
95
96        let issues = pointers
97            .into_iter()
98            .map(|path| ValidationIssue::new(path, self.id(), true))
99            .collect::<Vec<ValidationIssue>>();
100
101        Some(issues)
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use crate::checker::added_required_body_property_check::AddedRequiredBodyPropertyCheck;
108    use crate::checker::ValidationIssuer;
109    use crate::get_schema_diff;
110    use crate::schema::HttpSchema;
111    use crate::schemas::openapi303::schema::OpenApi303;
112
113    #[test]
114    fn test_added_required_property_check() {
115        let src_schema: HttpSchema = serde_json::from_str::<OpenApi303>(include_str!(
116            "../../data/checks/added-required-property/schema-with-required-body.json"
117        ))
118        .unwrap()
119        .into();
120
121        let tgt_schema: HttpSchema = serde_json::from_str::<OpenApi303>(include_str!(
122            "../../data/checks/added-required-property/schema-with-required-body-altered.json"
123        ))
124        .unwrap()
125        .into();
126
127        let diff = get_schema_diff(src_schema, tgt_schema);
128
129        let checker = AddedRequiredBodyPropertyCheck::default();
130        crate::visitor::dispatch_visitor(diff.get().unwrap(), &checker);
131        let issues = checker.issues().unwrap();
132
133        assert_eq!(issues.len(), 2);
134        // new schema to allOf added
135        assert_eq!(
136            issues.get(0).unwrap().path.get_path(),
137            "paths//test/put/requestBody/content/application/json/schema/properties/field1/allOf/0",
138        );
139        // new property added
140        assert_eq!(
141            issues.get(1).unwrap().path.get_path(),
142            "paths//test/put/requestBody/content/application/json/schema/properties/field2",
143        );
144    }
145
146    #[test]
147    fn test_added_not_required_property_check() {
148        let src_schema: HttpSchema = serde_json::from_str::<OpenApi303>(include_str!(
149            "../../data/checks/added-required-property/schema-with-required-body.json"
150        ))
151        .unwrap()
152        .into();
153
154        let get_tgt_schema = || {
155            let mut schema: HttpSchema = serde_json::from_str::<OpenApi303>(include_str!(
156                "../../data/checks/added-required-property/schema-with-required-body-altered.json"
157            ))
158            .unwrap()
159            .into();
160
161            schema
162                .paths
163                .as_mut()?
164                .get_mut("/test")
165                .as_mut()?
166                .value_mut()?
167                .put
168                .as_mut()?
169                .request_body
170                .as_mut()?
171                .value_mut()?
172                .required = Some(false);
173
174            Some(schema)
175        };
176
177        let tgt_schema: HttpSchema = get_tgt_schema().unwrap();
178
179        let diff = get_schema_diff(src_schema, tgt_schema);
180
181        let checker = AddedRequiredBodyPropertyCheck::default();
182        crate::visitor::dispatch_visitor(diff.get().unwrap(), &checker);
183        let issues = checker.issues().unwrap();
184
185        assert!(issues.is_empty());
186    }
187}