1use std::cell::RefCell;
2use std::collections::HashMap;
3
4use crate::core::{DiffResult, MapDiff, VecDiff};
5use crate::diff_result_type::DiffResultType;
6use crate::path_pointer::PathPointer;
7use crate::schema_diff::{
8 deref_parameter_diff, deref_request_body_diff, deref_response_diff,
9 deref_schema_diff, HttpSchemaDiff, MayBeRefDiff, MediaTypeDiff,
10 OperationDiff, ParameterDiff, RequestBodyDiff, ResponseDiff, SchemaDiff,
11};
12
13use crate::visitor::{dispatch_visitor, DiffVisitor};
14
15#[derive(Eq, Hash, PartialEq, Debug)]
16pub enum SharedChangeComponent {
17 RequestBody,
18 Parameter,
19 Response,
20 Schema,
21}
22
23#[derive(Eq, Hash, PartialEq, Debug)]
24pub struct SharedChange {
25 kind: DiffResultType,
26 reference: String,
27 component: SharedChangeComponent,
28}
29
30struct SharedChangesVisitor<'s> {
31 diff: &'s HttpSchemaDiff,
32 pointers: RefCell<HashMap<SharedChange, Vec<PathPointer>>>,
33}
34
35impl<'s> SharedChangesVisitor<'s> {
36 pub fn new(diff: &'s HttpSchemaDiff) -> Self {
37 Self {
38 diff,
39 pointers: RefCell::new(HashMap::new()),
40 }
41 }
42}
43
44impl<'s> DiffVisitor<'s> for SharedChangesVisitor<'s> {
45 fn visit_schema_ref(
48 &self,
49 pointer: &PathPointer,
50 may_be_ref: &'s DiffResult<MayBeRefDiff<SchemaDiff>>,
51 ) -> bool {
52 if !pointer.parent().is_updated() || may_be_ref.is_same_or_none() {
53 return false;
54 }
55
56 let Some(value) = may_be_ref.get() else {
57 return false;
58 };
59
60 let MayBeRefDiff::Ref(reference) = value else {
61 return true;
62 };
63
64 let Some(diff) = deref_schema_diff(self.diff, value) else {
65 return false;
66 };
67
68 let key = SharedChange {
69 kind: diff.into(),
70 reference: reference.reference.clone(),
71 component: SharedChangeComponent::Schema,
72 };
73
74 self.pointers
75 .borrow_mut()
76 .entry(key)
77 .and_modify(|arr| arr.push(pointer.clone()))
78 .or_insert_with(|| vec![pointer.clone()]);
79
80 false
81 }
82
83 fn visit_response_ref(
84 &self,
85 pointer: &PathPointer,
86 may_be_ref: &'s DiffResult<MayBeRefDiff<ResponseDiff>>,
87 ) -> bool {
88 if !pointer.parent().is_updated() || may_be_ref.is_same_or_none() {
89 return false;
90 }
91
92 let Some(value) = may_be_ref.get() else {
93 return false;
94 };
95
96 let MayBeRefDiff::Ref(reference) = value else {
97 return true;
98 };
99
100 let Some(diff) = deref_response_diff(self.diff, value) else {
101 return false;
102 };
103
104 let key = SharedChange {
105 kind: diff.into(),
106 reference: reference.reference.clone(),
107 component: SharedChangeComponent::Response,
108 };
109
110 self.pointers
111 .borrow_mut()
112 .entry(key)
113 .and_modify(|arr| arr.push(pointer.clone()))
114 .or_insert_with(|| vec![pointer.clone()]);
115
116 false
117 }
118
119 fn visit_parameter_ref(
120 &self,
121 pointer: &PathPointer,
122 may_be_ref: &'s DiffResult<MayBeRefDiff<ParameterDiff>>,
123 ) -> bool {
124 if !pointer.parent().is_updated() || may_be_ref.is_same_or_none() {
125 return false;
126 }
127
128 let Some(value) = may_be_ref.get() else {
129 return false;
130 };
131
132 let MayBeRefDiff::Ref(reference) = value else {
133 return true;
134 };
135
136 let Some(diff) = deref_parameter_diff(self.diff, value) else {
137 return false;
138 };
139
140 let key = SharedChange {
141 kind: diff.into(),
142 reference: reference.reference.clone(),
143 component: SharedChangeComponent::Parameter,
144 };
145
146 self.pointers
147 .borrow_mut()
148 .entry(key)
149 .and_modify(|arr| arr.push(pointer.clone()))
150 .or_insert_with(|| vec![pointer.clone()]);
151
152 false
153 }
154
155 fn visit_request_body_ref(
156 &self,
157 pointer: &PathPointer,
158 may_be_ref: &'s DiffResult<MayBeRefDiff<RequestBodyDiff>>,
159 ) -> bool {
160 if !pointer.parent().is_updated() || may_be_ref.is_same_or_none() {
161 return false;
162 }
163
164 let Some(value) = may_be_ref.get() else {
165 return false;
166 };
167
168 let MayBeRefDiff::Ref(reference) = value else {
169 return true;
170 };
171
172 let Some(diff) = deref_request_body_diff(self.diff, value) else {
173 return false;
174 };
175
176 let key = SharedChange {
177 kind: diff.into(),
178 reference: reference.reference.clone(),
179 component: SharedChangeComponent::RequestBody,
180 };
181
182 self.pointers
183 .borrow_mut()
184 .entry(key)
185 .and_modify(|arr| arr.push(pointer.clone()))
186 .or_insert_with(|| vec![pointer.clone()]);
187
188 false
189 }
190 fn visit_operation(
191 &self,
192 p: &PathPointer,
193 _: &str,
194 _: &'s DiffResult<OperationDiff>,
195 ) -> bool {
196 p.parent().is_updated()
197 }
198
199 fn visit_request_body(
200 &self,
201 p: &PathPointer,
202 _: &'s DiffResult<RequestBodyDiff>,
203 ) -> bool {
204 p.parent().is_updated()
205 }
206
207 fn visit_responses(
208 &self,
209 p: &PathPointer,
210 _: &'s DiffResult<MapDiff<MayBeRefDiff<ResponseDiff>>>,
211 ) -> bool {
212 p.parent().is_updated()
213 }
214
215 fn visit_media_types(
216 &self,
217 p: &PathPointer,
218 _: &'s DiffResult<MapDiff<MediaTypeDiff>>,
219 ) -> bool {
220 p.parent().is_updated()
221 }
222
223 fn visit_media_type(
224 &self,
225 p: &PathPointer,
226 _: &'s DiffResult<MediaTypeDiff>,
227 ) -> bool {
228 p.parent().is_updated()
229 }
230
231 fn visit_parameters(
232 &self,
233 p: &PathPointer,
234 _: &'s DiffResult<VecDiff<MayBeRefDiff<ParameterDiff>>>,
235 ) -> bool {
236 p.parent().is_updated()
237 }
238
239 fn visit_parameter(
240 &self,
241 p: &PathPointer,
242 _: &'s DiffResult<ParameterDiff>,
243 ) -> bool {
244 p.parent().is_updated()
245 }
246
247 fn visit_schema(
248 &self,
249 p: &PathPointer,
250 _: &'s DiffResult<SchemaDiff>,
251 ) -> bool {
252 p.parent().is_updated()
253 }
254}
255
256pub fn get_shared_changes(
257 diff: &HttpSchemaDiff,
258) -> HashMap<SharedChange, Vec<PathPointer>> {
259 let visitor = SharedChangesVisitor::new(diff);
260
261 dispatch_visitor(diff, &visitor);
262
263 visitor.pointers.into_inner()
264}