Skip to main content

mcproto_types/
component.rs

1//! Data model for Minecraft text components.
2//!
3//! The types in this module represent the component schema shared by JSON and
4//! NBT text component encodings.
5
6use std::{collections::BTreeMap, fmt, num::NonZeroI32};
7
8use fastnbt::Value as NbtValue;
9use serde::{
10    Deserialize, Deserializer, Serialize, Serializer,
11    de::{Error as _, MapAccess, SeqAccess, Visitor, value::MapAccessDeserializer},
12    ser::{SerializeMap, SerializeSeq},
13};
14
15/// Java Edition release whose text component schema is represented here.
16pub const TEXT_COMPONENT_FORMAT_VERSION: &str = "26.1";
17
18/// The Java Edition text component schema documented for the current protocol.
19///
20/// `V` is the wire format's deliberately dynamic value type. Use
21/// [`NbtComponent`] for NBT and [`JsonComponent`] for JSON.
22#[derive(Debug, Clone, PartialEq)]
23pub enum Component<V> {
24    /// The string shorthand for a plain-text component.
25    Text(String),
26    /// The non-empty list shorthand.
27    Sequence(ComponentSequence<V>),
28    /// A full component object.
29    Object(Box<ComponentObject<V>>),
30}
31
32/// A text component whose dynamic payloads use NBT values.
33pub type NbtComponent = Component<NbtValue>;
34/// A text component whose dynamic payloads use JSON values.
35pub type JsonComponent = Component<serde_json::Value>;
36
37impl<V> Component<V> {
38    /// Creates the string shorthand for a plain-text component.
39    pub fn text(value: impl Into<String>) -> Self {
40        Self::Text(value.into())
41    }
42
43    /// Creates a full component object with the supplied content.
44    ///
45    /// The new object has an empty [`Style`] and no extra components.
46    pub fn object(content: Content<V>) -> Self {
47        Self::Object(Box::new(ComponentObject::new(content)))
48    }
49
50    /// Creates a non-empty component sequence from its first element and the
51    /// remaining elements.
52    pub fn sequence(first: Component<V>, rest: impl IntoIterator<Item = Component<V>>) -> Self {
53        Self::Sequence(ComponentSequence::new(first, rest))
54    }
55
56    pub(crate) fn validate_depth(&self, max_depth: usize) -> Result<(), ComponentDepthError> {
57        let mut pending = vec![(self, 1_usize)];
58        while let Some((component, depth)) = pending.pop() {
59            if depth > max_depth {
60                return Err(ComponentDepthError { max_depth });
61            }
62            let next = depth + 1;
63            match component {
64                Self::Text(_) => {}
65                Self::Sequence(sequence) => {
66                    pending.extend(sequence.iter().map(|child| (child, next)));
67                }
68                Self::Object(object) => {
69                    pending.extend(object.extra.iter().map(|child| (child, next)));
70                    match &object.content {
71                        Content::Translatable { with, .. } => {
72                            pending.extend(with.iter().map(|child| (child, next)));
73                        }
74                        Content::Selector { separator, .. } | Content::Nbt { separator, .. } => {
75                            if let Some(separator) = separator {
76                                pending.push((separator, next));
77                            }
78                        }
79                        _ => {}
80                    }
81                    if let Some(hover) = &object.style.hover_event {
82                        match hover {
83                            HoverEvent::ShowText { value } => pending.push((value, next)),
84                            HoverEvent::ShowEntity { name, .. } => {
85                                if let Some(name) = name {
86                                    pending.push((name, next));
87                                }
88                            }
89                            HoverEvent::ShowItem { .. } => {}
90                        }
91                    }
92                }
93            }
94        }
95        Ok(())
96    }
97}
98
99impl Component<serde_json::Value> {
100    pub(crate) fn validate_dynamic_depth(
101        &self,
102        max_depth: usize,
103    ) -> Result<(), ComponentDepthError> {
104        validate_dynamic_values(self, |root| {
105            let mut pending = vec![(root, 1_usize)];
106            while let Some((value, depth)) = pending.pop() {
107                if depth > max_depth {
108                    return Err(ComponentDepthError { max_depth });
109                }
110                let next = depth + 1;
111                match value {
112                    serde_json::Value::Array(values) => {
113                        pending.extend(values.iter().map(|value| (value, next)));
114                    }
115                    serde_json::Value::Object(values) => {
116                        pending.extend(values.values().map(|value| (value, next)));
117                    }
118                    _ => {}
119                }
120            }
121            Ok(())
122        })
123    }
124}
125
126impl Component<NbtValue> {
127    pub(crate) fn validate_dynamic_depth(
128        &self,
129        max_depth: usize,
130    ) -> Result<(), ComponentDepthError> {
131        validate_dynamic_values(self, |root| {
132            let mut pending = vec![(root, 1_usize)];
133            while let Some((value, depth)) = pending.pop() {
134                if depth > max_depth {
135                    return Err(ComponentDepthError { max_depth });
136                }
137                let next = depth + 1;
138                match value {
139                    NbtValue::List(values) => {
140                        pending.extend(values.iter().map(|value| (value, next)));
141                    }
142                    NbtValue::Compound(values) => {
143                        pending.extend(values.values().map(|value| (value, next)));
144                    }
145                    _ => {}
146                }
147            }
148            Ok(())
149        })
150    }
151}
152
153fn validate_dynamic_values<V, E>(
154    root: &Component<V>,
155    mut validate: impl FnMut(&V) -> Result<(), E>,
156) -> Result<(), E> {
157    let mut pending = vec![root];
158    while let Some(component) = pending.pop() {
159        match component {
160            Component::Text(_) => {}
161            Component::Sequence(sequence) => pending.extend(sequence.iter()),
162            Component::Object(object) => {
163                pending.extend(&object.extra);
164                match &object.content {
165                    Content::Translatable { with, .. } => pending.extend(with),
166                    Content::Selector { separator, .. } | Content::Nbt { separator, .. } => {
167                        if let Some(separator) = separator {
168                            pending.push(separator);
169                        }
170                    }
171                    _ => {}
172                }
173                if let Some(click) = &object.style.click_event {
174                    match click {
175                        ClickEvent::ShowDialog {
176                            dialog: DialogReference::Inline(values),
177                        } => {
178                            for value in values.values() {
179                                validate(value)?;
180                            }
181                        }
182                        ClickEvent::Custom {
183                            payload: Some(value),
184                            ..
185                        } => validate(value)?,
186                        _ => {}
187                    }
188                }
189                if let Some(hover) = &object.style.hover_event {
190                    match hover {
191                        HoverEvent::ShowText { value } => pending.push(value),
192                        HoverEvent::ShowItem { components, .. } => {
193                            for value in components.values() {
194                                validate(value)?;
195                            }
196                        }
197                        HoverEvent::ShowEntity { name, .. } => {
198                            if let Some(name) = name {
199                                pending.push(name);
200                            }
201                        }
202                    }
203                }
204            }
205        }
206    }
207    Ok(())
208}
209
210impl<V> Default for Component<V> {
211    fn default() -> Self {
212        Self::text("")
213    }
214}
215
216impl<V> From<String> for Component<V> {
217    fn from(value: String) -> Self {
218        Self::text(value)
219    }
220}
221
222impl<V> From<&str> for Component<V> {
223    fn from(value: &str) -> Self {
224        Self::text(value)
225    }
226}
227
228impl<V: Serialize> Serialize for Component<V> {
229    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
230    where
231        S: Serializer,
232    {
233        match self {
234            Self::Text(text) => serializer.serialize_str(text),
235            Self::Sequence(sequence) => sequence.serialize(serializer),
236            Self::Object(object) => object.serialize(serializer),
237        }
238    }
239}
240
241impl<'de, V> Deserialize<'de> for Component<V>
242where
243    V: Deserialize<'de>,
244{
245    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246    where
247        D: Deserializer<'de>,
248    {
249        struct ComponentVisitor<V>(std::marker::PhantomData<V>);
250
251        impl<'de, V> Visitor<'de> for ComponentVisitor<V>
252        where
253            V: Deserialize<'de>,
254        {
255            type Value = Component<V>;
256
257            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
258                formatter.write_str("a text component string, non-empty list, or object")
259            }
260
261            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
262            where
263                E: serde::de::Error,
264            {
265                Ok(Component::Text(value.to_owned()))
266            }
267
268            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
269            where
270                E: serde::de::Error,
271            {
272                Ok(Component::Text(value))
273            }
274
275            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
276            where
277                A: SeqAccess<'de>,
278            {
279                let mut components =
280                    Vec::with_capacity(sequence.size_hint().unwrap_or(0).min(1024));
281                while let Some(component) = sequence.next_element()? {
282                    components.push(component);
283                }
284                ComponentSequence::try_from(components)
285                    .map(Component::Sequence)
286                    .map_err(A::Error::custom)
287            }
288
289            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
290            where
291                A: MapAccess<'de>,
292            {
293                ComponentObject::deserialize(MapAccessDeserializer::new(map))
294                    .map(Box::new)
295                    .map(Component::Object)
296            }
297        }
298
299        deserializer.deserialize_any(ComponentVisitor(std::marker::PhantomData))
300    }
301}
302
303/// A non-empty list shorthand for a sequence of text components.
304#[derive(Debug, Clone, PartialEq)]
305pub struct ComponentSequence<V> {
306    first: Box<Component<V>>,
307    rest: Vec<Component<V>>,
308}
309
310impl<V> ComponentSequence<V> {
311    /// Creates a sequence from its required first component and any remaining
312    /// components.
313    pub fn new(first: Component<V>, rest: impl IntoIterator<Item = Component<V>>) -> Self {
314        Self {
315            first: Box::new(first),
316            rest: rest.into_iter().collect(),
317        }
318    }
319
320    /// Returns the first component in the sequence.
321    pub fn first(&self) -> &Component<V> {
322        &self.first
323    }
324
325    /// Returns all components after the first one.
326    pub fn rest(&self) -> &[Component<V>] {
327        &self.rest
328    }
329
330    /// Iterates over every component in order, including the first one.
331    pub fn iter(&self) -> impl Iterator<Item = &Component<V>> {
332        std::iter::once(self.first.as_ref()).chain(&self.rest)
333    }
334}
335
336impl<V> TryFrom<Vec<Component<V>>> for ComponentSequence<V> {
337    type Error = EmptyComponentSequence;
338
339    fn try_from(mut value: Vec<Component<V>>) -> Result<Self, Self::Error> {
340        if value.is_empty() {
341            return Err(EmptyComponentSequence);
342        }
343        let rest = value.split_off(1);
344        let first = value.pop().ok_or(EmptyComponentSequence)?;
345        Ok(Self::new(first, rest))
346    }
347}
348
349impl<V: Serialize> Serialize for ComponentSequence<V> {
350    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
351    where
352        S: Serializer,
353    {
354        let mut sequence = serializer.serialize_seq(Some(1 + self.rest.len()))?;
355        for component in self.iter() {
356            sequence.serialize_element(component)?;
357        }
358        sequence.end()
359    }
360}
361
362/// Error returned when an empty list is converted into a [`ComponentSequence`].
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub struct EmptyComponentSequence;
365
366impl fmt::Display for EmptyComponentSequence {
367    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
368        formatter.write_str("a text component sequence cannot be empty")
369    }
370}
371
372impl std::error::Error for EmptyComponentSequence {}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
375pub(crate) struct ComponentDepthError {
376    pub max_depth: usize,
377}
378
379impl fmt::Display for ComponentDepthError {
380    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
381        write!(
382            formatter,
383            "text component nesting exceeds {} levels",
384            self.max_depth
385        )
386    }
387}
388
389impl std::error::Error for ComponentDepthError {}
390
391/// A full text component object with content, style, and appended components.
392#[derive(Debug, Clone, PartialEq)]
393pub struct ComponentObject<V> {
394    /// The content rendered by this component.
395    pub content: Content<V>,
396    /// Optional formatting and interaction behavior.
397    pub style: Style<V>,
398    /// Components appended after this component.
399    pub extra: Vec<Component<V>>,
400}
401
402impl<V> ComponentObject<V> {
403    /// Creates an object with the supplied content, an empty style, and no
404    /// extra components.
405    pub fn new(content: Content<V>) -> Self {
406        Self {
407            content,
408            style: Style::default(),
409            extra: Vec::new(),
410        }
411    }
412
413    /// Creates an object containing literal text.
414    pub fn text(value: impl Into<String>) -> Self {
415        Self::new(Content::Text { text: value.into() })
416    }
417}
418
419/// The mutually exclusive content payload of a [`ComponentObject`].
420#[derive(Debug, Clone, PartialEq)]
421pub enum Content<V> {
422    /// Literal text.
423    Text {
424        /// The text to display.
425        text: String,
426    },
427    /// Text looked up from a translation key.
428    Translatable {
429        /// The translation key.
430        translate: String,
431        /// Text used when the translation key is unavailable.
432        fallback: Option<String>,
433        /// Components substituted into the translated text.
434        with: Vec<Component<V>>,
435    },
436    /// A value from a scoreboard objective.
437    Score {
438        /// The scoreboard holder and objective to query.
439        score: Score,
440    },
441    /// The names selected by an entity selector.
442    Selector {
443        /// The entity selector expression.
444        selector: String,
445        /// Component placed between selected names.
446        separator: Option<Box<Component<V>>>,
447    },
448    /// The localized name of a client key binding.
449    Keybind {
450        /// The key-binding identifier.
451        keybind: String,
452    },
453    /// Values read from an NBT path.
454    Nbt {
455        /// The NBT path to evaluate.
456        nbt: String,
457        /// The entity, block, or storage source to query.
458        target: NbtTarget,
459        /// How extracted values are rendered.
460        display: NbtDisplay,
461        /// Component placed between multiple extracted values.
462        separator: Option<Box<Component<V>>>,
463    },
464    /// A rendered object such as an atlas sprite or player head.
465    Object {
466        /// The object-specific rendering data.
467        object: ObjectContent,
468        /// Fallback text used when the object cannot be rendered.
469        ///
470        /// Added by Java Edition 26.1.
471        fallback: Option<String>,
472    },
473}
474
475/// A scoreboard holder and objective referenced by score content.
476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477pub struct Score {
478    /// The score holder name or selector.
479    pub name: String,
480    /// The scoreboard objective name.
481    pub objective: String,
482}
483
484/// The source queried by an NBT component.
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub enum NbtTarget {
487    /// An entity selected by the contained selector.
488    Entity(String),
489    /// A block at the contained position expression.
490    Block(String),
491    /// A command storage entry identified by its resource location.
492    Storage(ResourceLocation),
493}
494
495/// Controls how values extracted from NBT are rendered.
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
497pub enum NbtDisplay {
498    /// Uses the default styled representation.
499    #[default]
500    Styled,
501    /// Displays the extracted value as plain text.
502    Plain,
503    /// Interprets the extracted value as a serialized text component.
504    Interpret,
505}
506
507/// The object rendered by object content.
508#[derive(Debug, Clone, PartialEq)]
509pub enum ObjectContent {
510    /// A sprite from a texture atlas.
511    Atlas {
512        /// The atlas containing the sprite, or the protocol default.
513        atlas: Option<ResourceLocation>,
514        /// The sprite to render.
515        sprite: ResourceLocation,
516    },
517    /// A player head derived from profile data.
518    Player {
519        /// The player name or complete profile.
520        player: PlayerProfile,
521        /// Whether to render the player's hat layer.
522        hat: Option<bool>,
523    },
524}
525
526/// The shorthand or full representation of a player profile.
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
528#[serde(untagged)]
529pub enum PlayerProfile {
530    /// A profile resolved from a player name.
531    Name(PlayerName),
532    /// Explicit profile data.
533    Profile(Profile),
534}
535
536/// Player profile data used to render a player object.
537#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
538pub struct Profile {
539    /// The player's validated account name.
540    #[serde(default, skip_serializing_if = "Option::is_none")]
541    pub name: Option<PlayerName>,
542    /// The player's UUID.
543    #[serde(default, skip_serializing_if = "Option::is_none")]
544    pub id: Option<Uuid>,
545    /// Signed or unsigned profile properties.
546    #[serde(default, skip_serializing_if = "Vec::is_empty")]
547    pub properties: Vec<ProfileProperty>,
548    /// The player's skin texture resource.
549    #[serde(default, skip_serializing_if = "Option::is_none")]
550    pub texture: Option<ResourceLocation>,
551    /// The player's cape texture resource.
552    #[serde(default, skip_serializing_if = "Option::is_none")]
553    pub cape: Option<ResourceLocation>,
554    /// The player's elytra texture resource.
555    #[serde(default, skip_serializing_if = "Option::is_none")]
556    pub elytra: Option<ResourceLocation>,
557    /// The geometry model used for the player skin.
558    #[serde(default, skip_serializing_if = "Option::is_none")]
559    pub model: Option<PlayerModel>,
560}
561
562/// A property attached to a player profile.
563#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
564pub struct ProfileProperty {
565    /// The kind of profile property.
566    pub name: ProfilePropertyName,
567    /// The encoded property value.
568    pub value: String,
569    /// The optional signature authenticating the value.
570    #[serde(default, skip_serializing_if = "Option::is_none")]
571    pub signature: Option<String>,
572}
573
574/// A supported player profile property name.
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
576#[serde(rename_all = "snake_case")]
577pub enum ProfilePropertyName {
578    /// Player skin and related texture data.
579    Textures,
580}
581
582/// The geometry used to render a player skin.
583#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
584#[serde(rename_all = "snake_case")]
585pub enum PlayerModel {
586    /// The standard model with wide arms.
587    Wide,
588    /// The slim model with narrow arms.
589    Slim,
590}
591
592/// Optional formatting and interaction properties for a component.
593#[derive(Debug, Clone, PartialEq)]
594pub struct Style<V> {
595    /// The text color.
596    pub color: Option<TextColor>,
597    /// The font resource used to render text.
598    pub font: Option<ResourceLocation>,
599    /// Whether text is rendered in bold.
600    pub bold: Option<bool>,
601    /// Whether text is rendered in italics.
602    pub italic: Option<bool>,
603    /// Whether text is underlined.
604    pub underlined: Option<bool>,
605    /// Whether text has a strikethrough line.
606    pub strikethrough: Option<bool>,
607    /// Whether text characters are continuously obfuscated.
608    pub obfuscated: Option<bool>,
609    /// The text shadow color.
610    pub shadow_color: Option<ShadowColor>,
611    /// Text inserted into chat when the component is shift-clicked.
612    pub insertion: Option<String>,
613    /// The action performed when the component is clicked.
614    pub click_event: Option<ClickEvent<V>>,
615    /// The content shown when the component is hovered.
616    pub hover_event: Option<HoverEvent<V>>,
617}
618
619impl<V> Default for Style<V> {
620    fn default() -> Self {
621        Self {
622            color: None,
623            font: None,
624            bold: None,
625            italic: None,
626            underlined: None,
627            strikethrough: None,
628            obfuscated: None,
629            shadow_color: None,
630            insertion: None,
631            click_event: None,
632            hover_event: None,
633        }
634    }
635}
636
637impl<V> Style<V> {
638    fn is_empty(&self) -> bool {
639        self.color.is_none()
640            && self.font.is_none()
641            && self.bold.is_none()
642            && self.italic.is_none()
643            && self.underlined.is_none()
644            && self.strikethrough.is_none()
645            && self.obfuscated.is_none()
646            && self.shadow_color.is_none()
647            && self.insertion.is_none()
648            && self.click_event.is_none()
649            && self.hover_event.is_none()
650    }
651}
652
653/// An action performed when a styled component is clicked.
654#[derive(Debug, Clone, PartialEq, Serialize)]
655#[serde(
656    tag = "action",
657    rename_all = "snake_case",
658    bound(serialize = "V: Serialize")
659)]
660pub enum ClickEvent<V> {
661    /// Opens an HTTP or HTTPS URL.
662    OpenUrl {
663        /// The URL to open.
664        url: HttpUrl,
665    },
666    /// Opens a local file path on the client.
667    OpenFile {
668        /// The file path to open.
669        path: String,
670    },
671    /// Runs a command.
672    RunCommand {
673        /// The command to run.
674        command: CommandString,
675    },
676    /// Places a command into the client's chat input.
677    SuggestCommand {
678        /// The command to suggest.
679        command: CommandString,
680    },
681    /// Changes the current book page.
682    ChangePage {
683        /// The one-based page number.
684        page: PositiveI32,
685    },
686    /// Copies text to the system clipboard.
687    CopyToClipboard {
688        /// The text to copy.
689        value: String,
690    },
691    /// Opens a dialog.
692    ShowDialog {
693        /// The dialog identifier or inline definition.
694        dialog: DialogReference<V>,
695    },
696    /// Performs a custom action identified by a resource location.
697    Custom {
698        /// The custom action identifier.
699        id: ResourceLocation,
700        /// An optional wire-format-specific action payload.
701        #[serde(default, skip_serializing_if = "Option::is_none")]
702        payload: Option<V>,
703    },
704}
705
706/// A reference to a registered dialog or an inline dialog definition.
707#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
708#[serde(
709    untagged,
710    bound(serialize = "V: Serialize", deserialize = "V: Deserialize<'de>")
711)]
712pub enum DialogReference<V> {
713    /// The resource location of a registered dialog.
714    Id(ResourceLocation),
715    /// An inline dialog represented by wire-format-specific values.
716    Inline(BTreeMap<String, V>),
717}
718
719/// Content shown when a styled component is hovered.
720#[derive(Debug, Clone, PartialEq, Serialize)]
721#[serde(
722    tag = "action",
723    rename_all = "snake_case",
724    bound(serialize = "V: Serialize")
725)]
726pub enum HoverEvent<V> {
727    /// Shows another text component.
728    ShowText {
729        /// The component displayed in the tooltip.
730        value: Box<Component<V>>,
731    },
732    /// Shows an item stack.
733    ShowItem {
734        /// The item type.
735        id: ResourceLocation,
736        /// The optional stack size.
737        #[serde(default, skip_serializing_if = "Option::is_none")]
738        count: Option<i32>,
739        /// Item data components keyed by resource location.
740        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
741        components: BTreeMap<ResourceLocation, V>,
742    },
743    /// Shows entity information.
744    ShowEntity {
745        /// The optional entity name displayed in the tooltip.
746        #[serde(default, skip_serializing_if = "Option::is_none")]
747        name: Option<Box<Component<V>>>,
748        /// The entity type.
749        id: ResourceLocation,
750        /// The entity UUID.
751        uuid: Uuid,
752    },
753}
754
755#[derive(Deserialize)]
756#[serde(bound(deserialize = "V: Deserialize<'de>"))]
757struct RawClickEvent<V> {
758    action: ClickAction,
759    #[serde(default)]
760    url: Option<HttpUrl>,
761    #[serde(default)]
762    path: Option<String>,
763    #[serde(default)]
764    command: Option<CommandString>,
765    #[serde(default)]
766    page: Option<PositiveI32>,
767    #[serde(default)]
768    value: Option<String>,
769    #[serde(default)]
770    dialog: Option<DialogReference<V>>,
771    #[serde(default)]
772    id: Option<ResourceLocation>,
773    #[serde(default)]
774    payload: Option<V>,
775}
776
777#[derive(Deserialize)]
778#[serde(rename_all = "snake_case")]
779enum ClickAction {
780    OpenUrl,
781    OpenFile,
782    RunCommand,
783    SuggestCommand,
784    ChangePage,
785    CopyToClipboard,
786    ShowDialog,
787    Custom,
788}
789
790impl<'de, V> Deserialize<'de> for ClickEvent<V>
791where
792    V: Deserialize<'de>,
793{
794    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
795    where
796        D: Deserializer<'de>,
797    {
798        let raw = RawClickEvent::deserialize(deserializer)?;
799        let missing = || D::Error::custom("click event is missing its action payload");
800        match raw.action {
801            ClickAction::OpenUrl => raw.url.map(|url| Self::OpenUrl { url }).ok_or_else(missing),
802            ClickAction::OpenFile => raw
803                .path
804                .map(|path| Self::OpenFile { path })
805                .ok_or_else(missing),
806            ClickAction::RunCommand => raw
807                .command
808                .map(|command| Self::RunCommand { command })
809                .ok_or_else(missing),
810            ClickAction::SuggestCommand => raw
811                .command
812                .map(|command| Self::SuggestCommand { command })
813                .ok_or_else(missing),
814            ClickAction::ChangePage => raw
815                .page
816                .map(|page| Self::ChangePage { page })
817                .ok_or_else(missing),
818            ClickAction::CopyToClipboard => raw
819                .value
820                .map(|value| Self::CopyToClipboard { value })
821                .ok_or_else(missing),
822            ClickAction::ShowDialog => raw
823                .dialog
824                .map(|dialog| Self::ShowDialog { dialog })
825                .ok_or_else(missing),
826            ClickAction::Custom => raw
827                .id
828                .map(|id| Self::Custom {
829                    id,
830                    payload: raw.payload,
831                })
832                .ok_or_else(missing),
833        }
834    }
835}
836
837#[derive(Deserialize)]
838#[serde(bound(deserialize = "V: Deserialize<'de>"))]
839struct RawHoverEvent<V> {
840    action: HoverAction,
841    #[serde(default)]
842    value: Option<Box<Component<V>>>,
843    #[serde(default)]
844    id: Option<ResourceLocation>,
845    #[serde(default)]
846    count: Option<i32>,
847    #[serde(default)]
848    components: BTreeMap<ResourceLocation, V>,
849    #[serde(default)]
850    name: Option<Box<Component<V>>>,
851    #[serde(default)]
852    uuid: Option<Uuid>,
853}
854
855#[derive(Deserialize)]
856enum HoverAction {
857    #[serde(rename = "show_text")]
858    Text,
859    #[serde(rename = "show_item")]
860    Item,
861    #[serde(rename = "show_entity")]
862    Entity,
863}
864
865impl<'de, V> Deserialize<'de> for HoverEvent<V>
866where
867    V: Deserialize<'de>,
868{
869    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
870    where
871        D: Deserializer<'de>,
872    {
873        let raw = RawHoverEvent::deserialize(deserializer)?;
874        let missing = || D::Error::custom("hover event is missing its action payload");
875        match raw.action {
876            HoverAction::Text => raw
877                .value
878                .map(|value| Self::ShowText { value })
879                .ok_or_else(missing),
880            HoverAction::Item => raw
881                .id
882                .map(|id| Self::ShowItem {
883                    id,
884                    count: raw.count,
885                    components: raw.components,
886                })
887                .ok_or_else(missing),
888            HoverAction::Entity => match (raw.id, raw.uuid) {
889                (Some(id), Some(uuid)) => Ok(Self::ShowEntity {
890                    name: raw.name,
891                    id,
892                    uuid,
893                }),
894                _ => Err(missing()),
895            },
896        }
897    }
898}
899
900/// A validated Minecraft resource location.
901///
902/// Namespaces permit lowercase ASCII letters, digits, `_`, `.`, and `-`;
903/// paths additionally permit `/`. The namespace may be omitted on input.
904#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
905pub struct ResourceLocation(String);
906
907impl ResourceLocation {
908    /// Validates and stores a resource location.
909    ///
910    /// Returns [`InvalidResourceLocation`] when the namespace or path contains
911    /// unsupported characters or is empty.
912    pub fn new(value: impl Into<String>) -> Result<Self, InvalidResourceLocation> {
913        let value = value.into();
914        validate_resource_location(&value)?;
915        Ok(Self(value))
916    }
917
918    /// Returns the resource location exactly as it was supplied.
919    pub fn as_str(&self) -> &str {
920        &self.0
921    }
922}
923
924impl fmt::Display for ResourceLocation {
925    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
926        formatter.write_str(&self.0)
927    }
928}
929
930impl Serialize for ResourceLocation {
931    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
932    where
933        S: Serializer,
934    {
935        serializer.serialize_str(&self.0)
936    }
937}
938
939impl<'de> Deserialize<'de> for ResourceLocation {
940    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
941    where
942        D: Deserializer<'de>,
943    {
944        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
945    }
946}
947
948/// Error returned when a string is not a valid [`ResourceLocation`].
949#[derive(Debug, Clone, PartialEq, Eq)]
950pub struct InvalidResourceLocation;
951
952impl fmt::Display for InvalidResourceLocation {
953    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
954        formatter.write_str("invalid Minecraft resource location")
955    }
956}
957
958impl std::error::Error for InvalidResourceLocation {}
959
960fn validate_resource_location(value: &str) -> Result<(), InvalidResourceLocation> {
961    if crate::basic::is_valid_identifier(value) {
962        Ok(())
963    } else {
964        Err(InvalidResourceLocation)
965    }
966}
967
968/// A validated absolute HTTP or HTTPS URL used by an `open_url` click event.
969#[derive(Debug, Clone, PartialEq, Eq, Hash)]
970pub struct HttpUrl(String);
971
972impl HttpUrl {
973    /// Validates and stores an absolute HTTP or HTTPS URL with a host.
974    pub fn new(value: impl Into<String>) -> Result<Self, InvalidHttpUrl> {
975        let value = value.into();
976        let parsed = url::Url::parse(&value).map_err(|_| InvalidHttpUrl)?;
977        if matches!(parsed.scheme(), "http" | "https") && parsed.host().is_some() {
978            Ok(Self(value))
979        } else {
980            Err(InvalidHttpUrl)
981        }
982    }
983
984    /// Returns the original URL string.
985    pub fn as_str(&self) -> &str {
986        &self.0
987    }
988}
989
990impl Serialize for HttpUrl {
991    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
992    where
993        S: Serializer,
994    {
995        serializer.serialize_str(&self.0)
996    }
997}
998
999impl<'de> Deserialize<'de> for HttpUrl {
1000    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1001    where
1002        D: Deserializer<'de>,
1003    {
1004        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
1005    }
1006}
1007
1008/// Error returned when an `open_url` value is not an absolute HTTP or HTTPS URL.
1009#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1010pub struct InvalidHttpUrl;
1011
1012impl fmt::Display for InvalidHttpUrl {
1013    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1014        formatter.write_str("open_url requires an absolute HTTP or HTTPS URL")
1015    }
1016}
1017
1018impl std::error::Error for InvalidHttpUrl {}
1019
1020/// A command string validated for use in a text component click event.
1021#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1022pub struct CommandString(String);
1023
1024impl CommandString {
1025    /// Validates and stores a command string.
1026    ///
1027    /// Minecraft control characters, DEL, and the legacy section sign are
1028    /// rejected.
1029    pub fn new(value: impl Into<String>) -> Result<Self, InvalidCommandString> {
1030        let value = value.into();
1031        if value
1032            .chars()
1033            .all(|character| character >= ' ' && character != '\u{7f}' && character != '\u{a7}')
1034        {
1035            Ok(Self(value))
1036        } else {
1037            Err(InvalidCommandString)
1038        }
1039    }
1040
1041    /// Returns the validated command string.
1042    pub fn as_str(&self) -> &str {
1043        &self.0
1044    }
1045}
1046
1047impl Serialize for CommandString {
1048    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1049    where
1050        S: Serializer,
1051    {
1052        serializer.serialize_str(&self.0)
1053    }
1054}
1055
1056impl<'de> Deserialize<'de> for CommandString {
1057    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1058    where
1059        D: Deserializer<'de>,
1060    {
1061        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
1062    }
1063}
1064
1065/// Error returned when a command contains a character forbidden by Minecraft.
1066#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1067pub struct InvalidCommandString;
1068
1069impl fmt::Display for InvalidCommandString {
1070    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1071        formatter.write_str("command contains a character forbidden by Minecraft")
1072    }
1073}
1074
1075impl std::error::Error for InvalidCommandString {}
1076
1077/// A validated Java Edition player name.
1078#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1079pub struct PlayerName(String);
1080
1081impl PlayerName {
1082    /// Validates and stores a player name containing 1 to 16 ASCII letters,
1083    /// digits, or underscores.
1084    pub fn new(value: impl Into<String>) -> Result<Self, InvalidPlayerName> {
1085        let value = value.into();
1086        if !value.is_empty()
1087            && value.len() <= 16
1088            && value
1089                .bytes()
1090                .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
1091        {
1092            Ok(Self(value))
1093        } else {
1094            Err(InvalidPlayerName)
1095        }
1096    }
1097
1098    /// Returns the validated player name.
1099    pub fn as_str(&self) -> &str {
1100        &self.0
1101    }
1102}
1103
1104impl Serialize for PlayerName {
1105    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1106    where
1107        S: Serializer,
1108    {
1109        serializer.serialize_str(&self.0)
1110    }
1111}
1112
1113impl<'de> Deserialize<'de> for PlayerName {
1114    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1115    where
1116        D: Deserializer<'de>,
1117    {
1118        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
1119    }
1120}
1121
1122/// Error returned when a string is not a valid [`PlayerName`].
1123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1124pub struct InvalidPlayerName;
1125
1126impl fmt::Display for InvalidPlayerName {
1127    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1128        formatter.write_str("player name must contain 1-16 ASCII letters, digits, or underscores")
1129    }
1130}
1131
1132impl std::error::Error for InvalidPlayerName {}
1133
1134/// A strictly positive signed 32-bit integer.
1135///
1136/// Text components use this type for one-based book page numbers.
1137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
1138#[serde(transparent)]
1139pub struct PositiveI32(NonZeroI32);
1140
1141impl PositiveI32 {
1142    /// Creates a positive integer, returning `None` for zero or negative input.
1143    pub fn new(value: i32) -> Option<Self> {
1144        NonZeroI32::new(value)
1145            .filter(|value| value.get() > 0)
1146            .map(Self)
1147    }
1148
1149    /// Returns the contained positive integer.
1150    pub fn get(self) -> i32 {
1151        self.0.get()
1152    }
1153}
1154
1155impl<'de> Deserialize<'de> for PositiveI32 {
1156    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1157    where
1158        D: Deserializer<'de>,
1159    {
1160        let value = i32::deserialize(deserializer)?;
1161        Self::new(value).ok_or_else(|| D::Error::custom("page must be a positive integer"))
1162    }
1163}
1164
1165/// A 128-bit universally unique identifier used by component profile data.
1166///
1167/// Parsing and formatting are delegated to the [`uuid`] crate; the wrapper
1168/// exists to support the custom serde representations used by text components
1169/// (a string, a four-integer list, or an NBT int array).
1170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1171pub struct Uuid(uuid::Uuid);
1172
1173impl Uuid {
1174    /// Creates a UUID from its 16 bytes in network order.
1175    pub fn from_bytes(bytes: [u8; 16]) -> Self {
1176        Self(uuid::Uuid::from_bytes(bytes))
1177    }
1178
1179    /// Returns the UUID as 16 bytes in network order.
1180    pub fn into_bytes(self) -> [u8; 16] {
1181        self.0.into_bytes()
1182    }
1183
1184    /// Parses a compact or hyphenated hexadecimal UUID string.
1185    pub fn parse(value: &str) -> Result<Self, InvalidUuid> {
1186        uuid::Uuid::try_parse(value)
1187            .map(Self)
1188            .map_err(|_| InvalidUuid)
1189    }
1190}
1191
1192impl fmt::Display for Uuid {
1193    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1194        self.0.fmt(formatter)
1195    }
1196}
1197
1198impl Serialize for Uuid {
1199    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1200    where
1201        S: Serializer,
1202    {
1203        serializer.collect_str(self)
1204    }
1205}
1206
1207impl<'de> Deserialize<'de> for Uuid {
1208    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1209    where
1210        D: Deserializer<'de>,
1211    {
1212        struct UuidVisitor;
1213
1214        impl<'de> Visitor<'de> for UuidVisitor {
1215            type Value = Uuid;
1216
1217            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1218                formatter.write_str("a UUID string, four-integer list, or NBT int array")
1219            }
1220
1221            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1222            where
1223                E: serde::de::Error,
1224            {
1225                Uuid::parse(value).map_err(E::custom)
1226            }
1227
1228            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
1229            where
1230                E: serde::de::Error,
1231            {
1232                self.visit_str(&value)
1233            }
1234
1235            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1236            where
1237                A: SeqAccess<'de>,
1238            {
1239                let mut values = [0_i32; 4];
1240                for (index, value) in values.iter_mut().enumerate() {
1241                    *value = sequence
1242                        .next_element()?
1243                        .ok_or_else(|| A::Error::invalid_length(index, &self))?;
1244                }
1245                if sequence.next_element::<serde::de::IgnoredAny>()?.is_some() {
1246                    return Err(A::Error::invalid_length(5, &self));
1247                }
1248                uuid_from_ints(&values).map_err(A::Error::custom)
1249            }
1250
1251            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
1252            where
1253                A: MapAccess<'de>,
1254            {
1255                let value = fastnbt::IntArray::deserialize(MapAccessDeserializer::new(map))?;
1256                uuid_from_ints(value.as_ref()).map_err(A::Error::custom)
1257            }
1258        }
1259
1260        deserializer.deserialize_any(UuidVisitor)
1261    }
1262}
1263
1264fn uuid_from_ints(value: &[i32]) -> Result<Uuid, InvalidUuid> {
1265    let value: [i32; 4] = value.try_into().map_err(|_| InvalidUuid)?;
1266    let mut bytes = [0_u8; 16];
1267    for (chunk, integer) in bytes.chunks_exact_mut(4).zip(value) {
1268        chunk.copy_from_slice(&integer.to_be_bytes());
1269    }
1270    Ok(Uuid::from_bytes(bytes))
1271}
1272
1273/// Error returned when a UUID representation is malformed.
1274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1275pub struct InvalidUuid;
1276
1277impl fmt::Display for InvalidUuid {
1278    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1279        formatter.write_str("invalid UUID")
1280    }
1281}
1282
1283impl std::error::Error for InvalidUuid {}
1284
1285/// A named Minecraft color or an explicit 24-bit RGB color.
1286#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1287pub enum TextColor {
1288    /// One of Minecraft's predefined named colors.
1289    Named(NamedColor),
1290    /// An explicit red, green, and blue color.
1291    Rgb(RgbColor),
1292}
1293
1294impl TextColor {
1295    /// Creates an RGB text color from a 24-bit integer.
1296    pub const fn rgb(value: u32) -> Result<Self, InvalidRgbColor> {
1297        match RgbColor::new(value) {
1298            Ok(value) => Ok(Self::Rgb(value)),
1299            Err(error) => Err(error),
1300        }
1301    }
1302}
1303
1304/// A 24-bit red, green, and blue color.
1305#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1306pub struct RgbColor(u32);
1307
1308impl RgbColor {
1309    /// The largest value that fits in an RGB color.
1310    pub const MAX: u32 = 0x00ff_ffff;
1311
1312    /// Creates a color from a 24-bit `0xRRGGBB` integer.
1313    pub const fn new(value: u32) -> Result<Self, InvalidRgbColor> {
1314        if value <= Self::MAX {
1315            Ok(Self(value))
1316        } else {
1317            Err(InvalidRgbColor)
1318        }
1319    }
1320
1321    /// Creates a color from its red, green, and blue channels.
1322    pub const fn from_channels(red: u8, green: u8, blue: u8) -> Self {
1323        Self(((red as u32) << 16) | ((green as u32) << 8) | blue as u32)
1324    }
1325
1326    /// Returns the color as a 24-bit `0xRRGGBB` integer.
1327    pub const fn value(self) -> u32 {
1328        self.0
1329    }
1330
1331    /// Returns the red, green, and blue channels in that order.
1332    pub const fn channels(self) -> [u8; 3] {
1333        [(self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
1334    }
1335}
1336
1337impl From<RgbColor> for TextColor {
1338    fn from(value: RgbColor) -> Self {
1339        Self::Rgb(value)
1340    }
1341}
1342
1343/// Error returned when an RGB value does not fit in 24 bits.
1344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1345pub struct InvalidRgbColor;
1346
1347impl fmt::Display for InvalidRgbColor {
1348    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1349        formatter.write_str("RGB color must fit in 24 bits")
1350    }
1351}
1352
1353impl std::error::Error for InvalidRgbColor {}
1354
1355/// A predefined Minecraft text color.
1356#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1357#[serde(rename_all = "snake_case")]
1358pub enum NamedColor {
1359    /// Black (`#000000`).
1360    Black,
1361    /// Dark blue (`#0000AA`).
1362    DarkBlue,
1363    /// Dark green (`#00AA00`).
1364    DarkGreen,
1365    /// Dark aqua (`#00AAAA`).
1366    DarkAqua,
1367    /// Dark red (`#AA0000`).
1368    DarkRed,
1369    /// Dark purple (`#AA00AA`).
1370    DarkPurple,
1371    /// Gold (`#FFAA00`).
1372    Gold,
1373    /// Gray (`#AAAAAA`).
1374    Gray,
1375    /// Dark gray (`#555555`).
1376    DarkGray,
1377    /// Blue (`#5555FF`).
1378    Blue,
1379    /// Green (`#55FF55`).
1380    Green,
1381    /// Aqua (`#55FFFF`).
1382    Aqua,
1383    /// Red (`#FF5555`).
1384    Red,
1385    /// Light purple (`#FF55FF`).
1386    LightPurple,
1387    /// Yellow (`#FFFF55`).
1388    Yellow,
1389    /// White (`#FFFFFF`).
1390    White,
1391}
1392
1393impl Serialize for TextColor {
1394    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1395    where
1396        S: Serializer,
1397    {
1398        match self {
1399            Self::Named(color) => color.serialize(serializer),
1400            Self::Rgb(rgb) => serializer.serialize_str(&format!("#{:06x}", rgb.value())),
1401        }
1402    }
1403}
1404
1405impl<'de> Deserialize<'de> for TextColor {
1406    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1407    where
1408        D: Deserializer<'de>,
1409    {
1410        let value = String::deserialize(deserializer)?;
1411        if let Some(rgb) = value.strip_prefix('#')
1412            && rgb.len() == 6
1413        {
1414            return u32::from_str_radix(rgb, 16)
1415                .map_err(D::Error::custom)
1416                .and_then(|value| {
1417                    RgbColor::new(value)
1418                        .map(Self::Rgb)
1419                        .map_err(D::Error::custom)
1420                });
1421        }
1422        serde_json::from_value::<NamedColor>(serde_json::Value::String(value))
1423            .map(Self::Named)
1424            .map_err(D::Error::custom)
1425    }
1426}
1427
1428/// A text shadow color stored as a packed 32-bit ARGB value.
1429#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
1430#[serde(transparent)]
1431pub struct ShadowColor(i32);
1432
1433impl ShadowColor {
1434    /// Creates a shadow color from a packed ARGB value.
1435    pub fn from_argb(argb: i32) -> Self {
1436        Self(argb)
1437    }
1438
1439    /// Returns the packed ARGB value.
1440    pub fn argb(self) -> i32 {
1441        self.0
1442    }
1443}
1444
1445impl<'de> Deserialize<'de> for ShadowColor {
1446    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1447    where
1448        D: Deserializer<'de>,
1449    {
1450        #[derive(Deserialize)]
1451        #[serde(untagged)]
1452        enum Repr {
1453            Argb(i32),
1454            Rgba([f32; 4]),
1455        }
1456
1457        match Repr::deserialize(deserializer)? {
1458            Repr::Argb(argb) => Ok(Self(argb)),
1459            Repr::Rgba(rgba) => {
1460                if rgba
1461                    .iter()
1462                    .any(|value| !value.is_finite() || !(0.0..=1.0).contains(value))
1463                {
1464                    return Err(D::Error::custom(
1465                        "shadow color channels must be between 0 and 1",
1466                    ));
1467                }
1468                let channel = |value: f32| (value * 255.0).round() as u32;
1469                let [red, green, blue, alpha] = rgba.map(channel);
1470                Ok(Self(
1471                    ((alpha << 24) | (red << 16) | (green << 8) | blue) as i32,
1472                ))
1473            }
1474        }
1475    }
1476}
1477
1478impl<V: Serialize> Serialize for ComponentObject<V> {
1479    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1480    where
1481        S: Serializer,
1482    {
1483        let mut map = serializer.serialize_map(None)?;
1484        serialize_content(&mut map, &self.content)?;
1485        serialize_style(&mut map, &self.style)?;
1486        if !self.extra.is_empty() {
1487            map.serialize_entry("extra", &self.extra)?;
1488        }
1489        map.end()
1490    }
1491}
1492
1493fn serialize_content<M, V>(map: &mut M, content: &Content<V>) -> Result<(), M::Error>
1494where
1495    M: SerializeMap,
1496    V: Serialize,
1497{
1498    match content {
1499        Content::Text { text } => {
1500            map.serialize_entry("type", "text")?;
1501            map.serialize_entry("text", text)?;
1502        }
1503        Content::Translatable {
1504            translate,
1505            fallback,
1506            with,
1507        } => {
1508            map.serialize_entry("type", "translatable")?;
1509            map.serialize_entry("translate", translate)?;
1510            if let Some(fallback) = fallback {
1511                map.serialize_entry("fallback", fallback)?;
1512            }
1513            if !with.is_empty() {
1514                map.serialize_entry("with", with)?;
1515            }
1516        }
1517        Content::Score { score } => {
1518            map.serialize_entry("type", "score")?;
1519            map.serialize_entry("score", score)?;
1520        }
1521        Content::Selector {
1522            selector,
1523            separator,
1524        } => {
1525            map.serialize_entry("type", "selector")?;
1526            map.serialize_entry("selector", selector)?;
1527            if let Some(separator) = separator {
1528                map.serialize_entry("separator", separator)?;
1529            }
1530        }
1531        Content::Keybind { keybind } => {
1532            map.serialize_entry("type", "keybind")?;
1533            map.serialize_entry("keybind", keybind)?;
1534        }
1535        Content::Nbt {
1536            nbt,
1537            target,
1538            display,
1539            separator,
1540        } => {
1541            map.serialize_entry("type", "nbt")?;
1542            map.serialize_entry("nbt", nbt)?;
1543            match target {
1544                NbtTarget::Entity(entity) => {
1545                    map.serialize_entry("source", "entity")?;
1546                    map.serialize_entry("entity", entity)?;
1547                }
1548                NbtTarget::Block(block) => {
1549                    map.serialize_entry("source", "block")?;
1550                    map.serialize_entry("block", block)?;
1551                }
1552                NbtTarget::Storage(storage) => {
1553                    map.serialize_entry("source", "storage")?;
1554                    map.serialize_entry("storage", storage)?;
1555                }
1556            }
1557            match display {
1558                NbtDisplay::Styled => {}
1559                NbtDisplay::Plain => map.serialize_entry("plain", &true)?,
1560                NbtDisplay::Interpret => map.serialize_entry("interpret", &true)?,
1561            }
1562            if let Some(separator) = separator {
1563                map.serialize_entry("separator", separator)?;
1564            }
1565        }
1566        Content::Object { object, fallback } => {
1567            map.serialize_entry("type", "object")?;
1568            match object {
1569                ObjectContent::Atlas { atlas, sprite } => {
1570                    map.serialize_entry("object", "atlas")?;
1571                    if let Some(atlas) = atlas {
1572                        map.serialize_entry("atlas", atlas)?;
1573                    }
1574                    map.serialize_entry("sprite", sprite)?;
1575                }
1576                ObjectContent::Player { player, hat } => {
1577                    map.serialize_entry("object", "player")?;
1578                    map.serialize_entry("player", player)?;
1579                    if let Some(hat) = hat {
1580                        map.serialize_entry("hat", hat)?;
1581                    }
1582                }
1583            }
1584            if let Some(fallback) = fallback {
1585                map.serialize_entry("fallback", fallback)?;
1586            }
1587        }
1588    }
1589    Ok(())
1590}
1591
1592fn serialize_style<M, V>(map: &mut M, style: &Style<V>) -> Result<(), M::Error>
1593where
1594    M: SerializeMap,
1595    V: Serialize,
1596{
1597    macro_rules! optional {
1598        ($field:ident) => {
1599            if let Some(value) = &style.$field {
1600                map.serialize_entry(stringify!($field), value)?;
1601            }
1602        };
1603    }
1604    optional!(color);
1605    optional!(font);
1606    optional!(bold);
1607    optional!(italic);
1608    optional!(underlined);
1609    optional!(strikethrough);
1610    optional!(obfuscated);
1611    optional!(shadow_color);
1612    optional!(insertion);
1613    optional!(click_event);
1614    optional!(hover_event);
1615    Ok(())
1616}
1617
1618#[derive(Deserialize)]
1619#[serde(bound(deserialize = "V: Deserialize<'de>"))]
1620struct RawComponent<V> {
1621    #[serde(rename = "type", default)]
1622    kind: Option<String>,
1623    #[serde(default)]
1624    text: Option<String>,
1625    #[serde(default)]
1626    translate: Option<String>,
1627    #[serde(default)]
1628    fallback: Option<String>,
1629    #[serde(default)]
1630    with: Vec<Component<V>>,
1631    #[serde(default)]
1632    score: Option<Score>,
1633    #[serde(default)]
1634    selector: Option<String>,
1635    #[serde(default)]
1636    separator: Option<Box<Component<V>>>,
1637    #[serde(default)]
1638    keybind: Option<String>,
1639    #[serde(default)]
1640    nbt: Option<String>,
1641    #[serde(default)]
1642    source: Option<NbtSource>,
1643    #[serde(default)]
1644    interpret: bool,
1645    #[serde(default)]
1646    plain: bool,
1647    #[serde(default)]
1648    entity: Option<String>,
1649    #[serde(default)]
1650    block: Option<String>,
1651    #[serde(default)]
1652    storage: Option<ResourceLocation>,
1653    #[serde(default)]
1654    object: Option<String>,
1655    #[serde(default)]
1656    atlas: Option<ResourceLocation>,
1657    #[serde(default)]
1658    sprite: Option<ResourceLocation>,
1659    #[serde(default)]
1660    player: Option<PlayerProfile>,
1661    #[serde(default)]
1662    hat: Option<bool>,
1663    #[serde(default)]
1664    extra: Vec<Component<V>>,
1665    #[serde(default)]
1666    color: Option<TextColor>,
1667    #[serde(default)]
1668    font: Option<ResourceLocation>,
1669    #[serde(default)]
1670    bold: Option<bool>,
1671    #[serde(default)]
1672    italic: Option<bool>,
1673    #[serde(default)]
1674    underlined: Option<bool>,
1675    #[serde(default)]
1676    strikethrough: Option<bool>,
1677    #[serde(default)]
1678    obfuscated: Option<bool>,
1679    #[serde(default)]
1680    shadow_color: Option<ShadowColor>,
1681    #[serde(default)]
1682    insertion: Option<String>,
1683    #[serde(default)]
1684    click_event: Option<ClickEvent<V>>,
1685    #[serde(default)]
1686    hover_event: Option<HoverEvent<V>>,
1687}
1688
1689#[derive(Debug, Clone, Copy, Deserialize)]
1690#[serde(rename_all = "snake_case")]
1691enum NbtSource {
1692    Entity,
1693    Block,
1694    Storage,
1695}
1696
1697impl<'de, V> Deserialize<'de> for ComponentObject<V>
1698where
1699    V: Deserialize<'de>,
1700{
1701    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1702    where
1703        D: Deserializer<'de>,
1704    {
1705        let raw = RawComponent::deserialize(deserializer)?;
1706        raw.try_into().map_err(D::Error::custom)
1707    }
1708}
1709
1710impl<V> TryFrom<RawComponent<V>> for ComponentObject<V> {
1711    type Error = InvalidComponentObject;
1712
1713    fn try_from(raw: RawComponent<V>) -> Result<Self, Self::Error> {
1714        let selected = select_content(&raw).ok_or(InvalidComponentObject::MissingContent)?;
1715        let content = match selected {
1716            SelectedContent::Text => Content::Text {
1717                text: raw.text.ok_or(InvalidComponentObject::MissingContent)?,
1718            },
1719            SelectedContent::Translatable => Content::Translatable {
1720                translate: raw
1721                    .translate
1722                    .ok_or(InvalidComponentObject::MissingContent)?,
1723                fallback: raw.fallback,
1724                with: raw.with,
1725            },
1726            SelectedContent::Score => Content::Score {
1727                score: raw.score.ok_or(InvalidComponentObject::MissingContent)?,
1728            },
1729            SelectedContent::Selector => Content::Selector {
1730                selector: raw.selector.ok_or(InvalidComponentObject::MissingContent)?,
1731                separator: raw.separator,
1732            },
1733            SelectedContent::Keybind => Content::Keybind {
1734                keybind: raw.keybind.ok_or(InvalidComponentObject::MissingContent)?,
1735            },
1736            SelectedContent::Nbt => {
1737                if raw.interpret && raw.plain {
1738                    return Err(InvalidComponentObject::ConflictingNbtDisplay);
1739                }
1740                let target =
1741                    select_nbt_target(&raw).ok_or(InvalidComponentObject::MissingNbtTarget)?;
1742                let display = if raw.interpret {
1743                    NbtDisplay::Interpret
1744                } else if raw.plain {
1745                    NbtDisplay::Plain
1746                } else {
1747                    NbtDisplay::Styled
1748                };
1749                Content::Nbt {
1750                    nbt: raw.nbt.ok_or(InvalidComponentObject::MissingContent)?,
1751                    target,
1752                    display,
1753                    separator: raw.separator,
1754                }
1755            }
1756            SelectedContent::Object => {
1757                let object = match raw.object.as_deref() {
1758                    Some("player") => ObjectContent::Player {
1759                        player: raw
1760                            .player
1761                            .ok_or(InvalidComponentObject::MissingObjectField)?,
1762                        hat: raw.hat,
1763                    },
1764                    None | Some("atlas") => ObjectContent::Atlas {
1765                        atlas: raw.atlas,
1766                        sprite: raw
1767                            .sprite
1768                            .ok_or(InvalidComponentObject::MissingObjectField)?,
1769                    },
1770                    Some(_) => return Err(InvalidComponentObject::InvalidObjectType),
1771                };
1772                Content::Object {
1773                    object,
1774                    fallback: raw.fallback,
1775                }
1776            }
1777        };
1778        Ok(Self {
1779            content,
1780            style: Style {
1781                color: raw.color,
1782                font: raw.font,
1783                bold: raw.bold,
1784                italic: raw.italic,
1785                underlined: raw.underlined,
1786                strikethrough: raw.strikethrough,
1787                obfuscated: raw.obfuscated,
1788                shadow_color: raw.shadow_color,
1789                insertion: raw.insertion,
1790                click_event: raw.click_event,
1791                hover_event: raw.hover_event,
1792            },
1793            extra: raw.extra,
1794        })
1795    }
1796}
1797
1798#[derive(Debug, Clone, Copy)]
1799enum SelectedContent {
1800    Text,
1801    Translatable,
1802    Score,
1803    Selector,
1804    Keybind,
1805    Nbt,
1806    Object,
1807}
1808
1809fn select_content<V>(raw: &RawComponent<V>) -> Option<SelectedContent> {
1810    let explicit = match raw.kind.as_deref() {
1811        Some("text") if raw.text.is_some() => Some(SelectedContent::Text),
1812        Some("translatable") if raw.translate.is_some() => Some(SelectedContent::Translatable),
1813        Some("score") if raw.score.is_some() => Some(SelectedContent::Score),
1814        Some("selector") if raw.selector.is_some() => Some(SelectedContent::Selector),
1815        Some("keybind") if raw.keybind.is_some() => Some(SelectedContent::Keybind),
1816        Some("nbt") if raw.nbt.is_some() && select_nbt_target(raw).is_some() => {
1817            Some(SelectedContent::Nbt)
1818        }
1819        Some("object") if object_fields_are_valid(raw) => Some(SelectedContent::Object),
1820        _ => None,
1821    };
1822    explicit.or_else(|| {
1823        if raw.text.is_some() {
1824            Some(SelectedContent::Text)
1825        } else if raw.translate.is_some() {
1826            Some(SelectedContent::Translatable)
1827        } else if raw.score.is_some() {
1828            Some(SelectedContent::Score)
1829        } else if raw.selector.is_some() {
1830            Some(SelectedContent::Selector)
1831        } else if raw.keybind.is_some() {
1832            Some(SelectedContent::Keybind)
1833        } else if raw.nbt.is_some() && select_nbt_target(raw).is_some() {
1834            Some(SelectedContent::Nbt)
1835        } else if object_fields_are_valid(raw) {
1836            Some(SelectedContent::Object)
1837        } else {
1838            None
1839        }
1840    })
1841}
1842
1843fn object_fields_are_valid<V>(raw: &RawComponent<V>) -> bool {
1844    match raw.object.as_deref() {
1845        Some("player") => raw.player.is_some(),
1846        None | Some("atlas") => raw.sprite.is_some(),
1847        Some(_) => false,
1848    }
1849}
1850
1851fn select_nbt_target<V>(raw: &RawComponent<V>) -> Option<NbtTarget> {
1852    match raw.source {
1853        Some(NbtSource::Entity) => raw.entity.clone().map(NbtTarget::Entity),
1854        Some(NbtSource::Block) => raw.block.clone().map(NbtTarget::Block),
1855        Some(NbtSource::Storage) => raw.storage.clone().map(NbtTarget::Storage),
1856        None => raw
1857            .entity
1858            .clone()
1859            .map(NbtTarget::Entity)
1860            .or_else(|| raw.block.clone().map(NbtTarget::Block))
1861            .or_else(|| raw.storage.clone().map(NbtTarget::Storage)),
1862    }
1863}
1864
1865/// Describes why a serialized component object does not match the component
1866/// schema.
1867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1868pub enum InvalidComponentObject {
1869    /// No recognized content field is present.
1870    MissingContent,
1871    /// NBT content does not identify an entity, block, or storage source.
1872    MissingNbtTarget,
1873    /// NBT content requests both plain and interpreted display modes.
1874    ConflictingNbtDisplay,
1875    /// Object content is missing a field required by its object type.
1876    MissingObjectField,
1877    /// The object type is not recognized.
1878    InvalidObjectType,
1879}
1880
1881impl fmt::Display for InvalidComponentObject {
1882    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1883        formatter.write_str(match self {
1884            Self::MissingContent => "text component object has no valid content",
1885            Self::MissingNbtTarget => "NBT text component has no matching source",
1886            Self::ConflictingNbtDisplay => "NBT text component cannot be plain and interpreted",
1887            Self::MissingObjectField => "object text component is missing a required field",
1888            Self::InvalidObjectType => "unknown object text component type",
1889        })
1890    }
1891}
1892
1893impl std::error::Error for InvalidComponentObject {}
1894
1895impl Component<NbtValue> {
1896    pub(crate) fn normalized_root_for_nbt(&self) -> Self {
1897        let component = normalize_nbt(self.clone());
1898        match component {
1899            Component::Sequence(_) => Component::Object(Box::new(component_into_object(component))),
1900            Component::Object(object) => {
1901                let ComponentObject {
1902                    content,
1903                    style,
1904                    extra,
1905                } = *object;
1906                match content {
1907                    Content::Text { text } if style.is_empty() && extra.is_empty() => {
1908                        Component::Text(text)
1909                    }
1910                    content => Component::Object(Box::new(ComponentObject {
1911                        content,
1912                        style,
1913                        extra,
1914                    })),
1915                }
1916            }
1917            _ => component,
1918        }
1919    }
1920}
1921
1922fn normalize_nbt(component: NbtComponent) -> NbtComponent {
1923    match component {
1924        Component::Text(_) => component,
1925        Component::Sequence(sequence) => {
1926            let ComponentSequence { first, rest } = sequence;
1927            let first = Component::Object(Box::new(component_into_object(normalize_nbt(*first))));
1928            let rest = rest
1929                .into_iter()
1930                .map(normalize_nbt)
1931                .map(component_into_object)
1932                .map(Box::new)
1933                .map(Component::Object);
1934            Component::Sequence(ComponentSequence::new(first, rest))
1935        }
1936        Component::Object(mut object) => {
1937            object.extra = object
1938                .extra
1939                .into_iter()
1940                .map(normalize_nbt)
1941                .map(component_into_object)
1942                .map(Box::new)
1943                .map(Component::Object)
1944                .collect();
1945            match &mut object.content {
1946                Content::Translatable { with, .. } => {
1947                    *with = std::mem::take(with)
1948                        .into_iter()
1949                        .map(normalize_nbt)
1950                        .map(component_into_object)
1951                        .map(Box::new)
1952                        .map(Component::Object)
1953                        .collect();
1954                }
1955                Content::Selector { separator, .. } | Content::Nbt { separator, .. } => {
1956                    if let Some(value) = separator.take() {
1957                        *separator = Some(Box::new(normalize_nbt(*value)));
1958                    }
1959                }
1960                _ => {}
1961            }
1962            if let Some(hover) = &mut object.style.hover_event {
1963                match hover {
1964                    HoverEvent::ShowText { value } => {
1965                        **value = normalize_nbt(std::mem::take(value.as_mut()));
1966                    }
1967                    HoverEvent::ShowEntity { name, .. } => {
1968                        if let Some(value) = name.take() {
1969                            *name = Some(Box::new(normalize_nbt(*value)));
1970                        }
1971                    }
1972                    HoverEvent::ShowItem { .. } => {}
1973                }
1974            }
1975            Component::Object(object)
1976        }
1977    }
1978}
1979
1980fn component_into_object(component: NbtComponent) -> ComponentObject<NbtValue> {
1981    match component {
1982        Component::Text(text) => ComponentObject::text(text),
1983        Component::Object(object) => *object,
1984        Component::Sequence(sequence) => {
1985            let ComponentSequence { first, rest } = sequence;
1986            let mut first = component_into_object(*first);
1987            first.extra.extend(
1988                rest.into_iter()
1989                    .map(component_into_object)
1990                    .map(Box::new)
1991                    .map(Component::Object),
1992            );
1993            first
1994        }
1995    }
1996}