Skip to main content

mcproto_types/
component.rs

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