Skip to main content

mcproto_types/
component.rs

1use std::{collections::BTreeMap, fmt, num::NonZeroI32};
2
3use fastnbt::Value as NbtValue;
4use serde::{
5    Deserialize, Deserializer, Serialize, Serializer,
6    de::{Error as _, MapAccess, SeqAccess, Visitor, value::MapAccessDeserializer},
7    ser::{SerializeMap, SerializeSeq},
8};
9
10/// Java Edition release whose text component schema is represented here.
11pub const TEXT_COMPONENT_FORMAT_VERSION: &str = "26.1";
12
13/// The Java Edition text component schema documented for the current protocol.
14///
15/// `V` is the wire format's deliberately dynamic value type. Use
16/// [`NbtComponent`] for NBT and [`JsonComponent`] for JSON.
17#[derive(Debug, Clone, PartialEq)]
18pub enum Component<V> {
19    /// The string shorthand for a plain-text component.
20    Text(String),
21    /// The non-empty list shorthand.
22    Sequence(ComponentSequence<V>),
23    /// A full component object.
24    Object(Box<ComponentObject<V>>),
25}
26
27pub type NbtComponent = Component<NbtValue>;
28pub type JsonComponent = Component<serde_json::Value>;
29
30impl<V> Component<V> {
31    pub fn text(value: impl Into<String>) -> Self {
32        Self::Text(value.into())
33    }
34
35    pub fn object(content: Content<V>) -> Self {
36        Self::Object(Box::new(ComponentObject::new(content)))
37    }
38
39    pub fn sequence(first: Component<V>, rest: impl IntoIterator<Item = Component<V>>) -> Self {
40        Self::Sequence(ComponentSequence::new(first, rest))
41    }
42
43    pub(crate) fn validate_depth(&self, max_depth: usize) -> Result<(), ComponentDepthError> {
44        let mut pending = vec![(self, 1_usize)];
45        while let Some((component, depth)) = pending.pop() {
46            if depth > max_depth {
47                return Err(ComponentDepthError { max_depth });
48            }
49            let next = depth + 1;
50            match component {
51                Self::Text(_) => {}
52                Self::Sequence(sequence) => {
53                    pending.extend(sequence.iter().map(|child| (child, next)));
54                }
55                Self::Object(object) => {
56                    pending.extend(object.extra.iter().map(|child| (child, next)));
57                    match &object.content {
58                        Content::Translatable { with, .. } => {
59                            pending.extend(with.iter().map(|child| (child, next)));
60                        }
61                        Content::Selector { separator, .. } | Content::Nbt { separator, .. } => {
62                            if let Some(separator) = separator {
63                                pending.push((separator, next));
64                            }
65                        }
66                        _ => {}
67                    }
68                    if let Some(hover) = &object.style.hover_event {
69                        match hover {
70                            HoverEvent::ShowText { value } => pending.push((value, next)),
71                            HoverEvent::ShowEntity { name, .. } => {
72                                if let Some(name) = name {
73                                    pending.push((name, next));
74                                }
75                            }
76                            HoverEvent::ShowItem { .. } => {}
77                        }
78                    }
79                }
80            }
81        }
82        Ok(())
83    }
84}
85
86impl<V> Default for Component<V> {
87    fn default() -> Self {
88        Self::text("")
89    }
90}
91
92impl<V> From<String> for Component<V> {
93    fn from(value: String) -> Self {
94        Self::text(value)
95    }
96}
97
98impl<V> From<&str> for Component<V> {
99    fn from(value: &str) -> Self {
100        Self::text(value)
101    }
102}
103
104impl<V: Serialize> Serialize for Component<V> {
105    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106    where
107        S: Serializer,
108    {
109        match self {
110            Self::Text(text) => serializer.serialize_str(text),
111            Self::Sequence(sequence) => sequence.serialize(serializer),
112            Self::Object(object) => object.serialize(serializer),
113        }
114    }
115}
116
117impl<'de, V> Deserialize<'de> for Component<V>
118where
119    V: Deserialize<'de>,
120{
121    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
122    where
123        D: Deserializer<'de>,
124    {
125        struct ComponentVisitor<V>(std::marker::PhantomData<V>);
126
127        impl<'de, V> Visitor<'de> for ComponentVisitor<V>
128        where
129            V: Deserialize<'de>,
130        {
131            type Value = Component<V>;
132
133            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134                formatter.write_str("a text component string, non-empty list, or object")
135            }
136
137            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
138            where
139                E: serde::de::Error,
140            {
141                Ok(Component::Text(value.to_owned()))
142            }
143
144            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
145            where
146                E: serde::de::Error,
147            {
148                Ok(Component::Text(value))
149            }
150
151            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
152            where
153                A: SeqAccess<'de>,
154            {
155                let mut components =
156                    Vec::with_capacity(sequence.size_hint().unwrap_or(0).min(1024));
157                while let Some(component) = sequence.next_element()? {
158                    components.push(component);
159                }
160                ComponentSequence::try_from(components)
161                    .map(Component::Sequence)
162                    .map_err(A::Error::custom)
163            }
164
165            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
166            where
167                A: MapAccess<'de>,
168            {
169                ComponentObject::deserialize(MapAccessDeserializer::new(map))
170                    .map(Box::new)
171                    .map(Component::Object)
172            }
173        }
174
175        deserializer.deserialize_any(ComponentVisitor(std::marker::PhantomData))
176    }
177}
178
179#[derive(Debug, Clone, PartialEq)]
180pub struct ComponentSequence<V> {
181    first: Box<Component<V>>,
182    rest: Vec<Component<V>>,
183}
184
185impl<V> ComponentSequence<V> {
186    pub fn new(first: Component<V>, rest: impl IntoIterator<Item = Component<V>>) -> Self {
187        Self {
188            first: Box::new(first),
189            rest: rest.into_iter().collect(),
190        }
191    }
192
193    pub fn first(&self) -> &Component<V> {
194        &self.first
195    }
196
197    pub fn rest(&self) -> &[Component<V>] {
198        &self.rest
199    }
200
201    pub fn iter(&self) -> impl Iterator<Item = &Component<V>> {
202        std::iter::once(self.first.as_ref()).chain(&self.rest)
203    }
204}
205
206impl<V> TryFrom<Vec<Component<V>>> for ComponentSequence<V> {
207    type Error = EmptyComponentSequence;
208
209    fn try_from(mut value: Vec<Component<V>>) -> Result<Self, Self::Error> {
210        if value.is_empty() {
211            return Err(EmptyComponentSequence);
212        }
213        let rest = value.split_off(1);
214        let first = value.pop().ok_or(EmptyComponentSequence)?;
215        Ok(Self::new(first, rest))
216    }
217}
218
219impl<V: Serialize> Serialize for ComponentSequence<V> {
220    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
221    where
222        S: Serializer,
223    {
224        let mut sequence = serializer.serialize_seq(Some(1 + self.rest.len()))?;
225        for component in self.iter() {
226            sequence.serialize_element(component)?;
227        }
228        sequence.end()
229    }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct EmptyComponentSequence;
234
235impl fmt::Display for EmptyComponentSequence {
236    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
237        formatter.write_str("a text component sequence cannot be empty")
238    }
239}
240
241impl std::error::Error for EmptyComponentSequence {}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub(crate) struct ComponentDepthError {
245    pub max_depth: usize,
246}
247
248impl fmt::Display for ComponentDepthError {
249    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(
251            formatter,
252            "text component nesting exceeds {} levels",
253            self.max_depth
254        )
255    }
256}
257
258impl std::error::Error for ComponentDepthError {}
259
260#[derive(Debug, Clone, PartialEq)]
261pub struct ComponentObject<V> {
262    pub content: Content<V>,
263    pub style: Style<V>,
264    pub extra: Vec<Component<V>>,
265}
266
267impl<V> ComponentObject<V> {
268    pub fn new(content: Content<V>) -> Self {
269        Self {
270            content,
271            style: Style::default(),
272            extra: Vec::new(),
273        }
274    }
275
276    pub fn text(value: impl Into<String>) -> Self {
277        Self::new(Content::Text { text: value.into() })
278    }
279}
280
281#[derive(Debug, Clone, PartialEq)]
282pub enum Content<V> {
283    Text {
284        text: String,
285    },
286    Translatable {
287        translate: String,
288        fallback: Option<String>,
289        with: Vec<Component<V>>,
290    },
291    Score {
292        score: Score,
293    },
294    Selector {
295        selector: String,
296        separator: Option<Box<Component<V>>>,
297    },
298    Keybind {
299        keybind: String,
300    },
301    Nbt {
302        nbt: String,
303        target: NbtTarget,
304        display: NbtDisplay,
305        separator: Option<Box<Component<V>>>,
306    },
307    Object {
308        object: ObjectContent,
309        /// Added by Java Edition 26.1.
310        fallback: Option<String>,
311    },
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
315pub struct Score {
316    pub name: String,
317    pub objective: String,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub enum NbtTarget {
322    Entity(String),
323    Block(String),
324    Storage(ResourceLocation),
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
328pub enum NbtDisplay {
329    #[default]
330    Styled,
331    Plain,
332    Interpret,
333}
334
335#[derive(Debug, Clone, PartialEq)]
336pub enum ObjectContent {
337    Atlas {
338        atlas: Option<ResourceLocation>,
339        sprite: ResourceLocation,
340    },
341    Player {
342        player: PlayerProfile,
343        hat: Option<bool>,
344    },
345}
346
347#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
348#[serde(untagged)]
349pub enum PlayerProfile {
350    Name(PlayerName),
351    Profile(Profile),
352}
353
354#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
355pub struct Profile {
356    #[serde(default, skip_serializing_if = "Option::is_none")]
357    pub name: Option<PlayerName>,
358    #[serde(default, skip_serializing_if = "Option::is_none")]
359    pub id: Option<Uuid>,
360    #[serde(default, skip_serializing_if = "Vec::is_empty")]
361    pub properties: Vec<ProfileProperty>,
362    #[serde(default, skip_serializing_if = "Option::is_none")]
363    pub texture: Option<ResourceLocation>,
364    #[serde(default, skip_serializing_if = "Option::is_none")]
365    pub cape: Option<ResourceLocation>,
366    #[serde(default, skip_serializing_if = "Option::is_none")]
367    pub elytra: Option<ResourceLocation>,
368    #[serde(default, skip_serializing_if = "Option::is_none")]
369    pub model: Option<PlayerModel>,
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
373pub struct ProfileProperty {
374    pub name: ProfilePropertyName,
375    pub value: String,
376    #[serde(default, skip_serializing_if = "Option::is_none")]
377    pub signature: Option<String>,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
381#[serde(rename_all = "snake_case")]
382pub enum ProfilePropertyName {
383    Textures,
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "snake_case")]
388pub enum PlayerModel {
389    Wide,
390    Slim,
391}
392
393#[derive(Debug, Clone, PartialEq)]
394pub struct Style<V> {
395    pub color: Option<TextColor>,
396    pub font: Option<ResourceLocation>,
397    pub bold: Option<bool>,
398    pub italic: Option<bool>,
399    pub underlined: Option<bool>,
400    pub strikethrough: Option<bool>,
401    pub obfuscated: Option<bool>,
402    pub shadow_color: Option<ShadowColor>,
403    pub insertion: Option<String>,
404    pub click_event: Option<ClickEvent<V>>,
405    pub hover_event: Option<HoverEvent<V>>,
406}
407
408impl<V> Default for Style<V> {
409    fn default() -> Self {
410        Self {
411            color: None,
412            font: None,
413            bold: None,
414            italic: None,
415            underlined: None,
416            strikethrough: None,
417            obfuscated: None,
418            shadow_color: None,
419            insertion: None,
420            click_event: None,
421            hover_event: None,
422        }
423    }
424}
425
426impl<V> Style<V> {
427    fn is_empty(&self) -> bool {
428        self.color.is_none()
429            && self.font.is_none()
430            && self.bold.is_none()
431            && self.italic.is_none()
432            && self.underlined.is_none()
433            && self.strikethrough.is_none()
434            && self.obfuscated.is_none()
435            && self.shadow_color.is_none()
436            && self.insertion.is_none()
437            && self.click_event.is_none()
438            && self.hover_event.is_none()
439    }
440}
441
442#[derive(Debug, Clone, PartialEq, Serialize)]
443#[serde(
444    tag = "action",
445    rename_all = "snake_case",
446    bound(serialize = "V: Serialize")
447)]
448pub enum ClickEvent<V> {
449    OpenUrl {
450        url: HttpUrl,
451    },
452    OpenFile {
453        path: String,
454    },
455    RunCommand {
456        command: CommandString,
457    },
458    SuggestCommand {
459        command: CommandString,
460    },
461    ChangePage {
462        page: PositiveI32,
463    },
464    CopyToClipboard {
465        value: String,
466    },
467    ShowDialog {
468        dialog: DialogReference<V>,
469    },
470    Custom {
471        id: ResourceLocation,
472        #[serde(default, skip_serializing_if = "Option::is_none")]
473        payload: Option<V>,
474    },
475}
476
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478#[serde(
479    untagged,
480    bound(serialize = "V: Serialize", deserialize = "V: Deserialize<'de>")
481)]
482pub enum DialogReference<V> {
483    Id(ResourceLocation),
484    Inline(BTreeMap<String, V>),
485}
486
487#[derive(Debug, Clone, PartialEq, Serialize)]
488#[serde(
489    tag = "action",
490    rename_all = "snake_case",
491    bound(serialize = "V: Serialize")
492)]
493pub enum HoverEvent<V> {
494    ShowText {
495        value: Box<Component<V>>,
496    },
497    ShowItem {
498        id: ResourceLocation,
499        #[serde(default, skip_serializing_if = "Option::is_none")]
500        count: Option<i32>,
501        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
502        components: BTreeMap<ResourceLocation, V>,
503    },
504    ShowEntity {
505        #[serde(default, skip_serializing_if = "Option::is_none")]
506        name: Option<Box<Component<V>>>,
507        id: ResourceLocation,
508        uuid: Uuid,
509    },
510}
511
512#[derive(Deserialize)]
513#[serde(bound(deserialize = "V: Deserialize<'de>"))]
514struct RawClickEvent<V> {
515    action: ClickAction,
516    #[serde(default)]
517    url: Option<HttpUrl>,
518    #[serde(default)]
519    path: Option<String>,
520    #[serde(default)]
521    command: Option<CommandString>,
522    #[serde(default)]
523    page: Option<PositiveI32>,
524    #[serde(default)]
525    value: Option<String>,
526    #[serde(default)]
527    dialog: Option<DialogReference<V>>,
528    #[serde(default)]
529    id: Option<ResourceLocation>,
530    #[serde(default)]
531    payload: Option<V>,
532}
533
534#[derive(Deserialize)]
535#[serde(rename_all = "snake_case")]
536enum ClickAction {
537    OpenUrl,
538    OpenFile,
539    RunCommand,
540    SuggestCommand,
541    ChangePage,
542    CopyToClipboard,
543    ShowDialog,
544    Custom,
545}
546
547impl<'de, V> Deserialize<'de> for ClickEvent<V>
548where
549    V: Deserialize<'de>,
550{
551    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
552    where
553        D: Deserializer<'de>,
554    {
555        let raw = RawClickEvent::deserialize(deserializer)?;
556        let missing = || D::Error::custom("click event is missing its action payload");
557        match raw.action {
558            ClickAction::OpenUrl => raw.url.map(|url| Self::OpenUrl { url }).ok_or_else(missing),
559            ClickAction::OpenFile => raw
560                .path
561                .map(|path| Self::OpenFile { path })
562                .ok_or_else(missing),
563            ClickAction::RunCommand => raw
564                .command
565                .map(|command| Self::RunCommand { command })
566                .ok_or_else(missing),
567            ClickAction::SuggestCommand => raw
568                .command
569                .map(|command| Self::SuggestCommand { command })
570                .ok_or_else(missing),
571            ClickAction::ChangePage => raw
572                .page
573                .map(|page| Self::ChangePage { page })
574                .ok_or_else(missing),
575            ClickAction::CopyToClipboard => raw
576                .value
577                .map(|value| Self::CopyToClipboard { value })
578                .ok_or_else(missing),
579            ClickAction::ShowDialog => raw
580                .dialog
581                .map(|dialog| Self::ShowDialog { dialog })
582                .ok_or_else(missing),
583            ClickAction::Custom => raw
584                .id
585                .map(|id| Self::Custom {
586                    id,
587                    payload: raw.payload,
588                })
589                .ok_or_else(missing),
590        }
591    }
592}
593
594#[derive(Deserialize)]
595#[serde(bound(deserialize = "V: Deserialize<'de>"))]
596struct RawHoverEvent<V> {
597    action: HoverAction,
598    #[serde(default)]
599    value: Option<Box<Component<V>>>,
600    #[serde(default)]
601    id: Option<ResourceLocation>,
602    #[serde(default)]
603    count: Option<i32>,
604    #[serde(default)]
605    components: BTreeMap<ResourceLocation, V>,
606    #[serde(default)]
607    name: Option<Box<Component<V>>>,
608    #[serde(default)]
609    uuid: Option<Uuid>,
610}
611
612#[derive(Deserialize)]
613enum HoverAction {
614    #[serde(rename = "show_text")]
615    Text,
616    #[serde(rename = "show_item")]
617    Item,
618    #[serde(rename = "show_entity")]
619    Entity,
620}
621
622impl<'de, V> Deserialize<'de> for HoverEvent<V>
623where
624    V: Deserialize<'de>,
625{
626    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
627    where
628        D: Deserializer<'de>,
629    {
630        let raw = RawHoverEvent::deserialize(deserializer)?;
631        let missing = || D::Error::custom("hover event is missing its action payload");
632        match raw.action {
633            HoverAction::Text => raw
634                .value
635                .map(|value| Self::ShowText { value })
636                .ok_or_else(missing),
637            HoverAction::Item => raw
638                .id
639                .map(|id| Self::ShowItem {
640                    id,
641                    count: raw.count,
642                    components: raw.components,
643                })
644                .ok_or_else(missing),
645            HoverAction::Entity => match (raw.id, raw.uuid) {
646                (Some(id), Some(uuid)) => Ok(Self::ShowEntity {
647                    name: raw.name,
648                    id,
649                    uuid,
650                }),
651                _ => Err(missing()),
652            },
653        }
654    }
655}
656
657#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
658pub struct ResourceLocation(String);
659
660impl ResourceLocation {
661    pub fn new(value: impl Into<String>) -> Result<Self, InvalidResourceLocation> {
662        let value = value.into();
663        validate_resource_location(&value)?;
664        Ok(Self(value))
665    }
666
667    pub fn as_str(&self) -> &str {
668        &self.0
669    }
670}
671
672impl fmt::Display for ResourceLocation {
673    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
674        formatter.write_str(&self.0)
675    }
676}
677
678impl Serialize for ResourceLocation {
679    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
680    where
681        S: Serializer,
682    {
683        serializer.serialize_str(&self.0)
684    }
685}
686
687impl<'de> Deserialize<'de> for ResourceLocation {
688    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
689    where
690        D: Deserializer<'de>,
691    {
692        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
693    }
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub struct InvalidResourceLocation;
698
699impl fmt::Display for InvalidResourceLocation {
700    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
701        formatter.write_str("invalid Minecraft resource location")
702    }
703}
704
705impl std::error::Error for InvalidResourceLocation {}
706
707fn validate_resource_location(value: &str) -> Result<(), InvalidResourceLocation> {
708    let (namespace, path) = match value.split_once(':') {
709        Some((namespace, path)) => (namespace, path),
710        None => ("minecraft", value),
711    };
712    let namespace_ok = !namespace.is_empty()
713        && namespace.bytes().all(|byte| {
714            byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_.-".contains(&byte)
715        });
716    let path_ok = !path.is_empty()
717        && path.bytes().all(|byte| {
718            byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"/._-".contains(&byte)
719        });
720    if namespace_ok && path_ok && !path.contains(':') {
721        Ok(())
722    } else {
723        Err(InvalidResourceLocation)
724    }
725}
726
727#[derive(Debug, Clone, PartialEq, Eq, Hash)]
728pub struct HttpUrl(String);
729
730impl HttpUrl {
731    pub fn new(value: impl Into<String>) -> Result<Self, InvalidHttpUrl> {
732        let value = value.into();
733        let parsed = url::Url::parse(&value).map_err(|_| InvalidHttpUrl)?;
734        if matches!(parsed.scheme(), "http" | "https") && parsed.host().is_some() {
735            Ok(Self(value))
736        } else {
737            Err(InvalidHttpUrl)
738        }
739    }
740
741    pub fn as_str(&self) -> &str {
742        &self.0
743    }
744}
745
746impl Serialize for HttpUrl {
747    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
748    where
749        S: Serializer,
750    {
751        serializer.serialize_str(&self.0)
752    }
753}
754
755impl<'de> Deserialize<'de> for HttpUrl {
756    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
757    where
758        D: Deserializer<'de>,
759    {
760        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
761    }
762}
763
764#[derive(Debug, Clone, Copy, PartialEq, Eq)]
765pub struct InvalidHttpUrl;
766
767impl fmt::Display for InvalidHttpUrl {
768    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
769        formatter.write_str("open_url requires an absolute HTTP or HTTPS URL")
770    }
771}
772
773impl std::error::Error for InvalidHttpUrl {}
774
775#[derive(Debug, Clone, PartialEq, Eq, Hash)]
776pub struct CommandString(String);
777
778impl CommandString {
779    pub fn new(value: impl Into<String>) -> Result<Self, InvalidCommandString> {
780        let value = value.into();
781        if value
782            .chars()
783            .all(|character| character >= ' ' && character != '\u{7f}' && character != '\u{a7}')
784        {
785            Ok(Self(value))
786        } else {
787            Err(InvalidCommandString)
788        }
789    }
790
791    pub fn as_str(&self) -> &str {
792        &self.0
793    }
794}
795
796impl Serialize for CommandString {
797    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
798    where
799        S: Serializer,
800    {
801        serializer.serialize_str(&self.0)
802    }
803}
804
805impl<'de> Deserialize<'de> for CommandString {
806    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
807    where
808        D: Deserializer<'de>,
809    {
810        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
811    }
812}
813
814#[derive(Debug, Clone, Copy, PartialEq, Eq)]
815pub struct InvalidCommandString;
816
817impl fmt::Display for InvalidCommandString {
818    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
819        formatter.write_str("command contains a character forbidden by Minecraft")
820    }
821}
822
823impl std::error::Error for InvalidCommandString {}
824
825#[derive(Debug, Clone, PartialEq, Eq, Hash)]
826pub struct PlayerName(String);
827
828impl PlayerName {
829    pub fn new(value: impl Into<String>) -> Result<Self, InvalidPlayerName> {
830        let value = value.into();
831        if !value.is_empty()
832            && value.len() <= 16
833            && value
834                .bytes()
835                .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
836        {
837            Ok(Self(value))
838        } else {
839            Err(InvalidPlayerName)
840        }
841    }
842
843    pub fn as_str(&self) -> &str {
844        &self.0
845    }
846}
847
848impl Serialize for PlayerName {
849    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
850    where
851        S: Serializer,
852    {
853        serializer.serialize_str(&self.0)
854    }
855}
856
857impl<'de> Deserialize<'de> for PlayerName {
858    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
859    where
860        D: Deserializer<'de>,
861    {
862        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
863    }
864}
865
866#[derive(Debug, Clone, Copy, PartialEq, Eq)]
867pub struct InvalidPlayerName;
868
869impl fmt::Display for InvalidPlayerName {
870    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
871        formatter.write_str("player name must contain 1-16 ASCII letters, digits, or underscores")
872    }
873}
874
875impl std::error::Error for InvalidPlayerName {}
876
877#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
878#[serde(transparent)]
879pub struct PositiveI32(NonZeroI32);
880
881impl PositiveI32 {
882    pub fn new(value: i32) -> Option<Self> {
883        NonZeroI32::new(value)
884            .filter(|value| value.get() > 0)
885            .map(Self)
886    }
887
888    pub fn get(self) -> i32 {
889        self.0.get()
890    }
891}
892
893impl<'de> Deserialize<'de> for PositiveI32 {
894    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
895    where
896        D: Deserializer<'de>,
897    {
898        let value = i32::deserialize(deserializer)?;
899        Self::new(value).ok_or_else(|| D::Error::custom("page must be a positive integer"))
900    }
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
904pub struct Uuid([u8; 16]);
905
906impl Uuid {
907    pub fn from_bytes(bytes: [u8; 16]) -> Self {
908        Self(bytes)
909    }
910
911    pub fn into_bytes(self) -> [u8; 16] {
912        self.0
913    }
914
915    pub fn parse(value: &str) -> Result<Self, InvalidUuid> {
916        let mut bytes = [0_u8; 16];
917        let mut digits = value.bytes().filter(|byte| *byte != b'-');
918        for byte in &mut bytes {
919            let high = hex(digits.next().ok_or(InvalidUuid)?)?;
920            let low = hex(digits.next().ok_or(InvalidUuid)?)?;
921            *byte = high << 4 | low;
922        }
923        if digits.next().is_some()
924            || (value.len() != 32 && value.len() != 36)
925            || (value.len() == 36
926                && !value
927                    .bytes()
928                    .enumerate()
929                    .all(|(index, byte)| [8, 13, 18, 23].contains(&index) == (byte == b'-')))
930        {
931            return Err(InvalidUuid);
932        }
933        Ok(Self(bytes))
934    }
935}
936
937impl fmt::Display for Uuid {
938    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
939        for (index, byte) in self.0.iter().enumerate() {
940            if [4, 6, 8, 10].contains(&index) {
941                formatter.write_str("-")?;
942            }
943            write!(formatter, "{byte:02x}")?;
944        }
945        Ok(())
946    }
947}
948
949impl Serialize for Uuid {
950    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
951    where
952        S: Serializer,
953    {
954        serializer.collect_str(self)
955    }
956}
957
958impl<'de> Deserialize<'de> for Uuid {
959    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
960    where
961        D: Deserializer<'de>,
962    {
963        #[derive(Deserialize)]
964        #[serde(untagged)]
965        enum Repr {
966            String(String),
967            IntArray(fastnbt::IntArray),
968            List([i32; 4]),
969        }
970
971        match Repr::deserialize(deserializer)? {
972            Repr::String(value) => Self::parse(&value).map_err(D::Error::custom),
973            Repr::IntArray(value) => uuid_from_ints(value.as_ref()).map_err(D::Error::custom),
974            Repr::List(value) => uuid_from_ints(&value).map_err(D::Error::custom),
975        }
976    }
977}
978
979fn uuid_from_ints(value: &[i32]) -> Result<Uuid, InvalidUuid> {
980    let value: [i32; 4] = value.try_into().map_err(|_| InvalidUuid)?;
981    let mut bytes = [0_u8; 16];
982    for (chunk, integer) in bytes.chunks_exact_mut(4).zip(value) {
983        chunk.copy_from_slice(&integer.to_be_bytes());
984    }
985    Ok(Uuid(bytes))
986}
987
988fn hex(value: u8) -> Result<u8, InvalidUuid> {
989    match value {
990        b'0'..=b'9' => Ok(value - b'0'),
991        b'a'..=b'f' => Ok(value - b'a' + 10),
992        b'A'..=b'F' => Ok(value - b'A' + 10),
993        _ => Err(InvalidUuid),
994    }
995}
996
997#[derive(Debug, Clone, Copy, PartialEq, Eq)]
998pub struct InvalidUuid;
999
1000impl fmt::Display for InvalidUuid {
1001    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1002        formatter.write_str("invalid UUID")
1003    }
1004}
1005
1006impl std::error::Error for InvalidUuid {}
1007
1008#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1009pub enum TextColor {
1010    Named(NamedColor),
1011    Rgb(u32),
1012}
1013
1014#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1015#[serde(rename_all = "snake_case")]
1016pub enum NamedColor {
1017    Black,
1018    DarkBlue,
1019    DarkGreen,
1020    DarkAqua,
1021    DarkRed,
1022    DarkPurple,
1023    Gold,
1024    Gray,
1025    DarkGray,
1026    Blue,
1027    Green,
1028    Aqua,
1029    Red,
1030    LightPurple,
1031    Yellow,
1032    White,
1033}
1034
1035impl Serialize for TextColor {
1036    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1037    where
1038        S: Serializer,
1039    {
1040        match self {
1041            Self::Named(color) => color.serialize(serializer),
1042            Self::Rgb(rgb) => serializer.serialize_str(&format!("#{:06x}", rgb & 0x00ff_ffff)),
1043        }
1044    }
1045}
1046
1047impl<'de> Deserialize<'de> for TextColor {
1048    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1049    where
1050        D: Deserializer<'de>,
1051    {
1052        let value = String::deserialize(deserializer)?;
1053        if let Some(rgb) = value.strip_prefix('#')
1054            && rgb.len() == 6
1055        {
1056            return u32::from_str_radix(rgb, 16)
1057                .map(Self::Rgb)
1058                .map_err(D::Error::custom);
1059        }
1060        serde_json::from_value::<NamedColor>(serde_json::Value::String(value))
1061            .map(Self::Named)
1062            .map_err(D::Error::custom)
1063    }
1064}
1065
1066#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
1067#[serde(transparent)]
1068pub struct ShadowColor(i32);
1069
1070impl ShadowColor {
1071    pub fn from_argb(argb: i32) -> Self {
1072        Self(argb)
1073    }
1074
1075    pub fn argb(self) -> i32 {
1076        self.0
1077    }
1078}
1079
1080impl<'de> Deserialize<'de> for ShadowColor {
1081    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1082    where
1083        D: Deserializer<'de>,
1084    {
1085        #[derive(Deserialize)]
1086        #[serde(untagged)]
1087        enum Repr {
1088            Argb(i32),
1089            Rgba([f32; 4]),
1090        }
1091
1092        match Repr::deserialize(deserializer)? {
1093            Repr::Argb(argb) => Ok(Self(argb)),
1094            Repr::Rgba(rgba) => {
1095                if rgba
1096                    .iter()
1097                    .any(|value| !value.is_finite() || !(0.0..=1.0).contains(value))
1098                {
1099                    return Err(D::Error::custom(
1100                        "shadow color channels must be between 0 and 1",
1101                    ));
1102                }
1103                let channel = |value: f32| (value * 255.0).round() as u32;
1104                let [red, green, blue, alpha] = rgba.map(channel);
1105                Ok(Self(
1106                    ((alpha << 24) | (red << 16) | (green << 8) | blue) as i32,
1107                ))
1108            }
1109        }
1110    }
1111}
1112
1113impl<V: Serialize> Serialize for ComponentObject<V> {
1114    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1115    where
1116        S: Serializer,
1117    {
1118        let mut map = serializer.serialize_map(None)?;
1119        serialize_content(&mut map, &self.content)?;
1120        serialize_style(&mut map, &self.style)?;
1121        if !self.extra.is_empty() {
1122            map.serialize_entry("extra", &self.extra)?;
1123        }
1124        map.end()
1125    }
1126}
1127
1128fn serialize_content<M, V>(map: &mut M, content: &Content<V>) -> Result<(), M::Error>
1129where
1130    M: SerializeMap,
1131    V: Serialize,
1132{
1133    match content {
1134        Content::Text { text } => {
1135            map.serialize_entry("type", "text")?;
1136            map.serialize_entry("text", text)?;
1137        }
1138        Content::Translatable {
1139            translate,
1140            fallback,
1141            with,
1142        } => {
1143            map.serialize_entry("type", "translatable")?;
1144            map.serialize_entry("translate", translate)?;
1145            if let Some(fallback) = fallback {
1146                map.serialize_entry("fallback", fallback)?;
1147            }
1148            if !with.is_empty() {
1149                map.serialize_entry("with", with)?;
1150            }
1151        }
1152        Content::Score { score } => {
1153            map.serialize_entry("type", "score")?;
1154            map.serialize_entry("score", score)?;
1155        }
1156        Content::Selector {
1157            selector,
1158            separator,
1159        } => {
1160            map.serialize_entry("type", "selector")?;
1161            map.serialize_entry("selector", selector)?;
1162            if let Some(separator) = separator {
1163                map.serialize_entry("separator", separator)?;
1164            }
1165        }
1166        Content::Keybind { keybind } => {
1167            map.serialize_entry("type", "keybind")?;
1168            map.serialize_entry("keybind", keybind)?;
1169        }
1170        Content::Nbt {
1171            nbt,
1172            target,
1173            display,
1174            separator,
1175        } => {
1176            map.serialize_entry("type", "nbt")?;
1177            map.serialize_entry("nbt", nbt)?;
1178            match target {
1179                NbtTarget::Entity(entity) => {
1180                    map.serialize_entry("source", "entity")?;
1181                    map.serialize_entry("entity", entity)?;
1182                }
1183                NbtTarget::Block(block) => {
1184                    map.serialize_entry("source", "block")?;
1185                    map.serialize_entry("block", block)?;
1186                }
1187                NbtTarget::Storage(storage) => {
1188                    map.serialize_entry("source", "storage")?;
1189                    map.serialize_entry("storage", storage)?;
1190                }
1191            }
1192            match display {
1193                NbtDisplay::Styled => {}
1194                NbtDisplay::Plain => map.serialize_entry("plain", &true)?,
1195                NbtDisplay::Interpret => map.serialize_entry("interpret", &true)?,
1196            }
1197            if let Some(separator) = separator {
1198                map.serialize_entry("separator", separator)?;
1199            }
1200        }
1201        Content::Object { object, fallback } => {
1202            map.serialize_entry("type", "object")?;
1203            match object {
1204                ObjectContent::Atlas { atlas, sprite } => {
1205                    map.serialize_entry("object", "atlas")?;
1206                    if let Some(atlas) = atlas {
1207                        map.serialize_entry("atlas", atlas)?;
1208                    }
1209                    map.serialize_entry("sprite", sprite)?;
1210                }
1211                ObjectContent::Player { player, hat } => {
1212                    map.serialize_entry("object", "player")?;
1213                    map.serialize_entry("player", player)?;
1214                    if let Some(hat) = hat {
1215                        map.serialize_entry("hat", hat)?;
1216                    }
1217                }
1218            }
1219            if let Some(fallback) = fallback {
1220                map.serialize_entry("fallback", fallback)?;
1221            }
1222        }
1223    }
1224    Ok(())
1225}
1226
1227fn serialize_style<M, V>(map: &mut M, style: &Style<V>) -> Result<(), M::Error>
1228where
1229    M: SerializeMap,
1230    V: Serialize,
1231{
1232    macro_rules! optional {
1233        ($field:ident) => {
1234            if let Some(value) = &style.$field {
1235                map.serialize_entry(stringify!($field), value)?;
1236            }
1237        };
1238    }
1239    optional!(color);
1240    optional!(font);
1241    optional!(bold);
1242    optional!(italic);
1243    optional!(underlined);
1244    optional!(strikethrough);
1245    optional!(obfuscated);
1246    optional!(shadow_color);
1247    optional!(insertion);
1248    optional!(click_event);
1249    optional!(hover_event);
1250    Ok(())
1251}
1252
1253#[derive(Deserialize)]
1254#[serde(bound(deserialize = "V: Deserialize<'de>"))]
1255struct RawComponent<V> {
1256    #[serde(rename = "type", default)]
1257    kind: Option<String>,
1258    #[serde(default)]
1259    text: Option<String>,
1260    #[serde(default)]
1261    translate: Option<String>,
1262    #[serde(default)]
1263    fallback: Option<String>,
1264    #[serde(default)]
1265    with: Vec<Component<V>>,
1266    #[serde(default)]
1267    score: Option<Score>,
1268    #[serde(default)]
1269    selector: Option<String>,
1270    #[serde(default)]
1271    separator: Option<Box<Component<V>>>,
1272    #[serde(default)]
1273    keybind: Option<String>,
1274    #[serde(default)]
1275    nbt: Option<String>,
1276    #[serde(default)]
1277    source: Option<NbtSource>,
1278    #[serde(default)]
1279    interpret: bool,
1280    #[serde(default)]
1281    plain: bool,
1282    #[serde(default)]
1283    entity: Option<String>,
1284    #[serde(default)]
1285    block: Option<String>,
1286    #[serde(default)]
1287    storage: Option<ResourceLocation>,
1288    #[serde(default)]
1289    object: Option<String>,
1290    #[serde(default)]
1291    atlas: Option<ResourceLocation>,
1292    #[serde(default)]
1293    sprite: Option<ResourceLocation>,
1294    #[serde(default)]
1295    player: Option<PlayerProfile>,
1296    #[serde(default)]
1297    hat: Option<bool>,
1298    #[serde(default)]
1299    extra: Vec<Component<V>>,
1300    #[serde(default)]
1301    color: Option<TextColor>,
1302    #[serde(default)]
1303    font: Option<ResourceLocation>,
1304    #[serde(default)]
1305    bold: Option<bool>,
1306    #[serde(default)]
1307    italic: Option<bool>,
1308    #[serde(default)]
1309    underlined: Option<bool>,
1310    #[serde(default)]
1311    strikethrough: Option<bool>,
1312    #[serde(default)]
1313    obfuscated: Option<bool>,
1314    #[serde(default)]
1315    shadow_color: Option<ShadowColor>,
1316    #[serde(default)]
1317    insertion: Option<String>,
1318    #[serde(default)]
1319    click_event: Option<ClickEvent<V>>,
1320    #[serde(default)]
1321    hover_event: Option<HoverEvent<V>>,
1322}
1323
1324#[derive(Debug, Clone, Copy, Deserialize)]
1325#[serde(rename_all = "snake_case")]
1326enum NbtSource {
1327    Entity,
1328    Block,
1329    Storage,
1330}
1331
1332impl<'de, V> Deserialize<'de> for ComponentObject<V>
1333where
1334    V: Deserialize<'de>,
1335{
1336    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1337    where
1338        D: Deserializer<'de>,
1339    {
1340        let raw = RawComponent::deserialize(deserializer)?;
1341        raw.try_into().map_err(D::Error::custom)
1342    }
1343}
1344
1345impl<V> TryFrom<RawComponent<V>> for ComponentObject<V> {
1346    type Error = InvalidComponentObject;
1347
1348    fn try_from(raw: RawComponent<V>) -> Result<Self, Self::Error> {
1349        let selected = select_content(&raw).ok_or(InvalidComponentObject::MissingContent)?;
1350        let content = match selected {
1351            SelectedContent::Text => Content::Text {
1352                text: raw.text.ok_or(InvalidComponentObject::MissingContent)?,
1353            },
1354            SelectedContent::Translatable => Content::Translatable {
1355                translate: raw
1356                    .translate
1357                    .ok_or(InvalidComponentObject::MissingContent)?,
1358                fallback: raw.fallback,
1359                with: raw.with,
1360            },
1361            SelectedContent::Score => Content::Score {
1362                score: raw.score.ok_or(InvalidComponentObject::MissingContent)?,
1363            },
1364            SelectedContent::Selector => Content::Selector {
1365                selector: raw.selector.ok_or(InvalidComponentObject::MissingContent)?,
1366                separator: raw.separator,
1367            },
1368            SelectedContent::Keybind => Content::Keybind {
1369                keybind: raw.keybind.ok_or(InvalidComponentObject::MissingContent)?,
1370            },
1371            SelectedContent::Nbt => {
1372                if raw.interpret && raw.plain {
1373                    return Err(InvalidComponentObject::ConflictingNbtDisplay);
1374                }
1375                let target =
1376                    select_nbt_target(&raw).ok_or(InvalidComponentObject::MissingNbtTarget)?;
1377                let display = if raw.interpret {
1378                    NbtDisplay::Interpret
1379                } else if raw.plain {
1380                    NbtDisplay::Plain
1381                } else {
1382                    NbtDisplay::Styled
1383                };
1384                Content::Nbt {
1385                    nbt: raw.nbt.ok_or(InvalidComponentObject::MissingContent)?,
1386                    target,
1387                    display,
1388                    separator: raw.separator,
1389                }
1390            }
1391            SelectedContent::Object => {
1392                let object = match raw.object.as_deref() {
1393                    Some("player") => ObjectContent::Player {
1394                        player: raw
1395                            .player
1396                            .ok_or(InvalidComponentObject::MissingObjectField)?,
1397                        hat: raw.hat,
1398                    },
1399                    None | Some("atlas") => ObjectContent::Atlas {
1400                        atlas: raw.atlas,
1401                        sprite: raw
1402                            .sprite
1403                            .ok_or(InvalidComponentObject::MissingObjectField)?,
1404                    },
1405                    Some(_) => return Err(InvalidComponentObject::InvalidObjectType),
1406                };
1407                Content::Object {
1408                    object,
1409                    fallback: raw.fallback,
1410                }
1411            }
1412        };
1413        Ok(Self {
1414            content,
1415            style: Style {
1416                color: raw.color,
1417                font: raw.font,
1418                bold: raw.bold,
1419                italic: raw.italic,
1420                underlined: raw.underlined,
1421                strikethrough: raw.strikethrough,
1422                obfuscated: raw.obfuscated,
1423                shadow_color: raw.shadow_color,
1424                insertion: raw.insertion,
1425                click_event: raw.click_event,
1426                hover_event: raw.hover_event,
1427            },
1428            extra: raw.extra,
1429        })
1430    }
1431}
1432
1433#[derive(Debug, Clone, Copy)]
1434enum SelectedContent {
1435    Text,
1436    Translatable,
1437    Score,
1438    Selector,
1439    Keybind,
1440    Nbt,
1441    Object,
1442}
1443
1444fn select_content<V>(raw: &RawComponent<V>) -> Option<SelectedContent> {
1445    let explicit = match raw.kind.as_deref() {
1446        Some("text") if raw.text.is_some() => Some(SelectedContent::Text),
1447        Some("translatable") if raw.translate.is_some() => Some(SelectedContent::Translatable),
1448        Some("score") if raw.score.is_some() => Some(SelectedContent::Score),
1449        Some("selector") if raw.selector.is_some() => Some(SelectedContent::Selector),
1450        Some("keybind") if raw.keybind.is_some() => Some(SelectedContent::Keybind),
1451        Some("nbt") if raw.nbt.is_some() && select_nbt_target(raw).is_some() => {
1452            Some(SelectedContent::Nbt)
1453        }
1454        Some("object") if object_fields_are_valid(raw) => Some(SelectedContent::Object),
1455        _ => None,
1456    };
1457    explicit.or_else(|| {
1458        if raw.text.is_some() {
1459            Some(SelectedContent::Text)
1460        } else if raw.translate.is_some() {
1461            Some(SelectedContent::Translatable)
1462        } else if raw.score.is_some() {
1463            Some(SelectedContent::Score)
1464        } else if raw.selector.is_some() {
1465            Some(SelectedContent::Selector)
1466        } else if raw.keybind.is_some() {
1467            Some(SelectedContent::Keybind)
1468        } else if raw.nbt.is_some() && select_nbt_target(raw).is_some() {
1469            Some(SelectedContent::Nbt)
1470        } else if object_fields_are_valid(raw) {
1471            Some(SelectedContent::Object)
1472        } else {
1473            None
1474        }
1475    })
1476}
1477
1478fn object_fields_are_valid<V>(raw: &RawComponent<V>) -> bool {
1479    match raw.object.as_deref() {
1480        Some("player") => raw.player.is_some(),
1481        None | Some("atlas") => raw.sprite.is_some(),
1482        Some(_) => false,
1483    }
1484}
1485
1486fn select_nbt_target<V>(raw: &RawComponent<V>) -> Option<NbtTarget> {
1487    match raw.source {
1488        Some(NbtSource::Entity) => raw.entity.clone().map(NbtTarget::Entity),
1489        Some(NbtSource::Block) => raw.block.clone().map(NbtTarget::Block),
1490        Some(NbtSource::Storage) => raw.storage.clone().map(NbtTarget::Storage),
1491        None => raw
1492            .entity
1493            .clone()
1494            .map(NbtTarget::Entity)
1495            .or_else(|| raw.block.clone().map(NbtTarget::Block))
1496            .or_else(|| raw.storage.clone().map(NbtTarget::Storage)),
1497    }
1498}
1499
1500#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1501pub enum InvalidComponentObject {
1502    MissingContent,
1503    MissingNbtTarget,
1504    ConflictingNbtDisplay,
1505    MissingObjectField,
1506    InvalidObjectType,
1507}
1508
1509impl fmt::Display for InvalidComponentObject {
1510    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1511        formatter.write_str(match self {
1512            Self::MissingContent => "text component object has no valid content",
1513            Self::MissingNbtTarget => "NBT text component has no matching source",
1514            Self::ConflictingNbtDisplay => "NBT text component cannot be plain and interpreted",
1515            Self::MissingObjectField => "object text component is missing a required field",
1516            Self::InvalidObjectType => "unknown object text component type",
1517        })
1518    }
1519}
1520
1521impl std::error::Error for InvalidComponentObject {}
1522
1523impl Component<NbtValue> {
1524    pub(crate) fn normalized_root_for_nbt(&self) -> Self {
1525        let component = normalize_nbt(self.clone());
1526        match component {
1527            Component::Sequence(_) => Component::Object(Box::new(component_into_object(component))),
1528            Component::Object(object) => {
1529                let ComponentObject {
1530                    content,
1531                    style,
1532                    extra,
1533                } = *object;
1534                match content {
1535                    Content::Text { text } if style.is_empty() && extra.is_empty() => {
1536                        Component::Text(text)
1537                    }
1538                    content => Component::Object(Box::new(ComponentObject {
1539                        content,
1540                        style,
1541                        extra,
1542                    })),
1543                }
1544            }
1545            _ => component,
1546        }
1547    }
1548}
1549
1550fn normalize_nbt(component: NbtComponent) -> NbtComponent {
1551    match component {
1552        Component::Text(_) => component,
1553        Component::Sequence(sequence) => {
1554            let ComponentSequence { first, rest } = sequence;
1555            let first = Component::Object(Box::new(component_into_object(normalize_nbt(*first))));
1556            let rest = rest
1557                .into_iter()
1558                .map(normalize_nbt)
1559                .map(component_into_object)
1560                .map(Box::new)
1561                .map(Component::Object);
1562            Component::Sequence(ComponentSequence::new(first, rest))
1563        }
1564        Component::Object(mut object) => {
1565            object.extra = object
1566                .extra
1567                .into_iter()
1568                .map(normalize_nbt)
1569                .map(component_into_object)
1570                .map(Box::new)
1571                .map(Component::Object)
1572                .collect();
1573            match &mut object.content {
1574                Content::Translatable { with, .. } => {
1575                    *with = std::mem::take(with)
1576                        .into_iter()
1577                        .map(normalize_nbt)
1578                        .map(component_into_object)
1579                        .map(Box::new)
1580                        .map(Component::Object)
1581                        .collect();
1582                }
1583                Content::Selector { separator, .. } | Content::Nbt { separator, .. } => {
1584                    if let Some(value) = separator.take() {
1585                        *separator = Some(Box::new(normalize_nbt(*value)));
1586                    }
1587                }
1588                _ => {}
1589            }
1590            if let Some(hover) = &mut object.style.hover_event {
1591                match hover {
1592                    HoverEvent::ShowText { value } => {
1593                        **value = normalize_nbt(std::mem::take(value.as_mut()));
1594                    }
1595                    HoverEvent::ShowEntity { name, .. } => {
1596                        if let Some(value) = name.take() {
1597                            *name = Some(Box::new(normalize_nbt(*value)));
1598                        }
1599                    }
1600                    HoverEvent::ShowItem { .. } => {}
1601                }
1602            }
1603            Component::Object(object)
1604        }
1605    }
1606}
1607
1608fn component_into_object(component: NbtComponent) -> ComponentObject<NbtValue> {
1609    match component {
1610        Component::Text(text) => ComponentObject::text(text),
1611        Component::Object(object) => *object,
1612        Component::Sequence(sequence) => {
1613            let ComponentSequence { first, rest } = sequence;
1614            let mut first = component_into_object(*first);
1615            first.extra.extend(
1616                rest.into_iter()
1617                    .map(component_into_object)
1618                    .map(Box::new)
1619                    .map(Component::Object),
1620            );
1621            first
1622        }
1623    }
1624}