bird_chat/
component.rs

1use std::borrow::Cow;
2use crate::formatting::{Color};
3use crate::identifier::Identifier;
4use serde::{Serialize, Deserialize};
5use uuid::Uuid;
6
7#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
8#[serde(rename_all = "snake_case", tag = "action", content = "value")]
9pub enum ClickEvent<'a> {
10    OpenUrl(Cow<'a, str>),
11    RunCommand(Cow<'a, str>),
12    SuggestCommand(Cow<'a, str>),
13    ChangePage(usize),
14    CopyToClipboard(Cow<'a, str>),
15}
16
17#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
18#[serde(rename_all = "snake_case", tag = "action", content = "value")]
19pub enum HoverEvent<'a> {
20    ShowText(either::Either<Box<TextComponent<'a>>, Cow<'a, str>>),
21    ShowItem(Cow<'a, str>),
22    ShowEntity(Cow<'a, str>),
23}
24
25#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
26#[serde(untagged)]
27pub enum Component<'a> {
28    Text(TextComponent<'a>),
29    Translatable(TranslatableComponent<'a>),
30    KeyBind(KeyBindComponent<'a>),
31    Score(ScoreComponent<'a>),
32    Selector(SelectorComponent<'a>),
33    Base(BaseComponent<'a>),
34}
35
36#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
37#[serde(rename_all = "camelCase")]
38pub struct BaseComponent<'a> {
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub bold: Option<bool>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub italic: Option<bool>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub underlined: Option<bool>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub strikethrough: Option<bool>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub obfuscated: Option<bool>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub font: Option<Identifier<'a>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub color: Option<Color<'a>>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub insertion: Option<Cow<'a, str>>,
55    #[serde(skip_serializing_if = "is_cow_empty")]
56    pub extra: Cow<'a, [Component<'a>]>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub click_event: Option<ClickEvent<'a>>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub hover_event: Option<HoverEvent<'a>>,
61}
62
63#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
64#[serde(rename_all = "camelCase")]
65pub struct TextComponent<'a> {
66    pub text: Cow<'a, str>,
67    #[serde(flatten)]
68    pub base: BaseComponent<'a>,
69}
70
71#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
72#[serde(rename_all = "camelCase")]
73pub struct TranslatableComponent<'a> {
74    pub translate: Cow<'a, str>,
75    #[serde(skip_serializing_if = "is_cow_empty")]
76    pub with: Cow<'a, [Component<'a>]>,
77    #[serde(flatten)]
78    pub base: BaseComponent<'a>,
79}
80
81#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
82#[serde(rename_all = "camelCase")]
83pub struct KeyBindComponent<'a> {
84    #[serde(rename = "keybind")]
85    pub key_bind: Cow<'a, str>,
86    #[serde(flatten)]
87    pub base: BaseComponent<'a>,
88}
89
90#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
91#[serde(rename_all = "camelCase")]
92pub struct ScoreComponent<'a> {
93    pub score: Score<'a>,
94    #[serde(flatten)]
95    pub base: BaseComponent<'a>,
96}
97
98#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
99#[serde(rename_all = "camelCase")]
100pub struct Score<'a> {
101    pub name: either::Either<Cow<'a, str>, Uuid>,
102    pub objective: Cow<'a, str>,
103    #[serde(skip_serializing_if = "serde_json::Value::is_null")]
104    pub value: serde_json::Value,
105}
106
107#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
108#[serde(rename_all = "camelCase")]
109pub struct SelectorComponent<'a> {
110    pub selector: Cow<'a, str>,
111    #[serde(flatten)]
112    pub base: BaseComponent<'a>,
113}
114
115fn is_cow_empty<T: Clone>(value: &Cow<[T]>) -> bool {
116    match value {
117        Cow::Borrowed(ref data) => data.is_empty(),
118        Cow::Owned(ref vec) => vec.is_empty(),
119    }
120}
121
122fn make_owned<T: ToOwned + ?Sized>(cow: &mut Cow<T>) {
123    if let Cow::Borrowed(borrowed) = cow {
124        *cow = Cow::Owned(borrowed.to_owned())
125    }
126}
127
128fn add<T: ToOwned + Clone>(into: &mut Cow<[T]>, to_add: T) {
129    match into.is_empty() {
130        true => *into = Cow::Owned(vec![to_add]),
131        false => {
132            make_owned(into);
133            match into {
134                Cow::Owned(ref mut owned) => owned.push(to_add),
135                // Safety. guarantied by make_owned
136                _ => unsafe { std::hint::unreachable_unchecked() }
137            }
138        }
139    }
140}
141
142fn add_values<'a, T: ToOwned + Clone>(into: &mut Cow<'a, [T]>, to_add: Cow<'a, [T]>) {
143    match into.is_empty() {
144        true => *into = to_add,
145        false => {
146            make_owned(into);
147            match into {
148                Cow::Owned(ref mut owned) => {
149                    let mut to_add = to_add.into();
150                    make_owned(&mut to_add);
151                    match to_add {
152                        Cow::Owned(push) => for to_add in push {
153                            owned.push(to_add)
154                        },
155                        // Safety. guarantied by make_owned
156                        _ => unsafe { std::hint::unreachable_unchecked() }
157                    }
158                }
159                // Safety. guarantied by make_owned
160                _ => unsafe { std::hint::unreachable_unchecked() }
161            }
162        }
163    }
164}
165
166impl<'a> BaseComponent<'a> {
167    pub fn add_extra(&mut self, extra: impl Into<Component<'a>>) {
168        add(&mut self.extra, extra.into())
169    }
170
171    pub fn add_extras(&mut self, extras: impl Into<Cow<'a, [Component<'a>]>>) {
172        add_values(&mut self.extra, extras.into());
173    }
174}
175
176impl <'a> TranslatableComponent<'a> {
177    pub fn add_arg(&mut self, arg: impl Into<Component<'a>>) {
178        add(&mut self.with, arg.into())
179    }
180
181    pub fn add_args(&mut self, args: impl Into<Cow<'a, [Component<'a>]>>) {
182        add_values(&mut self.with, args.into());
183    }
184}
185
186impl<'a> From<TextComponent<'a>> for Component<'a> {
187    fn from(component: TextComponent<'a>) -> Self {
188        Self::Text(component)
189    }
190}
191
192impl<'a> From<TranslatableComponent<'a>> for Component<'a> {
193    fn from(component: TranslatableComponent<'a>) -> Self {
194        Self::Translatable(component)
195    }
196}
197
198impl<'a> From<ScoreComponent<'a>> for Component<'a> {
199    fn from(component: ScoreComponent<'a>) -> Self {
200        Self::Score(component)
201    }
202}
203
204impl<'a> From<SelectorComponent<'a>> for Component<'a> {
205    fn from(component: SelectorComponent<'a>) -> Self {
206        Self::Selector(component)
207    }
208}
209
210impl<'a> From<KeyBindComponent<'a>> for Component<'a> {
211    fn from(component: KeyBindComponent<'a>) -> Self {
212        Self::KeyBind(component)
213    }
214}
215
216impl<'a> From<BaseComponent<'a>> for Component<'a> {
217    fn from(component: BaseComponent<'a>) -> Self {
218        Self::Base(component)
219    }
220}