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