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