Skip to main content

mcproto_types/
json_text_component.rs

1//! JSON-backed Minecraft text components encoded as protocol strings.
2//!
3//! This module wraps the shared component model for the JSON representation
4//! used by the Minecraft protocol.
5
6use std::io::{Read, Write};
7
8use mcproto_codec::error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason};
9use serde::{Deserialize, Serialize};
10
11use crate::{
12    TypeCodec,
13    basic::{decode_prefixed_string, encode_prefixed_string},
14    component::JsonComponent,
15};
16
17/// The dynamically typed JSON value used by [`JsonComponent`].
18pub use serde_json::Value as JsonValue;
19
20/// A text component encoded as JSON in a protocol string.
21///
22/// Since Java Edition 1.20.3, the vanilla implementation permits up to
23/// 262,144 UTF-16 code units when decoding but refuses to encode more than
24/// 32,767. See the [text component format].
25///
26/// [text component format]: https://minecraft.wiki/w/Text_component_format
27#[derive(Debug, Clone, PartialEq)]
28pub struct JsonTextComponent(
29    /// The structured text component value.
30    pub JsonComponent,
31);
32
33impl JsonTextComponent {
34    /// The maximum number of UTF-16 code units accepted while decoding.
35    pub const MAX_DECODE_UTF16_CODE_UNITS: usize = 262_144;
36    /// The maximum decoded UTF-8 payload size, excluding its VarInt length prefix.
37    pub const MAX_DECODE_BYTES: usize = Self::MAX_DECODE_UTF16_CODE_UNITS * 3;
38    /// The maximum decoded size, including the VarInt length prefix.
39    pub const MAX_DECODE_ENCODED_BYTES: usize = Self::MAX_DECODE_BYTES + 3;
40
41    /// The maximum number of UTF-16 code units permitted while encoding.
42    ///
43    /// Vanilla Java Edition 1.20.3 and later still refuses to encode larger
44    /// JSON components despite accepting them while decoding.
45    pub const MAX_ENCODE_UTF16_CODE_UNITS: usize = 32_767;
46    /// The maximum encoded UTF-8 payload size, excluding its VarInt length prefix.
47    pub const MAX_ENCODE_BYTES: usize = Self::MAX_ENCODE_UTF16_CODE_UNITS * 3;
48    /// The maximum encoded size, including the VarInt length prefix.
49    pub const MAX_ENCODE_ENCODED_BYTES: usize = Self::MAX_ENCODE_BYTES + 3;
50
51    const MAX_COMPONENT_DEPTH: usize = 512;
52
53    /// Creates a plain-text component.
54    pub fn text(value: impl Into<String>) -> Self {
55        Self(JsonComponent::text(value))
56    }
57
58    /// Parses and validates a text component from JSON without a length prefix.
59    ///
60    /// On failure, the number of bytes of `value` is reported as
61    /// [`CodecError::bytes_processed`].
62    ///
63    /// # Errors
64    ///
65    /// Returns a [`CodecError`] if `value` is invalid JSON, does not represent
66    /// a text component, or exceeds the supported nesting depth.
67    pub fn from_json_str(value: &str) -> Result<Self, CodecError> {
68        let component = deserialize_json(value)
69            .map_err(|source| Self::invalid_json(CodecOperation::Read, value.len(), source))?;
70        validate_component(&component)
71            .map_err(|source| Self::invalid_json(CodecOperation::Read, value.len(), source))?;
72        Ok(Self(component))
73    }
74
75    fn invalid_json(
76        operation: CodecOperation,
77        bytes_processed: usize,
78        source: impl std::error::Error + Send + Sync + 'static,
79    ) -> CodecError {
80        CodecError::invalid_encoding_for_operation_with_source(
81            CodecKind::JsonTextComponent,
82            operation,
83            bytes_processed,
84            InvalidEncodingReason::InvalidJson,
85            source,
86        )
87    }
88}
89
90impl Default for JsonTextComponent {
91    fn default() -> Self {
92        Self::text("")
93    }
94}
95
96impl From<JsonComponent> for JsonTextComponent {
97    fn from(value: JsonComponent) -> Self {
98        Self(value)
99    }
100}
101
102impl From<String> for JsonTextComponent {
103    fn from(value: String) -> Self {
104        Self::text(value)
105    }
106}
107
108impl From<&str> for JsonTextComponent {
109    fn from(value: &str) -> Self {
110        Self::text(value)
111    }
112}
113
114impl TypeCodec for JsonTextComponent {
115    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
116        validate_component(&self.0)
117            .map_err(|source| Self::invalid_json(CodecOperation::Write, 0, source))?;
118        let mut bytes = Vec::new();
119        let mut serializer = serde_json::Serializer::new(&mut bytes);
120        self.0
121            .serialize(serde_stacker::Serializer::new(&mut serializer))
122            .map_err(|source| Self::invalid_json(CodecOperation::Write, 0, source))?;
123        let json = String::from_utf8(bytes)
124            .map_err(|source| Self::invalid_json(CodecOperation::Write, 0, source))?;
125        encode_prefixed_string(
126            &json,
127            writer,
128            CodecKind::JsonTextComponent,
129            Self::MAX_ENCODE_BYTES,
130            Self::MAX_ENCODE_UTF16_CODE_UNITS,
131        )
132    }
133
134    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
135        let (json, bytes_processed) = decode_prefixed_string(
136            reader,
137            CodecKind::JsonTextComponent,
138            Self::MAX_DECODE_BYTES,
139            Self::MAX_DECODE_UTF16_CODE_UNITS,
140        )?;
141        let component = deserialize_json(&json)
142            .map_err(|source| Self::invalid_json(CodecOperation::Read, bytes_processed, source))?;
143        validate_component(&component)
144            .map_err(|source| Self::invalid_json(CodecOperation::Read, bytes_processed, source))?;
145        Ok(Self(component))
146    }
147}
148
149fn deserialize_json(value: &str) -> Result<JsonComponent, serde_json::Error> {
150    validate_json_syntax_depth(value, JsonTextComponent::MAX_COMPONENT_DEPTH * 2 + 8)
151        .map_err(json_validation_error)?;
152    let mut deserializer = serde_json::Deserializer::from_str(value);
153    deserializer.disable_recursion_limit();
154    let component =
155        JsonComponent::deserialize(serde_stacker::Deserializer::new(&mut deserializer))?;
156    deserializer.end()?;
157    Ok(component)
158}
159
160fn validate_json_syntax_depth(value: &str, max_depth: usize) -> Result<(), JsonSyntaxDepthError> {
161    let mut depth = 0_usize;
162    let mut in_string = false;
163    let mut escaped = false;
164    for byte in value.bytes() {
165        if in_string {
166            if escaped {
167                escaped = false;
168            } else if byte == b'\\' {
169                escaped = true;
170            } else if byte == b'"' {
171                in_string = false;
172            }
173            continue;
174        }
175        match byte {
176            b'"' => in_string = true,
177            b'{' | b'[' => {
178                depth += 1;
179                if depth > max_depth {
180                    return Err(JsonSyntaxDepthError { max_depth });
181                }
182            }
183            b'}' | b']' => depth = depth.saturating_sub(1),
184            _ => {}
185        }
186    }
187    Ok(())
188}
189
190#[derive(Debug)]
191struct JsonSyntaxDepthError {
192    max_depth: usize,
193}
194
195impl std::fmt::Display for JsonSyntaxDepthError {
196    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        write!(
198            formatter,
199            "JSON nesting exceeds the {}-container limit",
200            self.max_depth
201        )
202    }
203}
204
205impl std::error::Error for JsonSyntaxDepthError {}
206
207fn validate_component(
208    component: &JsonComponent,
209) -> Result<(), crate::component::ComponentDepthError> {
210    component.validate_depth(JsonTextComponent::MAX_COMPONENT_DEPTH)?;
211    component.validate_dynamic_depth(JsonTextComponent::MAX_COMPONENT_DEPTH)
212}
213
214fn json_validation_error(
215    source: impl std::error::Error + Send + Sync + 'static,
216) -> serde_json::Error {
217    serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, source))
218}