schemadoc_diff/
schema_diff.rs

1use serde::{Deserialize, Serialize};
2use std::fmt::Debug;
3
4use schemadoc_diff_derive::{Diff, DiffOwnChanges, Empty};
5use serde_json::Value;
6
7use crate::core::{
8    Diff, DiffContext, DiffResult, EitherDiff, Empty, MapDiff,
9    MayBeRefCoreDiff, Referencable, VecDiff,
10};
11
12use crate::schema::HttpSchemaRef;
13use crate::schema_diff_utils::{PathsMapPathResolver, TypeVecDiffSorter};
14
15pub type MayBeRefDiff<T> = MayBeRefCoreDiff<T, HttpSchemaRef>;
16
17fn check_custom_fields(fields: &DiffResult<MapDiff<Value>>) -> bool {
18    if fields.is_same() {
19        let fields = fields.get().expect("Must always be set");
20        fields.is_empty()
21    } else {
22        fields.is_none()
23    }
24}
25
26#[derive(
27    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
28)]
29#[serde(rename_all = "camelCase")]
30pub struct HttpSchemaDiff {
31    pub version: String,
32
33    pub schema_version: String,
34    pub schema_source: String,
35    pub schema_source_version: String,
36
37    pub info: DiffResult<InfoDiff>,
38    pub servers: DiffResult<VecDiff<ServerDiff>>,
39    pub paths:
40        DiffResult<MapDiff<MayBeRefDiff<PathDiff>, PathsMapPathResolver>>,
41    pub components: DiffResult<ComponentsDiff>,
42    pub tags: DiffResult<VecDiff<TagDiff>>,
43    pub external_docs: DiffResult<ExternalDocDiff>,
44}
45
46impl HttpSchemaDiff {
47    pub fn get_diff_version(&self) -> String {
48        format!(
49            "{}-{}-{}",
50            self.schema_version,
51            self.schema_source,
52            self.schema_source_version
53        )
54    }
55}
56
57#[derive(
58    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
59)]
60#[serde(rename_all = "camelCase")]
61pub struct InfoDiff {
62    #[serde(skip_serializing_if = "DiffResult::is_none")]
63    pub title: DiffResult<String>,
64    #[serde(skip_serializing_if = "DiffResult::is_none")]
65    pub description: DiffResult<String>,
66    #[serde(skip_serializing_if = "DiffResult::is_none")]
67    pub terms_of_service: DiffResult<String>,
68    #[serde(skip_serializing_if = "DiffResult::is_none")]
69    pub contact: DiffResult<ContactDiff>,
70    #[serde(skip_serializing_if = "DiffResult::is_none")]
71    pub license: DiffResult<LicenseDiff>,
72    #[serde(skip_serializing_if = "DiffResult::is_none")]
73    pub version: DiffResult<String>,
74}
75
76#[derive(
77    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
78)]
79pub struct ContactDiff {
80    #[serde(skip_serializing_if = "DiffResult::is_none")]
81    pub name: DiffResult<String>,
82    #[serde(skip_serializing_if = "DiffResult::is_none")]
83    pub url: DiffResult<String>,
84    #[serde(skip_serializing_if = "DiffResult::is_none")]
85    pub email: DiffResult<String>,
86}
87
88#[derive(
89    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
90)]
91pub struct LicenseDiff {
92    #[serde(skip_serializing_if = "DiffResult::is_none")]
93    pub name: DiffResult<String>,
94    #[serde(skip_serializing_if = "DiffResult::is_none")]
95    pub url: DiffResult<String>,
96}
97
98#[derive(
99    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
100)]
101pub struct ServerDiff {
102    #[serde(skip_serializing_if = "DiffResult::is_none")]
103    pub url: DiffResult<String>,
104    #[serde(skip_serializing_if = "DiffResult::is_none")]
105    pub description: DiffResult<String>,
106    #[serde(skip_serializing_if = "DiffResult::is_none")]
107    pub variables: DiffResult<MapDiff<ServerVariableDiff>>,
108}
109
110#[derive(
111    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
112)]
113pub struct ServerVariableDiff {
114    #[serde(skip_serializing_if = "DiffResult::is_none")]
115    pub r#enum: DiffResult<VecDiff<String>>,
116    #[serde(skip_serializing_if = "DiffResult::is_none")]
117    pub default: DiffResult<Value>,
118    #[serde(skip_serializing_if = "DiffResult::is_none")]
119    pub description: DiffResult<String>,
120}
121
122#[derive(
123    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
124)]
125#[serde(rename_all = "camelCase")]
126pub struct ComponentsDiff {
127    #[serde(skip_serializing_if = "DiffResult::is_none")]
128    pub schemas: DiffResult<MapDiff<MayBeRefDiff<SchemaDiff>>>,
129    #[serde(skip_serializing_if = "DiffResult::is_none")]
130    pub responses: DiffResult<MapDiff<MayBeRefDiff<ResponseDiff>>>,
131    #[serde(skip_serializing_if = "DiffResult::is_none")]
132    pub parameters: DiffResult<MapDiff<MayBeRefDiff<ParameterDiff>>>,
133    #[serde(skip_serializing_if = "DiffResult::is_none")]
134    pub examples: DiffResult<MapDiff<MayBeRefDiff<ExampleDiff>>>,
135    #[serde(skip_serializing_if = "DiffResult::is_none")]
136    pub request_bodies: DiffResult<MapDiff<MayBeRefDiff<RequestBodyDiff>>>,
137    #[serde(skip_serializing_if = "DiffResult::is_none")]
138    pub headers: DiffResult<MapDiff<MayBeRefDiff<HeaderDiff>>>,
139    #[serde(skip_serializing_if = "DiffResult::is_none")]
140    pub security_schemes:
141        DiffResult<MapDiff<MayBeRefDiff<SecuritySchemeDiff>>>,
142    #[serde(skip_serializing_if = "DiffResult::is_none")]
143    pub links: DiffResult<MapDiff<MayBeRefDiff<LinkDiff>>>,
144}
145
146#[derive(
147    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
148)]
149#[serde(rename_all = "camelCase")]
150pub struct ExternalDocDiff {
151    #[serde(skip_serializing_if = "DiffResult::is_none")]
152    pub url: DiffResult<String>,
153    #[serde(skip_serializing_if = "DiffResult::is_none")]
154    pub description: DiffResult<String>,
155}
156
157#[derive(
158    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
159)]
160#[serde(rename_all = "camelCase")]
161pub struct ParameterDiff {
162    pub name: String,
163    pub r#in: String,
164    #[serde(skip_serializing_if = "DiffResult::is_none")]
165    pub description: DiffResult<String>,
166    #[serde(skip_serializing_if = "DiffResult::is_none")]
167    pub required: DiffResult<bool>,
168    #[serde(skip_serializing_if = "DiffResult::is_none")]
169    pub deprecated: DiffResult<bool>,
170    #[serde(skip_serializing_if = "DiffResult::is_none")]
171    pub allow_empty_value: DiffResult<bool>,
172    #[serde(skip_serializing_if = "DiffResult::is_none")]
173    pub style: DiffResult<String>,
174    #[serde(skip_serializing_if = "DiffResult::is_none")]
175    pub explode: DiffResult<bool>,
176    #[serde(skip_serializing_if = "DiffResult::is_none")]
177    pub allow_reserved: DiffResult<bool>,
178
179    #[serde(skip_serializing_if = "DiffResult::is_none")]
180    pub schema: DiffResult<MayBeRefDiff<SchemaDiff>>,
181
182    #[serde(skip_serializing_if = "DiffResult::is_none")]
183    pub examples: DiffResult<MapDiff<MayBeRefDiff<Value>>>,
184    #[serde(skip_serializing_if = "DiffResult::is_none")]
185    pub content: DiffResult<MapDiff<MediaTypeDiff>>,
186
187    #[serde(skip_serializing_if = "check_custom_fields")]
188    pub custom_fields: DiffResult<MapDiff<Value>>,
189}
190
191impl Referencable for ParameterDiff {}
192
193#[derive(
194    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
195)]
196#[serde(rename_all = "camelCase")]
197pub struct RequestBodyDiff {
198    #[serde(skip_serializing_if = "DiffResult::is_none")]
199    pub description: DiffResult<String>,
200    #[serde(skip_serializing_if = "DiffResult::is_none")]
201    pub content: DiffResult<MapDiff<MediaTypeDiff>>,
202    #[serde(skip_serializing_if = "DiffResult::is_none")]
203    pub required: DiffResult<bool>,
204}
205
206impl Referencable for RequestBodyDiff {}
207
208#[derive(
209    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
210)]
211#[serde(rename_all = "camelCase")]
212pub struct MediaTypeDiff {
213    #[serde(skip_serializing_if = "DiffResult::is_none")]
214    pub schema: DiffResult<MayBeRefDiff<SchemaDiff>>,
215
216    #[serde(skip_serializing_if = "DiffResult::is_none")]
217    pub examples: DiffResult<MapDiff<MayBeRefDiff<ExampleDiff>>>,
218    #[serde(skip_serializing_if = "DiffResult::is_none")]
219    pub encoding: DiffResult<MapDiff<EncodingDiff>>,
220}
221
222#[derive(
223    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
224)]
225#[serde(rename_all = "camelCase")]
226pub struct EncodingDiff {
227    #[serde(skip_serializing_if = "DiffResult::is_none")]
228    pub content_type: DiffResult<String>,
229    #[serde(skip_serializing_if = "DiffResult::is_none")]
230    pub headers: DiffResult<MapDiff<MayBeRefDiff<HeaderDiff>>>,
231    #[serde(skip_serializing_if = "DiffResult::is_none")]
232    pub style: DiffResult<String>,
233    #[serde(skip_serializing_if = "DiffResult::is_none")]
234    pub explode: DiffResult<bool>,
235    #[serde(skip_serializing_if = "DiffResult::is_none")]
236    pub allow_reserved: DiffResult<bool>,
237}
238
239impl Referencable for EncodingDiff {}
240
241#[derive(
242    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
243)]
244#[serde(rename_all = "camelCase")]
245pub struct LinkDiff {
246    #[serde(skip_serializing_if = "DiffResult::is_none")]
247    pub operation_ref: DiffResult<String>,
248    #[serde(skip_serializing_if = "DiffResult::is_none")]
249    pub operation_id: DiffResult<String>,
250    #[serde(skip_serializing_if = "DiffResult::is_none")]
251    pub parameters: DiffResult<MapDiff<Value>>,
252    #[serde(skip_serializing_if = "DiffResult::is_none")]
253    pub request_body: DiffResult<Value>,
254    #[serde(skip_serializing_if = "DiffResult::is_none")]
255    pub description: DiffResult<String>,
256    #[serde(skip_serializing_if = "DiffResult::is_none")]
257    pub server: DiffResult<ServerDiff>,
258}
259
260impl Referencable for LinkDiff {}
261
262#[derive(
263    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
264)]
265pub struct ResponseDiff {
266    #[serde(skip_serializing_if = "DiffResult::is_none")]
267    pub description: DiffResult<String>,
268
269    #[serde(skip_serializing_if = "DiffResult::is_none")]
270    pub content: DiffResult<MapDiff<MediaTypeDiff>>,
271    #[serde(skip_serializing_if = "DiffResult::is_none")]
272    pub links: DiffResult<MapDiff<MayBeRefDiff<LinkDiff>>>,
273    #[serde(skip_serializing_if = "DiffResult::is_none")]
274    pub headers: DiffResult<MapDiff<MayBeRefDiff<HeaderDiff>>>,
275}
276
277impl Referencable for ResponseDiff {}
278
279#[derive(
280    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
281)]
282#[serde(rename_all = "camelCase")]
283pub struct ExampleDiff {
284    #[serde(skip_serializing_if = "DiffResult::is_none")]
285    pub summary: DiffResult<String>,
286    #[serde(skip_serializing_if = "DiffResult::is_none")]
287    pub description: DiffResult<String>,
288    #[serde(skip_serializing_if = "DiffResult::is_none")]
289    pub value: DiffResult<Value>,
290    #[serde(skip_serializing_if = "DiffResult::is_none")]
291    pub external_value: DiffResult<String>,
292}
293
294impl Referencable for ExampleDiff {}
295
296#[derive(
297    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
298)]
299#[serde(rename_all = "camelCase")]
300pub struct DiscriminatorDiff {
301    #[serde(skip_serializing_if = "DiffResult::is_none")]
302    pub property_name: DiffResult<String>,
303    #[serde(skip_serializing_if = "DiffResult::is_none")]
304    pub mapping: DiffResult<MapDiff<String>>,
305}
306
307#[derive(
308    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
309)]
310#[serde(rename_all = "camelCase")]
311pub struct XmlDiff {
312    #[serde(skip_serializing_if = "DiffResult::is_none")]
313    pub name: DiffResult<String>,
314    #[serde(skip_serializing_if = "DiffResult::is_none")]
315    pub namespace: DiffResult<String>,
316    #[serde(skip_serializing_if = "DiffResult::is_none")]
317    pub prefix: DiffResult<String>,
318    #[serde(skip_serializing_if = "DiffResult::is_none")]
319    pub attribute: DiffResult<bool>,
320    #[serde(skip_serializing_if = "DiffResult::is_none")]
321    pub wrapped: DiffResult<bool>,
322}
323
324#[derive(
325    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
326)]
327#[serde(rename_all = "camelCase")]
328pub struct SecuritySchemeDiff {
329    #[serde(skip_serializing_if = "DiffResult::is_none")]
330    pub r#type: DiffResult<String>,
331    #[serde(skip_serializing_if = "DiffResult::is_none")]
332    pub description: DiffResult<String>,
333    #[serde(skip_serializing_if = "DiffResult::is_none")]
334    pub name: DiffResult<String>,
335    #[serde(skip_serializing_if = "DiffResult::is_none")]
336    pub r#in: DiffResult<String>,
337    #[serde(skip_serializing_if = "DiffResult::is_none")]
338    pub scheme: DiffResult<String>,
339    #[serde(skip_serializing_if = "DiffResult::is_none")]
340    pub bearer_format: DiffResult<String>,
341    #[serde(skip_serializing_if = "DiffResult::is_none")]
342    pub flows: DiffResult<OAuthFlowsDiff>,
343    #[serde(skip_serializing_if = "DiffResult::is_none")]
344    pub open_id_connect_url: DiffResult<String>,
345}
346
347impl Referencable for SecuritySchemeDiff {}
348
349#[derive(
350    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
351)]
352#[serde(rename_all = "camelCase")]
353pub struct OAuthFlowsDiff {
354    #[serde(skip_serializing_if = "DiffResult::is_none")]
355    pub implicit: DiffResult<OAuthFlowDiff>,
356    #[serde(skip_serializing_if = "DiffResult::is_none")]
357    pub password: DiffResult<OAuthFlowDiff>,
358    #[serde(skip_serializing_if = "DiffResult::is_none")]
359    pub client_credentials: DiffResult<OAuthFlowDiff>,
360    #[serde(skip_serializing_if = "DiffResult::is_none")]
361    pub authorization_code: DiffResult<OAuthFlowDiff>,
362}
363
364#[derive(
365    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
366)]
367#[serde(rename_all = "camelCase")]
368pub struct OAuthFlowDiff {
369    #[serde(skip_serializing_if = "DiffResult::is_none")]
370    pub authorization_url: DiffResult<String>,
371    #[serde(skip_serializing_if = "DiffResult::is_none")]
372    pub token_url: DiffResult<String>,
373    #[serde(skip_serializing_if = "DiffResult::is_none")]
374    pub refresh_url: DiffResult<String>,
375    #[serde(skip_serializing_if = "DiffResult::is_none")]
376    pub scopes: DiffResult<MapDiff<String>>,
377}
378
379#[derive(
380    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
381)]
382#[serde(rename_all = "camelCase")]
383pub struct TagDiff {
384    #[serde(skip_serializing_if = "DiffResult::is_none")]
385    pub name: DiffResult<String>,
386    #[serde(skip_serializing_if = "DiffResult::is_none")]
387    pub description: DiffResult<String>,
388    #[serde(skip_serializing_if = "DiffResult::is_none")]
389    pub external_doc: DiffResult<ExternalDocDiff>,
390}
391
392#[derive(
393    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
394)]
395#[serde(rename_all = "camelCase")]
396pub struct SchemaDiff {
397    #[serde(skip_serializing_if = "DiffResult::is_none")]
398    pub title: DiffResult<String>,
399    #[serde(skip_serializing_if = "DiffResult::is_none")]
400    pub multiple_of: DiffResult<f32>,
401    #[serde(skip_serializing_if = "DiffResult::is_none")]
402    pub maximum: DiffResult<f32>,
403    #[serde(skip_serializing_if = "DiffResult::is_none")]
404    pub exclusive_maximum: DiffResult<bool>,
405    #[serde(skip_serializing_if = "DiffResult::is_none")]
406    pub minimum: DiffResult<f32>,
407    #[serde(skip_serializing_if = "DiffResult::is_none")]
408    pub exclusive_minimum: DiffResult<bool>,
409    #[serde(skip_serializing_if = "DiffResult::is_none")]
410    pub max_length: DiffResult<usize>,
411    #[serde(skip_serializing_if = "DiffResult::is_none")]
412    pub min_length: DiffResult<usize>,
413    #[serde(skip_serializing_if = "DiffResult::is_none")]
414    pub pattern: DiffResult<String>,
415    #[serde(skip_serializing_if = "DiffResult::is_none")]
416    pub max_items: DiffResult<usize>,
417    #[serde(skip_serializing_if = "DiffResult::is_none")]
418    pub min_items: DiffResult<usize>,
419    #[serde(skip_serializing_if = "DiffResult::is_none")]
420    pub unique_items: DiffResult<bool>,
421
422    #[serde(skip_serializing_if = "DiffResult::is_none")]
423    pub max_properties: DiffResult<usize>,
424    #[serde(skip_serializing_if = "DiffResult::is_none")]
425    pub min_properties: DiffResult<usize>,
426
427    #[serde(skip_serializing_if = "DiffResult::is_none")]
428    pub required: DiffResult<VecDiff<String>>,
429    #[serde(skip_serializing_if = "DiffResult::is_none")]
430    pub r#enum: DiffResult<VecDiff<Value>>,
431
432    #[serde(skip_serializing_if = "DiffResult::is_none")]
433    pub r#type:
434        DiffResult<EitherDiff<String, VecDiff<String, TypeVecDiffSorter>>>,
435    #[serde(skip_serializing_if = "DiffResult::is_none")]
436    pub all_of: DiffResult<VecDiff<MayBeRefDiff<SchemaDiff>>>,
437    #[serde(skip_serializing_if = "DiffResult::is_none")]
438    pub one_of: DiffResult<VecDiff<MayBeRefDiff<SchemaDiff>>>,
439    #[serde(skip_serializing_if = "DiffResult::is_none")]
440    pub any_of: DiffResult<VecDiff<MayBeRefDiff<SchemaDiff>>>,
441    #[serde(skip_serializing_if = "DiffResult::is_none")]
442    pub not: DiffResult<VecDiff<MayBeRefDiff<SchemaDiff>>>,
443    #[serde(skip_serializing_if = "DiffResult::is_none")]
444    pub items: Box<DiffResult<MayBeRefDiff<SchemaDiff>>>,
445    #[serde(skip_serializing_if = "DiffResult::is_none")]
446    pub properties: DiffResult<MapDiff<MayBeRefDiff<SchemaDiff>>>,
447    #[serde(skip_serializing_if = "DiffResult::is_none")]
448    pub additional_properties:
449        DiffResult<EitherDiff<bool, MayBeRefDiff<SchemaDiff>>>,
450    #[serde(skip_serializing_if = "DiffResult::is_none")]
451    pub description: DiffResult<String>,
452    #[serde(skip_serializing_if = "DiffResult::is_none")]
453    pub format: DiffResult<String>,
454    #[serde(skip_serializing_if = "DiffResult::is_none")]
455    pub default: DiffResult<Value>,
456
457    #[serde(skip_serializing_if = "DiffResult::is_none")]
458    pub discriminator: DiffResult<DiscriminatorDiff>,
459    #[serde(skip_serializing_if = "DiffResult::is_none")]
460    pub read_only: DiffResult<bool>,
461    #[serde(skip_serializing_if = "DiffResult::is_none")]
462    pub write_only: DiffResult<bool>,
463    #[serde(skip_serializing_if = "DiffResult::is_none")]
464    pub xml: DiffResult<XmlDiff>,
465    #[serde(skip_serializing_if = "DiffResult::is_none")]
466    pub external_docs: DiffResult<ExternalDocDiff>,
467    #[serde(skip_serializing_if = "DiffResult::is_none")]
468    pub example: DiffResult<Value>,
469    #[serde(skip_serializing_if = "DiffResult::is_none")]
470    pub deprecated: DiffResult<bool>,
471
472    #[serde(skip_serializing_if = "check_custom_fields")]
473    pub custom_fields: DiffResult<MapDiff<Value>>,
474}
475
476impl Referencable for SchemaDiff {}
477
478#[derive(
479    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
480)]
481#[serde(rename_all = "camelCase")]
482pub struct HeaderDiff {
483    #[serde(skip_serializing_if = "DiffResult::is_none")]
484    pub description: DiffResult<String>,
485    #[serde(skip_serializing_if = "DiffResult::is_none")]
486    pub required: DiffResult<bool>,
487    #[serde(skip_serializing_if = "DiffResult::is_none")]
488    pub deprecated: DiffResult<bool>,
489    #[serde(skip_serializing_if = "DiffResult::is_none")]
490    pub allow_empty_value: DiffResult<bool>,
491    #[serde(skip_serializing_if = "DiffResult::is_none")]
492    pub style: DiffResult<String>,
493    #[serde(skip_serializing_if = "DiffResult::is_none")]
494    pub explode: DiffResult<bool>,
495    #[serde(skip_serializing_if = "DiffResult::is_none")]
496    pub allow_reserved: DiffResult<bool>,
497
498    #[serde(skip_serializing_if = "DiffResult::is_none")]
499    pub schema: DiffResult<MayBeRefDiff<SchemaDiff>>,
500
501    #[serde(skip_serializing_if = "DiffResult::is_none")]
502    pub examples: DiffResult<MapDiff<MayBeRefDiff<Value>>>,
503    #[serde(skip_serializing_if = "DiffResult::is_none")]
504    pub content: DiffResult<MapDiff<MediaTypeDiff>>,
505
506    #[serde(skip_serializing_if = "check_custom_fields")]
507    pub custom_fields: DiffResult<MapDiff<Value>>,
508}
509
510impl Referencable for HeaderDiff {}
511
512#[derive(
513    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
514)]
515#[serde(rename_all = "camelCase")]
516pub struct OperationDiff {
517    #[serde(skip_serializing_if = "DiffResult::is_none")]
518    pub tags: DiffResult<VecDiff<String>>,
519    #[serde(skip_serializing_if = "DiffResult::is_none")]
520    pub summary: DiffResult<String>,
521    #[serde(skip_serializing_if = "DiffResult::is_none")]
522    pub description: DiffResult<String>,
523
524    #[serde(skip_serializing_if = "DiffResult::is_none")]
525    pub external_docs: DiffResult<ExternalDocDiff>,
526
527    pub operation_id: DiffResult<String>,
528
529    #[serde(skip_serializing_if = "DiffResult::is_none")]
530    pub parameters: DiffResult<VecDiff<MayBeRefDiff<ParameterDiff>>>,
531    #[serde(skip_serializing_if = "DiffResult::is_none")]
532    pub responses: DiffResult<MapDiff<MayBeRefDiff<ResponseDiff>>>,
533
534    #[serde(skip_serializing_if = "DiffResult::is_none")]
535    pub request_body: DiffResult<MayBeRefDiff<RequestBodyDiff>>,
536    #[serde(skip_serializing_if = "DiffResult::is_none")]
537    pub servers: DiffResult<VecDiff<ServerDiff>>,
538
539    #[serde(skip_serializing_if = "DiffResult::is_none")]
540    pub security: DiffResult<VecDiff<MapDiff<VecDiff<String>>>>,
541
542    #[serde(skip_serializing_if = "DiffResult::is_none")]
543    pub deprecated: DiffResult<bool>,
544}
545
546#[derive(
547    Debug, Clone, Serialize, Deserialize, Empty, Diff, DiffOwnChanges,
548)]
549pub struct PathDiff {
550    #[serde(skip_serializing_if = "DiffResult::is_none")]
551    pub get: DiffResult<OperationDiff>,
552    #[serde(skip_serializing_if = "DiffResult::is_none")]
553    pub put: DiffResult<OperationDiff>,
554    #[serde(skip_serializing_if = "DiffResult::is_none")]
555    pub post: DiffResult<OperationDiff>,
556    #[serde(skip_serializing_if = "DiffResult::is_none")]
557    pub delete: DiffResult<OperationDiff>,
558    #[serde(skip_serializing_if = "DiffResult::is_none")]
559    pub options: DiffResult<OperationDiff>,
560    #[serde(skip_serializing_if = "DiffResult::is_none")]
561    pub head: DiffResult<OperationDiff>,
562    #[serde(skip_serializing_if = "DiffResult::is_none")]
563    pub patch: DiffResult<OperationDiff>,
564    #[serde(skip_serializing_if = "DiffResult::is_none")]
565    pub trace: DiffResult<OperationDiff>,
566
567    #[serde(skip_serializing_if = "DiffResult::is_none")]
568    pub servers: DiffResult<VecDiff<ServerDiff>>,
569
570    #[serde(skip_serializing_if = "DiffResult::is_none")]
571    pub summary: DiffResult<String>,
572    #[serde(skip_serializing_if = "DiffResult::is_none")]
573    pub description: DiffResult<String>,
574}
575
576impl Referencable for PathDiff {}
577
578pub(crate) fn deref_schema_diff<'a>(
579    diff: &'a HttpSchemaDiff,
580    may_be_ref: &'a MayBeRefDiff<SchemaDiff>,
581) -> Option<&'a DiffResult<SchemaDiff>> {
582    match may_be_ref {
583        MayBeRefDiff::Value(value) => return Some(value),
584        MayBeRefDiff::Ref(value) => {
585            if value.reference.starts_with("#/components/schemas/") {
586                let key = value.reference.replace("#/components/schemas/", "");
587                if diff.components.exists() {
588                    if let Some(components) = diff.components.get() {
589                        if let Some(schemas) = components.schemas.get() {
590                            if let Some(may_be_schema) = schemas.get(&key) {
591                                if let Some(MayBeRefDiff::Value(schema)) =
592                                    may_be_schema.get()
593                                {
594                                    return Some(schema);
595                                }
596                            }
597                        }
598                    }
599                }
600            }
601        }
602    };
603    None
604}
605
606pub(crate) fn deref_parameter_diff<'a>(
607    diff: &'a HttpSchemaDiff,
608    may_be_ref: &'a MayBeRefDiff<ParameterDiff>,
609) -> Option<&'a DiffResult<ParameterDiff>> {
610    match may_be_ref {
611        MayBeRefDiff::Value(value) => return Some(value),
612        MayBeRefDiff::Ref(value) => {
613            if value.reference.starts_with("#/components/parameters/") {
614                let key =
615                    value.reference.replace("#/components/parameters/", "");
616                if diff.components.exists() {
617                    if let Some(components) = diff.components.get() {
618                        if let Some(parameters) = components.parameters.get() {
619                            if let Some(may_be_parameter) =
620                                parameters.get(&key)
621                            {
622                                if let Some(MayBeRefDiff::Value(parameter)) =
623                                    may_be_parameter.get()
624                                {
625                                    return Some(parameter);
626                                }
627                            }
628                        }
629                    }
630                }
631            }
632        }
633    };
634    None
635}
636
637pub(crate) fn deref_request_body_diff<'a>(
638    diff: &'a HttpSchemaDiff,
639    may_be_ref: &'a MayBeRefDiff<RequestBodyDiff>,
640) -> Option<&'a DiffResult<RequestBodyDiff>> {
641    match may_be_ref {
642        MayBeRefDiff::Value(value) => return Some(value),
643        MayBeRefDiff::Ref(value) => {
644            if value.reference.starts_with("#/components/requestBodies/") {
645                let key =
646                    value.reference.replace("#/components/requestBodies/", "");
647                if diff.components.exists() {
648                    if let Some(components) = diff.components.get() {
649                        if let Some(request_bodies) =
650                            components.request_bodies.get()
651                        {
652                            if let Some(request_body) =
653                                request_bodies.get(&key)
654                            {
655                                if let Some(MayBeRefDiff::Value(
656                                    request_body,
657                                )) = request_body.get()
658                                {
659                                    return Some(request_body);
660                                }
661                            }
662                        }
663                    }
664                }
665            }
666        }
667    };
668    None
669}
670
671pub(crate) fn deref_response_diff<'a>(
672    diff: &'a HttpSchemaDiff,
673    may_be_ref: &'a MayBeRefDiff<ResponseDiff>,
674) -> Option<&'a DiffResult<ResponseDiff>> {
675    match may_be_ref {
676        MayBeRefDiff::Value(value) => return Some(value),
677        MayBeRefDiff::Ref(value) => {
678            if value.reference.starts_with("#/components/responses/") {
679                let key =
680                    value.reference.replace("#/components/responses/", "");
681                if diff.components.exists() {
682                    if let Some(components) = diff.components.get() {
683                        if let Some(responses) = components.responses.get() {
684                            if let Some(response) = responses.get(&key) {
685                                if let Some(MayBeRefDiff::Value(response)) =
686                                    response.get()
687                                {
688                                    return Some(response);
689                                }
690                            }
691                        }
692                    }
693                }
694            }
695        }
696    };
697    None
698}
699
700#[cfg(test)]
701mod tests {
702    use crate::core::Either;
703    use crate::schema::*;
704
705    #[test]
706    fn check_operation() {
707        let op_def = r#"{
708      "post": {
709        "tags": ["Nodes"],
710        "summary": "Export Xlsx Template",
711        "description": "Generate XLSX-template for aggregated node data editing",
712        "operationId": "export_xlsx_template_api_v2_nodes__path__template_generate__post",
713        "parameters": [
714          {
715            "required": true,
716            "schema": { "title": "Path", "type": "string" },
717            "name": "path",
718            "in": "path"
719          },
720          {
721            "required": false,
722            "schema": { "title": "Update Sender", "type": "string" },
723            "name": "update_sender",
724            "in": "query"
725          },
726          {
727            "required": false,
728            "schema": { "title": "Force", "type": "boolean", "default": false },
729            "name": "force",
730            "in": "query"
731          },
732          {
733            "required": false,
734            "schema": { "title": "Compound Amount", "type": "integer" },
735            "name": "compound_amount",
736            "in": "query"
737          },
738          {
739            "required": false,
740            "schema": {
741              "allOf": [{ "$ref": "/components/schemas/ExportFmt" }],
742              "default": "xlsx"
743            },
744            "name": "export_format",
745            "in": "query"
746          }
747        ],
748        "requestBody": {
749          "content": {
750            "application/json": {
751              "schema": {
752                "$ref": "/components/schemas/Body_export_xlsx_template_api_v2_nodes__path__template_generate__post"
753              }
754            }
755          }
756        },
757        "responses": {
758          "200": {
759            "description": "Successful Response",
760            "content": {
761              "application/json": { "schema": {} },
762              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {}
763            }
764          },
765          "422": {
766            "description": "Validation Error",
767            "content": {
768              "application/json": {
769                "schema": { "$ref": "/components/schemas/HTTPValidationError" }
770              }
771            }
772          }
773        },
774        "security": [{ "OAuth2PasswordBearer": [] }]
775      }
776    }"#;
777
778        let _: Path = serde_json::from_str(op_def).unwrap();
779    }
780
781    #[test]
782    fn check_schema_additional_properties() {
783        let op_def = r#"{
784            "title": "AdditionalProperties",
785            "type": "object",
786            "additionalProperties": {
787              "$ref": "/components/schemas/AdditionalProperties"
788            }
789          }"#;
790
791        let op: Schema = serde_json::from_str(op_def).unwrap();
792        assert!(matches!(op.additional_properties, Some(Either::Right(_))));
793
794        let op_def = r#"{
795            "title": "AdditionalProperties",
796            "type": "object",
797            "additionalProperties": false
798          }"#;
799
800        let op: Schema = serde_json::from_str(op_def).unwrap();
801        assert!(matches!(op.additional_properties, Some(Either::Left(_))));
802
803        let sc_def = r#"
804        {
805        "type": "object",
806        "discriminator": { "propertyName": "type" },
807        "properties": {
808          "type": {
809            "type": "string",
810            "description": "The type of context being attached to the entity.",
811            "enum": ["link", "text", "image"]
812          }
813        },
814        "required": ["type"]
815      }
816        "#;
817        let op: Schema = serde_json::from_str(sc_def).unwrap();
818        assert!(matches!(op.discriminator, Some(_)))
819    }
820}