Skip to main content

nbt_rust/
tag.rs

1use indexmap::IndexMap;
2
3use crate::error::{Error, Result};
4
5pub type CompoundTag = IndexMap<String, Tag>;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[repr(u8)]
9pub enum TagType {
10    End = 0,
11    Byte = 1,
12    Short = 2,
13    Int = 3,
14    Long = 4,
15    Float = 5,
16    Double = 6,
17    ByteArray = 7,
18    String = 8,
19    List = 9,
20    Compound = 10,
21    IntArray = 11,
22    LongArray = 12,
23}
24
25impl TagType {
26    pub const fn id(self) -> u8 {
27        self as u8
28    }
29}
30
31impl TryFrom<u8> for TagType {
32    type Error = Error;
33
34    fn try_from(value: u8) -> Result<Self> {
35        let tag_type = match value {
36            0 => Self::End,
37            1 => Self::Byte,
38            2 => Self::Short,
39            3 => Self::Int,
40            4 => Self::Long,
41            5 => Self::Float,
42            6 => Self::Double,
43            7 => Self::ByteArray,
44            8 => Self::String,
45            9 => Self::List,
46            10 => Self::Compound,
47            11 => Self::IntArray,
48            12 => Self::LongArray,
49            _ => return Err(Error::UnknownTag { id: value }),
50        };
51        Ok(tag_type)
52    }
53}
54
55#[derive(Debug, Clone, PartialEq)]
56pub struct ListTag {
57    pub element_type: TagType,
58    pub elements: Vec<Tag>,
59}
60
61impl ListTag {
62    pub fn new(element_type: TagType, elements: Vec<Tag>) -> Result<Self> {
63        let list = Self {
64            element_type,
65            elements,
66        };
67        list.validate()?;
68        Ok(list)
69    }
70
71    pub fn empty(element_type: TagType) -> Self {
72        Self {
73            element_type,
74            elements: Vec::new(),
75        }
76    }
77
78    pub fn validate(&self) -> Result<()> {
79        if self.element_type == TagType::End && !self.elements.is_empty() {
80            return Err(Error::InvalidListHeader {
81                element_type_id: self.element_type.id(),
82                length: self.elements.len(),
83            });
84        }
85
86        for element in &self.elements {
87            let actual = element.tag_type();
88            if actual != self.element_type {
89                return Err(Error::UnexpectedType {
90                    context: "list_element_type",
91                    expected_id: self.element_type.id(),
92                    actual_id: actual.id(),
93                });
94            }
95        }
96        Ok(())
97    }
98}
99
100#[derive(Debug, Clone, PartialEq)]
101pub enum Tag {
102    /// Marker value for TAG_End (id=0).
103    ///
104    /// This is modeled explicitly for completeness, but TAG_End is not a
105    /// normal payload value in NBT streams.
106    End,
107    Byte(i8),
108    Short(i16),
109    Int(i32),
110    Long(i64),
111    Float(f32),
112    Double(f64),
113    ByteArray(Vec<u8>),
114    String(String),
115    List(ListTag),
116    Compound(CompoundTag),
117    IntArray(Vec<i32>),
118    LongArray(Vec<i64>),
119}
120
121impl Tag {
122    pub fn tag_type(&self) -> TagType {
123        match self {
124            Tag::End => TagType::End,
125            Tag::Byte(_) => TagType::Byte,
126            Tag::Short(_) => TagType::Short,
127            Tag::Int(_) => TagType::Int,
128            Tag::Long(_) => TagType::Long,
129            Tag::Float(_) => TagType::Float,
130            Tag::Double(_) => TagType::Double,
131            Tag::ByteArray(_) => TagType::ByteArray,
132            Tag::String(_) => TagType::String,
133            Tag::List(_) => TagType::List,
134            Tag::Compound(_) => TagType::Compound,
135            Tag::IntArray(_) => TagType::IntArray,
136            Tag::LongArray(_) => TagType::LongArray,
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn end_tag_maps_to_end_tag_type() {
147        assert_eq!(Tag::End.tag_type(), TagType::End);
148    }
149}