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