1use std::collections::{BTreeMap, BTreeSet};
5
6use smol_str::SmolStr;
7
8use super::{PyComponent, PyEnum, PyModule, PyProperty, PyStruct, PyStructField, PyStructOrEnum};
9
10#[cfg(test)]
11use super::PyEnumVariant;
12
13impl PyModule {
14 pub fn changed_globals(&self, other: &Self) -> Option<PyComponentsDifference> {
15 PyComponentsDifference::compare(&self.globals, &other.globals)
16 }
17
18 pub fn changed_components(&self, other: &Self) -> Option<PyComponentsDifference> {
19 PyComponentsDifference::compare(&self.components, &other.components)
20 }
21
22 pub fn changed_structs_or_enums(&self, other: &Self) -> Option<PyStructsOrEnumsDifference> {
23 PyStructsOrEnumsDifference::compare(&self.structs_and_enums, &other.structs_and_enums)
24 }
25}
26
27pub struct PyComponentsDifference {
28 pub added_components: Vec<SmolStr>,
29 pub removed_components: Vec<SmolStr>,
30 pub changed_components: Vec<(SmolStr, ComponentDifference)>,
31}
32
33impl PyComponentsDifference {
34 fn compare(orig: &[PyComponent], new: &[PyComponent]) -> Option<Self> {
35 let orig_components = orig
36 .iter()
37 .map(|compo| (compo.name.as_str(), compo))
38 .collect::<BTreeMap<&str, &PyComponent>>();
39
40 let new_components = new
41 .iter()
42 .map(|compo| (compo.name.as_str(), compo))
43 .collect::<BTreeMap<&str, &PyComponent>>();
44
45 let added_components = new_components
46 .iter()
47 .filter_map(|(name, _)| {
48 if orig_components.contains_key(name) { None } else { Some((*name).into()) }
49 })
50 .collect::<Vec<_>>();
51
52 let removed_components =
53 orig_components
54 .iter()
55 .filter_map(|(name, _)| {
56 if new_components.contains_key(name) { None } else { Some((*name).into()) }
57 })
58 .collect::<Vec<_>>();
59
60 let changed_components = orig_components
61 .iter()
62 .filter_map(|(name, orig_global)| {
63 let new_glob = new_components.get(name)?;
64
65 let diff = ComponentDifference::compare(&orig_global, &new_glob);
66
67 diff.map(|diff| ((*name).into(), diff))
68 })
69 .collect::<Vec<_>>();
70
71 if !added_components.is_empty()
72 || !removed_components.is_empty()
73 || !changed_components.is_empty()
74 {
75 Some(PyComponentsDifference {
76 added_components,
77 removed_components,
78 changed_components,
79 })
80 } else {
81 None
82 }
83 }
84
85 pub fn incompatible_changes(&self) -> bool {
86 !self.removed_components.is_empty()
87 || self.changed_components.iter().any(|(_, change)| change.incompatible_changes())
88 }
89}
90
91#[derive(PartialEq, Debug)]
92pub struct TypeChange {
93 pub name: SmolStr,
94 pub old_type: SmolStr,
95 pub new_type: SmolStr,
96}
97
98#[derive(PartialEq, Debug)]
99pub struct ComponentDifference {
100 pub added_properties: Vec<PyProperty>,
102 pub removed_properties: Vec<PyProperty>,
103 pub type_changed_properties: Vec<TypeChange>,
104 pub added_aliases: Vec<SmolStr>,
105 pub removed_aliases: Vec<SmolStr>,
106}
107
108impl ComponentDifference {
109 fn compare(old_compo: &PyComponent, new_compo: &PyComponent) -> Option<Self> {
110 let orig_props = old_compo
111 .properties
112 .iter()
113 .map(|p| (p.name.as_str(), p))
114 .collect::<BTreeMap<&str, &PyProperty>>();
115 let new_props = new_compo
116 .properties
117 .iter()
118 .map(|p| (p.name.as_str(), p))
119 .collect::<BTreeMap<&str, &PyProperty>>();
120
121 let added_properties = new_props
122 .iter()
123 .filter_map(|(name, new_prop)| {
124 if orig_props.contains_key(name) { None } else { Some((*new_prop).clone()) }
125 })
126 .collect::<Vec<_>>();
127
128 let removed_properties =
129 orig_props
130 .iter()
131 .filter_map(|(name, old_prop)| {
132 if new_props.contains_key(name) { None } else { Some((*old_prop).clone()) }
133 })
134 .collect::<Vec<_>>();
135
136 let type_changed_properties = orig_props
137 .iter()
138 .filter_map(|(name, orig_prop)| {
139 let new_prop = new_props.get(name)?;
140
141 if orig_prop.ty != new_prop.ty {
142 Some(TypeChange {
143 name: (*name).into(),
144 old_type: orig_prop.ty.clone(),
145 new_type: new_prop.ty.clone(),
146 })
147 } else {
148 None
149 }
150 })
151 .collect::<Vec<_>>();
152
153 let old_aliases = old_compo.aliases.iter().collect::<BTreeSet<_>>();
154 let new_aliases = new_compo.aliases.iter().collect::<BTreeSet<_>>();
155
156 let added_aliases =
157 new_aliases.difference(&old_aliases).map(|s| (*s).clone()).collect::<Vec<_>>();
158 let removed_aliases =
159 old_aliases.difference(&new_aliases).map(|s| (*s).clone()).collect::<Vec<_>>();
160
161 let diff = Self {
162 added_properties,
163 removed_properties,
164 type_changed_properties,
165 added_aliases,
166 removed_aliases,
167 };
168 if diff.has_difference() { Some(diff) } else { None }
169 }
170
171 fn has_difference(&self) -> bool {
172 !self.added_properties.is_empty()
173 || !self.removed_properties.is_empty()
174 || !self.type_changed_properties.is_empty()
175 || !self.added_aliases.is_empty()
176 || !self.removed_aliases.is_empty()
177 }
178
179 fn incompatible_changes(&self) -> bool {
180 !self.removed_properties.is_empty()
181 || !self.type_changed_properties.is_empty()
182 || !self.removed_aliases.is_empty()
183 }
184}
185
186pub struct PyStructsOrEnumsDifference {
187 pub added_structs: Vec<SmolStr>,
188 pub removed_structs: Vec<SmolStr>,
189 pub changed_structs: Vec<(SmolStr, StructDifference)>,
190 pub added_enums: Vec<SmolStr>,
191 pub removed_enums: Vec<SmolStr>,
192 pub changed_enums: Vec<(SmolStr, EnumDifference)>,
193}
194
195impl PyStructsOrEnumsDifference {
196 fn compare(orig: &[PyStructOrEnum], new: &[PyStructOrEnum]) -> Option<Self> {
197 let mut orig_structs = BTreeMap::new();
198 let mut orig_enums = BTreeMap::new();
199 for struct_or_enum in orig {
200 match struct_or_enum {
201 PyStructOrEnum::Struct(py_struct) => {
202 orig_structs.insert(py_struct.name.as_str(), py_struct);
203 }
204 PyStructOrEnum::Enum(py_enum) => {
205 orig_enums.insert(py_enum.name.as_str(), py_enum);
206 }
207 }
208 }
209
210 let mut new_structs = BTreeMap::new();
211 let mut new_enums = BTreeMap::new();
212 for struct_or_enum in new {
213 match struct_or_enum {
214 PyStructOrEnum::Struct(py_struct) => {
215 new_structs.insert(py_struct.name.as_str(), py_struct);
216 }
217 PyStructOrEnum::Enum(py_enum) => {
218 new_enums.insert(py_enum.name.as_str(), py_enum);
219 }
220 }
221 }
222
223 let added_structs =
224 new_structs
225 .iter()
226 .filter_map(|(name, _)| {
227 if orig_structs.contains_key(name) { None } else { Some((*name).into()) }
228 })
229 .collect::<Vec<_>>();
230
231 let added_enums = new_enums
232 .iter()
233 .filter_map(
234 |(name, _)| {
235 if orig_enums.contains_key(name) { None } else { Some((*name).into()) }
236 },
237 )
238 .collect::<Vec<_>>();
239
240 let removed_structs =
241 orig_structs
242 .iter()
243 .filter_map(|(name, _)| {
244 if new_structs.contains_key(name) { None } else { Some((*name).into()) }
245 })
246 .collect::<Vec<_>>();
247
248 let removed_enums = orig_enums
249 .iter()
250 .filter_map(
251 |(name, _)| {
252 if new_enums.contains_key(name) { None } else { Some((*name).into()) }
253 },
254 )
255 .collect::<Vec<_>>();
256
257 let changed_structs = orig_structs
258 .iter()
259 .filter_map(|(name, orig_struct)| {
260 let new_struct = new_structs.get(name)?;
261
262 let diff = StructDifference::compare(&orig_struct, &new_struct);
263
264 diff.map(|diff| ((*name).into(), diff))
265 })
266 .collect::<Vec<_>>();
267
268 let changed_enums = orig_enums
269 .iter()
270 .filter_map(|(name, orig_enum)| {
271 let new_enum = new_enums.get(name)?;
272
273 let diff = EnumDifference::compare(&orig_enum, &new_enum);
274
275 diff.map(|diff| ((*name).into(), diff))
276 })
277 .collect::<Vec<_>>();
278
279 if !added_structs.is_empty()
280 || !removed_structs.is_empty()
281 || !changed_structs.is_empty()
282 || !added_enums.is_empty()
283 || !removed_enums.is_empty()
284 || !changed_enums.is_empty()
285 {
286 Some(Self {
287 added_structs,
288 removed_structs,
289 changed_structs,
290 added_enums,
291 removed_enums,
292 changed_enums,
293 })
294 } else {
295 None
296 }
297 }
298
299 pub fn incompatible_changes(&self) -> bool {
300 !self.removed_structs.is_empty()
301 || !self.removed_enums.is_empty()
302 || self.changed_structs.iter().any(|(_, c)| c.incompatible_changes())
303 || self.changed_enums.iter().any(|(_, c)| c.incompatible_changes())
304 }
305}
306
307#[derive(PartialEq, Debug)]
308pub struct StructDifference {
309 pub added_fields: Vec<PyStructField>,
310 pub removed_fields: Vec<PyStructField>,
311 pub type_changed_fields: Vec<TypeChange>,
312 pub added_aliases: Vec<SmolStr>,
313 pub removed_aliases: Vec<SmolStr>,
314}
315
316impl StructDifference {
317 fn compare(old_struct: &PyStruct, new_struct: &PyStruct) -> Option<Self> {
318 let orig_fields = old_struct
319 .fields
320 .iter()
321 .map(|f| (f.name.as_str(), f))
322 .collect::<BTreeMap<&str, &PyStructField>>();
323 let new_fields = new_struct
324 .fields
325 .iter()
326 .map(|f| (f.name.as_str(), f))
327 .collect::<BTreeMap<&str, &PyStructField>>();
328
329 let added_fields = new_fields
330 .iter()
331 .filter_map(|(name, new_field)| {
332 if orig_fields.contains_key(name) { None } else { Some((*new_field).clone()) }
333 })
334 .collect::<Vec<_>>();
335
336 let removed_fields = orig_fields
337 .iter()
338 .filter_map(|(name, old_field)| {
339 if new_fields.contains_key(name) { None } else { Some((*old_field).clone()) }
340 })
341 .collect::<Vec<_>>();
342
343 let type_changed_fields = orig_fields
344 .iter()
345 .filter_map(|(name, orig_field)| {
346 let new_field = new_fields.get(name)?;
347
348 if orig_field.ty != new_field.ty {
349 Some(TypeChange {
350 name: (*name).into(),
351 old_type: orig_field.ty.clone(),
352 new_type: new_field.ty.clone(),
353 })
354 } else {
355 None
356 }
357 })
358 .collect::<Vec<_>>();
359
360 let old_aliases = old_struct.aliases.iter().collect::<BTreeSet<_>>();
361 let new_aliases = new_struct.aliases.iter().collect::<BTreeSet<_>>();
362
363 let added_aliases =
364 new_aliases.difference(&old_aliases).map(|s| (*s).clone()).collect::<Vec<_>>();
365 let removed_aliases =
366 old_aliases.difference(&new_aliases).map(|s| (*s).clone()).collect::<Vec<_>>();
367
368 let diff = Self {
369 added_fields,
370 removed_fields,
371 type_changed_fields,
372 added_aliases,
373 removed_aliases,
374 };
375 if diff.has_difference() { Some(diff) } else { None }
376 }
377
378 fn has_difference(&self) -> bool {
379 !self.added_fields.is_empty()
380 || !self.removed_fields.is_empty()
381 || !self.type_changed_fields.is_empty()
382 || !self.added_aliases.is_empty()
383 || !self.removed_aliases.is_empty()
384 }
385
386 fn incompatible_changes(&self) -> bool {
387 !self.removed_fields.is_empty()
388 || !self.removed_aliases.is_empty()
389 || !self.type_changed_fields.is_empty()
390 }
391}
392
393#[derive(Debug, PartialEq)]
394pub struct EnumDifference {
395 pub added_variants: Vec<SmolStr>,
396 pub removed_variants: Vec<SmolStr>,
397 pub added_aliases: Vec<SmolStr>,
398 pub removed_aliases: Vec<SmolStr>,
399}
400
401impl EnumDifference {
402 fn compare(old_enum: &PyEnum, new_enum: &PyEnum) -> Option<Self> {
403 let old_variants = old_enum.variants.iter().map(|v| &v.name).collect::<BTreeSet<_>>();
404 let new_variants = new_enum.variants.iter().map(|v| &v.name).collect::<BTreeSet<_>>();
405
406 let added_variants =
407 new_variants.difference(&old_variants).map(|s| (*s).clone()).collect::<Vec<_>>();
408 let removed_variants =
409 old_variants.difference(&new_variants).map(|s| (*s).clone()).collect::<Vec<_>>();
410
411 let old_aliases = old_enum.aliases.iter().collect::<BTreeSet<_>>();
412 let new_aliases = new_enum.aliases.iter().collect::<BTreeSet<_>>();
413
414 let added_aliases =
415 new_aliases.difference(&old_aliases).map(|s| (*s).clone()).collect::<Vec<_>>();
416 let removed_aliases =
417 old_aliases.difference(&new_aliases).map(|s| (*s).clone()).collect::<Vec<_>>();
418
419 let diff = Self { added_variants, removed_variants, added_aliases, removed_aliases };
420 if diff.has_difference() { Some(diff) } else { None }
421 }
422
423 fn has_difference(&self) -> bool {
424 !self.added_variants.is_empty()
425 || !self.removed_variants.is_empty()
426 || !self.added_aliases.is_empty()
427 || !self.removed_aliases.is_empty()
428 }
429
430 fn incompatible_changes(&self) -> bool {
431 !self.removed_variants.is_empty() || !self.removed_aliases.is_empty()
432 }
433}
434
435#[test]
436fn globals() {
437 let old = super::PyModule {
438 globals: vec![
439 PyComponent {
440 name: SmolStr::new_static("SameGlobal"),
441 properties: vec![PyProperty {
442 name: SmolStr::new_static("str_prop"),
443 ty: SmolStr::new_static("str"),
444 }],
445 aliases: vec![SmolStr::new_static("SameGlobalAlias")],
446 },
447 PyComponent {
448 name: SmolStr::new_static("ChangedGlobal"),
449 properties: vec![
450 PyProperty {
451 name: SmolStr::new_static("same_str_prop"),
452 ty: SmolStr::new_static("str"),
453 },
454 PyProperty {
455 name: SmolStr::new_static("change_to_int_prop"),
456 ty: SmolStr::new_static("str"),
457 },
458 PyProperty {
459 name: SmolStr::new_static("removed_prop"),
460 ty: SmolStr::new_static("int"),
461 },
462 ],
463 aliases: vec![SmolStr::new_static("ChangedGlobalAlias")],
464 },
465 PyComponent {
466 name: SmolStr::new_static("ToBeRemoved"),
467 properties: Vec::new(),
468 aliases: Vec::new(),
469 },
470 ],
471 ..Default::default()
472 };
473
474 let new = super::PyModule {
475 globals: vec![
476 PyComponent {
477 name: SmolStr::new_static("SameGlobal"),
478 properties: vec![PyProperty {
479 name: SmolStr::new_static("str_prop"),
480 ty: SmolStr::new_static("str"),
481 }],
482 aliases: vec![SmolStr::new_static("SameGlobalAlias")],
483 },
484 PyComponent {
485 name: SmolStr::new_static("ChangedGlobal"),
486 properties: vec![
487 PyProperty {
488 name: SmolStr::new_static("same_str_prop"),
489 ty: SmolStr::new_static("str"),
490 },
491 PyProperty {
492 name: SmolStr::new_static("change_to_int_prop"),
493 ty: SmolStr::new_static("int"),
494 },
495 PyProperty {
496 name: SmolStr::new_static("new_prop"),
497 ty: SmolStr::new_static("float"),
498 },
499 ],
500 aliases: vec![SmolStr::new_static("NewGlobalAlias")],
501 },
502 PyComponent {
503 name: SmolStr::new_static("NewGlobal"),
504 properties: vec![PyProperty {
505 name: SmolStr::new_static("str_prop"),
506 ty: SmolStr::new_static("str"),
507 }],
508 aliases: Vec::new(),
509 },
510 ],
511 ..Default::default()
512 };
513
514 assert!(old.changed_globals(&old).is_none());
515
516 let changed = old.changed_globals(&new);
517 assert!(changed.is_some());
518 let changed = changed.unwrap();
519
520 assert_eq!(changed.added_components, vec![SmolStr::new_static("NewGlobal")]);
521 assert_eq!(changed.removed_components, vec![SmolStr::new_static("ToBeRemoved")]);
522
523 let expected_glob_change = ComponentDifference {
524 added_properties: vec![PyProperty {
525 name: SmolStr::new_static("new_prop"),
526 ty: SmolStr::new_static("float"),
527 }],
528 removed_properties: vec![PyProperty {
529 name: SmolStr::new_static("removed_prop"),
530 ty: SmolStr::new_static("int"),
531 }],
532 type_changed_properties: vec![TypeChange {
533 name: SmolStr::new_static("change_to_int_prop"),
534 old_type: SmolStr::new_static("str"),
535 new_type: SmolStr::new_static("int"),
536 }],
537 added_aliases: vec![SmolStr::new_static("NewGlobalAlias")],
538 removed_aliases: vec![SmolStr::new_static("ChangedGlobalAlias")],
539 };
540
541 assert_eq!(
542 changed.changed_components,
543 vec![(SmolStr::new_static("ChangedGlobal"), expected_glob_change)]
544 );
545}
546
547#[test]
548fn structs_and_enums() {
549 let old = super::PyModule {
550 structs_and_enums: vec![
551 PyStructOrEnum::Struct(PyStruct {
552 name: SmolStr::new_static("SameStruct"),
553 fields: vec![PyStructField {
554 name: SmolStr::new_static("intfield"),
555 ty: SmolStr::new_static("int"),
556 }],
557 aliases: vec![SmolStr::new_static("SameStructalias")],
558 }),
559 PyStructOrEnum::Struct(PyStruct {
560 name: SmolStr::new_static("StructWithChangedFields"),
561 fields: vec![
562 PyStructField {
563 name: SmolStr::new_static("removed_field"),
564 ty: SmolStr::new_static("str"),
565 },
566 PyStructField {
567 name: SmolStr::new_static("unchanged_field"),
568 ty: SmolStr::new_static("str"),
569 },
570 PyStructField {
571 name: SmolStr::new_static("to_int_field"),
572 ty: SmolStr::new_static("float"),
573 },
574 ],
575 aliases: vec![SmolStr::new_static("RemovedAlias")],
576 }),
577 PyStructOrEnum::Struct(PyStruct {
578 name: SmolStr::new_static("RemovedStruct"),
579 fields: Vec::new(),
580 aliases: Vec::new(),
581 }),
582 PyStructOrEnum::Struct(PyStruct {
583 name: SmolStr::new_static("StructBecomesEnum"),
584 fields: Vec::new(),
585 aliases: Vec::new(),
586 }),
587 PyStructOrEnum::Enum(PyEnum {
588 name: SmolStr::new_static("SameEnum"),
589 variants: vec![
590 PyEnumVariant {
591 name: SmolStr::new_static("Variant1"),
592 strvalue: SmolStr::new_static("Variant1"),
593 },
594 PyEnumVariant {
595 name: SmolStr::new_static("Variant2"),
596 strvalue: SmolStr::new_static("Variant2"),
597 },
598 ],
599 aliases: vec![SmolStr::new_static("SameEnumAlias")],
600 }),
601 PyStructOrEnum::Enum(PyEnum {
602 name: SmolStr::new_static("ChangedEnum"),
603 variants: vec![
604 PyEnumVariant {
605 name: SmolStr::new_static("Variant1"),
606 strvalue: SmolStr::new_static("Variant1"),
607 },
608 PyEnumVariant {
609 name: SmolStr::new_static("Variant2"),
610 strvalue: SmolStr::new_static("Variant2"),
611 },
612 ],
613 aliases: vec![SmolStr::new_static("ChangedEnumRemovedAlias")],
614 }),
615 PyStructOrEnum::Enum(PyEnum {
616 name: SmolStr::new_static("RemovedEnum"),
617 variants: vec![
618 PyEnumVariant {
619 name: SmolStr::new_static("Variant1"),
620 strvalue: SmolStr::new_static("Variant1"),
621 },
622 PyEnumVariant {
623 name: SmolStr::new_static("Variant2"),
624 strvalue: SmolStr::new_static("Variant2"),
625 },
626 ],
627 aliases: Vec::new(),
628 }),
629 ],
630 ..Default::default()
631 };
632
633 let new = super::PyModule {
634 structs_and_enums: vec![
635 PyStructOrEnum::Struct(PyStruct {
636 name: SmolStr::new_static("SameStruct"),
637 fields: vec![PyStructField {
638 name: SmolStr::new_static("intfield"),
639 ty: SmolStr::new_static("int"),
640 }],
641 aliases: vec![SmolStr::new_static("SameStructalias")],
642 }),
643 PyStructOrEnum::Struct(PyStruct {
644 name: SmolStr::new_static("StructWithChangedFields"),
645 fields: vec![
646 PyStructField {
647 name: SmolStr::new_static("added_field"),
648 ty: SmolStr::new_static("str"),
649 },
650 PyStructField {
651 name: SmolStr::new_static("unchanged_field"),
652 ty: SmolStr::new_static("str"),
653 },
654 PyStructField {
655 name: SmolStr::new_static("to_int_field"),
656 ty: SmolStr::new_static("int"),
657 },
658 ],
659 aliases: vec![SmolStr::new_static("NewAlias")],
660 }),
661 PyStructOrEnum::Struct(PyStruct {
662 name: SmolStr::new_static("AddedStruct"),
663 fields: Vec::new(),
664 aliases: Vec::new(),
665 }),
666 PyStructOrEnum::Enum(PyEnum {
667 name: SmolStr::new_static("StructBecomesEnum"),
668 variants: vec![
669 PyEnumVariant {
670 name: SmolStr::new_static("Variant1"),
671 strvalue: SmolStr::new_static("Variant1"),
672 },
673 PyEnumVariant {
674 name: SmolStr::new_static("Variant2"),
675 strvalue: SmolStr::new_static("Variant2"),
676 },
677 ],
678 aliases: Vec::new(),
679 }),
680 PyStructOrEnum::Enum(PyEnum {
681 name: SmolStr::new_static("SameEnum"),
682 variants: vec![
683 PyEnumVariant {
684 name: SmolStr::new_static("Variant1"),
685 strvalue: SmolStr::new_static("Variant1"),
686 },
687 PyEnumVariant {
688 name: SmolStr::new_static("Variant2"),
689 strvalue: SmolStr::new_static("Variant2"),
690 },
691 ],
692 aliases: vec![SmolStr::new_static("SameEnumAlias")],
693 }),
694 PyStructOrEnum::Enum(PyEnum {
695 name: SmolStr::new_static("ChangedEnum"),
696 variants: vec![
697 PyEnumVariant {
698 name: SmolStr::new_static("Variant3"),
699 strvalue: SmolStr::new_static("Variant3"),
700 },
701 PyEnumVariant {
702 name: SmolStr::new_static("Variant4"),
703 strvalue: SmolStr::new_static("Variant4"),
704 },
705 ],
706 aliases: vec![SmolStr::new_static("ChangedEnumAddedAlias")],
707 }),
708 PyStructOrEnum::Enum(PyEnum {
709 name: SmolStr::new_static("AddedEnum"),
710 variants: vec![
711 PyEnumVariant {
712 name: SmolStr::new_static("Variant1"),
713 strvalue: SmolStr::new_static("Variant1"),
714 },
715 PyEnumVariant {
716 name: SmolStr::new_static("Variant2"),
717 strvalue: SmolStr::new_static("Variant2"),
718 },
719 ],
720 aliases: Vec::new(),
721 }),
722 ],
723 ..Default::default()
724 };
725
726 assert!(old.changed_structs_or_enums(&old).is_none());
727
728 let changed = old.changed_structs_or_enums(&new);
729 assert!(changed.is_some());
730 let changed = changed.unwrap();
731
732 assert_eq!(changed.added_structs, vec![SmolStr::new_static("AddedStruct")]);
733 assert_eq!(
734 changed.removed_structs,
735 vec![SmolStr::new_static("RemovedStruct"), SmolStr::new_static("StructBecomesEnum")]
736 );
737
738 assert_eq!(
739 changed.added_enums,
740 vec![SmolStr::new_static("AddedEnum"), SmolStr::new_static("StructBecomesEnum")]
741 );
742 assert_eq!(changed.removed_enums, vec![SmolStr::new_static("RemovedEnum")]);
743
744 let expected_struct_change = StructDifference {
745 added_fields: vec![PyStructField {
746 name: SmolStr::new_static("added_field"),
747 ty: SmolStr::new_static("str"),
748 }],
749 removed_fields: vec![PyStructField {
750 name: SmolStr::new_static("removed_field"),
751 ty: SmolStr::new_static("str"),
752 }],
753 type_changed_fields: vec![TypeChange {
754 name: SmolStr::new_static("to_int_field"),
755 old_type: SmolStr::new_static("float"),
756 new_type: SmolStr::new_static("int"),
757 }],
758 added_aliases: vec![SmolStr::new_static("NewAlias")],
759 removed_aliases: vec![SmolStr::new_static("RemovedAlias")],
760 };
761
762 assert_eq!(
763 changed.changed_structs,
764 vec![(SmolStr::new_static("StructWithChangedFields"), expected_struct_change)]
765 );
766
767 let expected_enum_change = EnumDifference {
768 added_variants: vec![SmolStr::new_static("Variant3"), SmolStr::new_static("Variant4")],
769 removed_variants: vec![SmolStr::new_static("Variant1"), SmolStr::new_static("Variant2")],
770 added_aliases: vec![SmolStr::new_static("ChangedEnumAddedAlias")],
771 removed_aliases: vec![SmolStr::new_static("ChangedEnumRemovedAlias")],
772 };
773
774 assert_eq!(
775 changed.changed_enums,
776 vec![(SmolStr::new_static("ChangedEnum"), expected_enum_change)]
777 );
778}