chunkedge_binary/impls/
string.rs

1use std::io::Write;
2
3use anyhow::{ensure, Context};
4use chunkedge_nbt::compound::NetworkCompound;
5use chunkedge_nbt::serde::ser::CompoundSerializer;
6use chunkedge_text::{JsonText, Text};
7use serde::de::IntoDeserializer;
8use serde::{Deserialize, Serialize};
9
10use crate::{Bounded, Decode, Encode, VarInt};
11
12const DEFAULT_MAX_STRING_CHARS: usize = 32767;
13const MAX_TEXT_CHARS: usize = 262144;
14
15impl Encode for str {
16    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
17        Bounded::<_, DEFAULT_MAX_STRING_CHARS>(self).encode(w)
18    }
19}
20
21impl<const MAX_CHARS: usize> Encode for Bounded<&'_ str, MAX_CHARS> {
22    fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
23        let char_count = self.encode_utf16().count();
24
25        ensure!(
26            char_count <= MAX_CHARS,
27            "char count of string exceeds maximum (expected <= {MAX_CHARS}, got {char_count})"
28        );
29
30        VarInt(self.len() as i32).encode(&mut w)?;
31        Ok(w.write_all(self.as_bytes())?)
32    }
33}
34
35impl<'a> Decode<'a> for &'a str {
36    fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
37        Ok(Bounded::<_, DEFAULT_MAX_STRING_CHARS>::decode(r)?.0)
38    }
39}
40
41impl<'a, const MAX_CHARS: usize> Decode<'a> for Bounded<&'a str, MAX_CHARS> {
42    fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
43        let len = VarInt::decode(r)?.0;
44        ensure!(len >= 0, "attempt to decode string with negative length");
45        let len = len as usize;
46        ensure!(
47            len <= r.len(),
48            "not enough data remaining ({} bytes) to decode string of {len} bytes",
49            r.len()
50        );
51
52        let (res, remaining) = r.split_at(len);
53        let res = std::str::from_utf8(res)?;
54
55        let char_count = res.encode_utf16().count();
56        ensure!(
57            char_count <= MAX_CHARS,
58            "char count of string exceeds maximum (expected <= {MAX_CHARS}, got {char_count})"
59        );
60
61        *r = remaining;
62
63        Ok(Bounded(res))
64    }
65}
66
67impl Encode for String {
68    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
69        self.as_str().encode(w)
70    }
71}
72
73impl<const MAX_CHARS: usize> Encode for Bounded<String, MAX_CHARS> {
74    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
75        Bounded::<_, MAX_CHARS>(self.as_str()).encode(w)
76    }
77}
78
79impl Decode<'_> for String {
80    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
81        Ok(<&str>::decode(r)?.into())
82    }
83}
84
85impl<const MAX_CHARS: usize> Decode<'_> for Bounded<String, MAX_CHARS> {
86    fn decode(r: &mut &'_ [u8]) -> anyhow::Result<Self> {
87        Ok(Bounded(Bounded::<&str, MAX_CHARS>::decode(r)?.0.into()))
88    }
89}
90
91impl Decode<'_> for Box<str> {
92    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
93        Ok(<&str>::decode(r)?.into())
94    }
95}
96
97impl<const MAX_CHARS: usize> Decode<'_> for Bounded<Box<str>, MAX_CHARS> {
98    fn decode(r: &mut &'_ [u8]) -> anyhow::Result<Self> {
99        Ok(Bounded(Bounded::<&str, MAX_CHARS>::decode(r)?.0.into()))
100    }
101}
102
103impl Decode<'_> for Text {
104    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
105        let c = NetworkCompound::decode(r)
106            .context("decoding text from NBT")?
107            .compound;
108        Text::deserialize(c.into_deserializer()).context("deserializing text NBT")
109    }
110}
111
112impl Encode for Text {
113    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
114        let c = self
115            .serialize(CompoundSerializer)
116            .context("serializing text as compound")?;
117
118        NetworkCompound::from(c).encode(w)
119    }
120}
121
122impl Encode for JsonText {
123    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
124        let s = serde_json::to_string(self).context("serializing text JSON")?;
125
126        Bounded::<_, MAX_TEXT_CHARS>(s).encode(w)
127    }
128}
129
130impl Decode<'_> for JsonText {
131    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
132        let str = Bounded::<&str, MAX_TEXT_CHARS>::decode(r)?.0;
133
134        serde_json::from_str(str).context("deserializing text JSON")
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use chunkedge_text::{Color, IntoText};
141
142    use super::*;
143
144    #[test]
145    fn test_text_encoding() {
146        let text = Text::from("Hello, world!").color(Color::RED);
147        let mut encoded = Vec::new();
148        text.encode(&mut encoded).unwrap();
149
150        let decoded: Text = Text::decode(&mut encoded.as_slice()).unwrap();
151        assert_eq!(decoded, text);
152
153        let text = "Italic text".italic();
154        let mut encoded = Vec::new();
155        text.encode(&mut encoded).unwrap();
156        let decoded: Text = Text::decode(&mut encoded.as_slice()).unwrap();
157        assert_eq!(decoded, text);
158    }
159
160    #[test]
161    fn test_json_text_encoding() {
162        let json_text = JsonText(Text::from("Hello, world!").color(Color::RED));
163        let mut encoded = Vec::new();
164        json_text.encode(&mut encoded).unwrap();
165
166        let decoded: JsonText = JsonText::decode(&mut encoded.as_slice()).unwrap();
167        assert_eq!(decoded, json_text);
168    }
169}