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