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 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 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}