chunkedge_item/
lib.rs

1mod components;
2mod impls;
3mod stack;
4mod vanilla_components;
5
6pub(crate) const NUM_ITEM_COMPONENTS: usize = 96;
7/// Controls the maximum recursion depth for encoding and decoding item
8/// components.
9pub(crate) const MAX_RECURSION_DEPTH: usize = 16;
10
11pub use chunkedge_generated::item::ItemKind;
12
13pub use crate::components::{
14    AttributeModifier, AttributeSlot, AxolotlType, BannerLayer, BannerPattern, BeeData,
15    BlockPredicate, ConsumableAnimation, ConsumeEffect, ConsumeEffectData, DamageReduction,
16    DyeColor, EquipSlot, ExactComponentMatcher, FireworkExplosionData, FoxType, HorseColor,
17    InstrumentDefinition, ItemComponent, JukeboxSong, LlamaColor, LodestoneTarget,
18    MapPostProcessingType, ModePair, MooshroomType, PaintingVariantDefinition, ParrotType,
19    PartialComponentMatcher, PotionEffect, ProfileProperty, Property, PropertyValue, RabbitType,
20    Rarity, ResolvableProfile, SalmonScale, SoundEventDefinition, ToolRule, TrimMaterial,
21    TrimPattern, TropicalFishPattern, WritablePage, WrittenPage,
22};
23pub use crate::impls::decode_item_stack_recursive;
24pub use crate::stack::{HashedItemStack, ItemStack};
25
26#[cfg(test)]
27mod tests {
28    use chunkedge_binary::{Decode, Encode, VarInt};
29    use chunkedge_generated::attributes::EntityAttributeOperation;
30    use chunkedge_generated::item::ItemKind;
31    use chunkedge_generated::registry_id::RegistryId;
32    use chunkedge_ident::ident;
33    use chunkedge_nbt::Compound;
34    use chunkedge_text::Text;
35
36    use super::*;
37    use crate::components::{
38        AttributeModifier, AttributeSlot, DyeColor, ModePair, Patchable, PropertyValue, Rarity,
39    };
40
41    // --- Helpers ---
42
43    fn create_test_stack(item: ItemKind, count: i8) -> ItemStack {
44        ItemStack::new(item, count)
45    }
46
47    fn roundtrip<T: Encode + for<'a> Decode<'a> + PartialEq + std::fmt::Debug>(val: &T) {
48        let mut buf = Vec::new();
49        val.encode(&mut buf).expect("Failed to encode");
50        let mut slice = buf.as_slice();
51        let decoded = T::decode(&mut slice).expect("Failed to decode");
52        assert_eq!(val, &decoded, "Roundtrip failed equality check");
53        assert!(slice.is_empty(), "Buffer not fully consumed");
54    }
55
56    // --- Patchable Tests ---
57
58    #[test]
59    fn test_patchable_logic() {
60        let p_added = Patchable::Added((Box::new(10), 123));
61        let p_default = Patchable::Default(Box::new(5));
62        let p_removed: Patchable<Box<i32>> = Patchable::Removed;
63        let p_none: Patchable<Box<i32>> = Patchable::None;
64
65        assert_eq!(p_added.as_option().map(|v| **v), Some(10));
66        assert_eq!(p_default.as_option().map(|v| **v), Some(5));
67        assert_eq!(p_removed.as_option(), None);
68        assert_eq!(p_none.as_option(), None);
69    }
70
71    // --- ItemStack Logical Tests ---
72
73    #[test]
74    fn test_item_stack_empty() {
75        let empty = ItemStack::EMPTY;
76        assert!(empty.is_empty());
77
78        let air = ItemStack::new(ItemKind::Air, 1);
79        assert!(air.is_empty());
80
81        let stack = ItemStack::new(ItemKind::Diamond, 0);
82        assert!(stack.is_empty());
83
84        let stack = ItemStack::new(ItemKind::Diamond, -1);
85        assert!(stack.is_empty());
86    }
87
88    #[test]
89    fn test_component_insertion_and_removal() {
90        let mut stack = create_test_stack(ItemKind::Diamond, 1);
91
92        // Test Insert
93        let custom_name = ItemComponent::CustomName(Text::from("Test Item").into());
94        stack.insert_component(custom_name.clone());
95
96        assert_eq!(stack.get_component(5_usize), Some(&custom_name));
97        assert_eq!(stack.components().len(), 1);
98
99        // Test Remove
100        let removed = stack.remove_component(5_usize);
101        assert_eq!(removed, Some(custom_name));
102        assert_eq!(stack.get_component(5_usize), None);
103
104        // Ensure "Removed" patch is applied (important for serialization)
105        assert!(matches!(stack.components[5], Patchable::Removed));
106    }
107
108    // --- Serialization Roundtrips ---
109
110    #[test]
111    fn test_serialization_basic_stack() {
112        let stack = ItemStack::new(ItemKind::Stone, 32);
113        roundtrip(&stack);
114    }
115
116    #[test]
117    fn test_serialization_with_complex_components() {
118        let mut stack = ItemStack::new(ItemKind::DiamondSword, 1);
119
120        // Add multiple types of components
121        stack.insert_component(ItemComponent::Damage(VarInt(50)));
122        stack.insert_component(ItemComponent::Rarity(Rarity::Epic));
123
124        // Test vec-based components (Enchantments)
125        stack.insert_component(ItemComponent::Enchantments(vec![(
126            RegistryId::new(1),
127            VarInt(5),
128        )]));
129
130        roundtrip(&stack);
131    }
132
133    #[test]
134    fn test_serialization_removed_components() {
135        // Start with a stack that has a component, then remove it
136        let mut stack = ItemStack::new(ItemKind::Diamond, 1);
137        stack.insert_component(ItemComponent::Unbreakable);
138        stack.remove_component(ItemComponent::Unbreakable.id() as usize);
139
140        roundtrip(&stack);
141    }
142
143    #[test]
144    fn test_mode_pair_serialization() {
145        let m0 = ModePair::<String, RegistryId>::Mode0("minecraft:standard".to_owned());
146        let m1 = ModePair::<String, RegistryId>::Mode1(RegistryId::new(1));
147
148        roundtrip(&m0);
149        roundtrip(&m1);
150    }
151
152    #[test]
153    fn test_property_value_serialization() {
154        let exact = PropertyValue::Exact("true".into());
155        let min_max = PropertyValue::MinMax {
156            min: "1".into(),
157            max: "5".into(),
158        };
159
160        roundtrip(&exact);
161        roundtrip(&min_max);
162    }
163
164    // --- Recursion and Nested Components ---
165
166    #[test]
167    fn test_nested_container_serialization() {
168        let mut inner_stack = ItemStack::new(ItemKind::Apple, 1);
169        inner_stack.insert_component(ItemComponent::ItemName(Text::from("Inner").into()));
170
171        let mut outer_stack = ItemStack::new(ItemKind::Chest, 1);
172        outer_stack.insert_component(ItemComponent::Container(vec![inner_stack]));
173
174        roundtrip(&outer_stack);
175    }
176
177    #[test]
178    fn test_recursion_limit() {
179        let mut buf = Vec::new();
180
181        // Helper to write a recursive bundle structure manually
182        fn write_recursive_bundle(w: &mut Vec<u8>, depth: usize) {
183            VarInt(1).encode(&mut *w).unwrap(); // Count
184            ItemKind::Bundle.encode(&mut *w).unwrap(); // Item
185
186            VarInt(1).encode(&mut *w).unwrap(); // Added components count
187            VarInt(0).encode(&mut *w).unwrap(); // Removed components count
188
189            VarInt(41).encode(&mut *w).unwrap(); // Component ID: BundleContents
190
191            if depth > 0 {
192                VarInt(1).encode(&mut *w).unwrap(); // Nested list length
193                write_recursive_bundle(w, depth - 1);
194            } else {
195                VarInt(0).encode(&mut *w).unwrap(); // Empty nested list
196            }
197        }
198
199        write_recursive_bundle(&mut buf, 20); // 20 > 16
200
201        let mut slice = buf.as_slice();
202        let result = ItemStack::decode(&mut slice);
203
204        assert!(result.is_err());
205        assert!(result
206            .unwrap_err()
207            .to_string()
208            .contains("recursion limit exceeded"));
209    }
210
211    // --- HashedItemStack Tests ---
212
213    #[test]
214    fn test_hashed_item_stack_roundtrip() {
215        let mut hashed = HashedItemStack::EMPTY;
216        hashed.item = ItemKind::IronIngot;
217        hashed.count = 10;
218        // In real use, these would be crc hashes
219        hashed.components[1] = Patchable::Added(((), 123456));
220
221        roundtrip(&hashed);
222    }
223
224    #[test]
225    fn test_hashed_item_stack_empty() {
226        let hashed = HashedItemStack::EMPTY;
227        let mut buf = Vec::new();
228        hashed.encode(&mut buf).unwrap();
229
230        let mut slice = buf.as_slice();
231        let decoded = HashedItemStack::decode(&mut slice).unwrap();
232        assert!(decoded.is_empty());
233    }
234
235    // --- Edge Cases ---
236
237    #[test]
238    fn test_invalid_component_id() {
239        let mut buf = Vec::new();
240        VarInt(1).encode(&mut buf).unwrap(); // Count
241        ItemKind::Stone.encode(&mut buf).unwrap(); // Item
242        VarInt(1).encode(&mut buf).unwrap(); // Added count
243        VarInt(999).encode(&mut buf).unwrap(); // INVALID ID
244
245        let mut slice = buf.as_slice();
246        let result = ItemStack::decode(&mut slice);
247        assert!(result.is_err());
248    }
249
250    #[test]
251    fn test_all_item_component_ids() {
252        let mut ids = std::collections::HashSet::new();
253        let components = vec![
254            ItemComponent::CustomData(Compound::default()),
255            ItemComponent::MaxStackSize(VarInt(64)),
256            ItemComponent::Unbreakable,
257            ItemComponent::Glider,
258            ItemComponent::ShulkerColor(DyeColor::Black),
259        ];
260
261        for comp in components {
262            let id = comp.id();
263            assert!(id < NUM_ITEM_COMPONENTS as u32);
264            ids.insert(id);
265        }
266
267        assert!(ids.contains(&4));
268        assert!(ids.contains(&30));
269    }
270
271    #[test]
272    fn test_food_component_serialization() {
273        let food = ItemComponent::Food {
274            nutrition: VarInt(4),
275            saturation_modifier: 0.5,
276            can_always_eat: true,
277        };
278        let mut stack = ItemStack::new(ItemKind::Apple, 1);
279        stack.insert_component(food);
280        roundtrip(&stack);
281    }
282
283    #[test]
284    fn test_attribute_modifiers_serialization() {
285        let modifier = AttributeModifier {
286            attribute_id: RegistryId::new(0),
287            modifier_id: ident!("test_mod").into(),
288            value: 5.0,
289            operation: EntityAttributeOperation::Add,
290            slot: AttributeSlot::MainHand,
291        };
292
293        let comp = ItemComponent::AttributeModifiers {
294            modifiers: vec![modifier],
295        };
296
297        let mut stack = ItemStack::new(ItemKind::NetheriteSword, 1);
298        stack.insert_component(comp);
299        roundtrip(&stack);
300    }
301}