steel_registry/data_components/components/
enchantable.rs1use std::error::Error;
4use std::fmt::{self, Display, Formatter};
5use std::io::{Cursor, Error as IoError, Result as IoResult, Write};
6
7use simdnbt::owned::{NbtCompound, NbtTag};
8use simdnbt::{FromNbtTag, ToNbtTag};
9use steel_utils::codec::VarInt;
10use steel_utils::hash::{ComponentHasher, HashComponent};
11use steel_utils::nbt::NbtNumeric as _;
12use steel_utils::serial::{ReadFrom, WriteTo};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct InvalidEnchantableValue {
16 pub value: i32,
17}
18
19impl Display for InvalidEnchantableValue {
20 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
21 write!(
22 formatter,
23 "Enchantment value must be positive, but was {}",
24 self.value
25 )
26 }
27}
28
29impl Error for InvalidEnchantableValue {}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct Enchantable {
34 value: i32,
35}
36
37impl Enchantable {
38 pub const fn new(value: i32) -> Result<Self, InvalidEnchantableValue> {
39 if value <= 0 {
40 return Err(InvalidEnchantableValue { value });
41 }
42 Ok(Self { value })
43 }
44
45 pub(crate) const fn from_extracted_value(value: i32) -> Self {
47 assert!(value > 0, "extracted enchantability must be positive");
48 Self { value }
49 }
50
51 #[must_use]
52 pub const fn value(self) -> i32 {
53 self.value
54 }
55}
56
57impl WriteTo for Enchantable {
58 fn write(&self, writer: &mut impl Write) -> IoResult<()> {
59 VarInt(self.value).write(writer)
60 }
61}
62
63impl ReadFrom for Enchantable {
64 fn read(data: &mut Cursor<&[u8]>) -> IoResult<Self> {
65 Self::new(VarInt::read(data)?.0).map_err(IoError::other)
66 }
67}
68
69impl ToNbtTag for Enchantable {
70 fn to_nbt_tag(self) -> NbtTag {
71 let mut compound = NbtCompound::new();
72 compound.insert("value", self.value);
73 NbtTag::Compound(compound)
74 }
75}
76
77impl FromNbtTag for Enchantable {
78 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
79 let value = tag.compound()?.get("value")?.codec_i32()?;
80 Self::new(value).ok()
81 }
82}
83
84impl HashComponent for Enchantable {
85 fn hash_component(&self, hasher: &mut ComponentHasher) {
86 self.to_nbt_tag().hash_component(hasher);
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use std::io::Cursor;
93
94 use simdnbt::borrow::read_tag;
95 use simdnbt::owned::{NbtCompound, NbtTag};
96 use simdnbt::{FromNbtTag as _, ToNbtTag as _};
97 use steel_utils::codec::VarInt;
98 use steel_utils::hash::HashComponent as _;
99 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
100
101 use super::Enchantable;
102
103 fn parse(tag: NbtTag) -> Option<Enchantable> {
104 let mut bytes = Vec::new();
105 tag.write(&mut bytes);
106 let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
107 Enchantable::from_nbt_tag(borrowed.as_tag())
108 }
109
110 #[test]
111 fn persistent_codec_requires_a_positive_value() {
112 for value in [-1, 0] {
113 let mut compound = NbtCompound::new();
114 compound.insert("value", value);
115 assert_eq!(parse(NbtTag::Compound(compound)), None);
116 }
117
118 let mut compound = NbtCompound::new();
119 compound.insert("value", 15_i8);
120 assert_eq!(
121 parse(NbtTag::Compound(compound)),
122 Some(Enchantable::new(15).expect("15 is positive"))
123 );
124 }
125
126 #[test]
127 fn network_codec_uses_a_validated_varint() {
128 let value = Enchantable::new(22).expect("22 is positive");
129 let mut encoded = Vec::new();
130 value.write(&mut encoded).expect("value should encode");
131 assert_eq!(
132 Enchantable::read(&mut Cursor::new(encoded.as_slice())).expect("value should decode"),
133 value
134 );
135
136 let mut zero = Vec::new();
137 VarInt(0).write(&mut zero).expect("zero should encode");
138 assert!(Enchantable::read(&mut Cursor::new(zero.as_slice())).is_err());
139 }
140
141 #[test]
142 fn persistent_hash_uses_the_record_codec_shape() {
143 let value = Enchantable::new(15).expect("15 is positive");
144 assert_eq!(value.compute_hash(), value.to_nbt_tag().compute_hash());
145 }
146}