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