1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10pub type ComponentId = String;
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
24pub struct DataBinding {
25 pub path: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub struct FunctionCall {
32 pub call: String,
33 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
34 pub args: HashMap<String, serde_json::Value>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48#[serde(untagged)]
49pub enum DynamicString {
50 Literal(String),
52 Binding(DataBinding),
54 Function(FunctionCall),
56}
57
58impl DynamicString {
59 pub fn is_literal(&self) -> bool {
61 matches!(self, Self::Literal(_))
62 }
63
64 pub fn as_literal(&self) -> Option<&str> {
66 match self {
67 Self::Literal(s) => Some(s),
68 _ => None,
69 }
70 }
71}
72
73impl Default for DynamicString {
74 fn default() -> Self {
75 Self::Literal(String::new())
76 }
77}
78
79impl From<String> for DynamicString {
80 fn from(s: String) -> Self {
81 Self::Literal(s)
82 }
83}
84
85impl From<&str> for DynamicString {
86 fn from(s: &str) -> Self {
87 Self::Literal(s.to_string())
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93#[serde(untagged)]
94pub enum DynamicNumber {
95 Literal(f64),
96 Binding(DataBinding),
97 Function(FunctionCall),
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
102#[serde(untagged)]
103pub enum DynamicBoolean {
104 Literal(bool),
105 Binding(DataBinding),
106 Function(FunctionCall),
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
112#[serde(untagged)]
113pub enum DynamicBooleanCondition {
114 Literal(bool),
115 Binding(DataBinding),
116 Function(FunctionCall),
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121#[serde(untagged)]
122pub enum DynamicValue {
123 String(String),
124 Number(f64),
125 Boolean(bool),
126 Array(Vec<serde_json::Value>),
127 Binding(DataBinding),
128 Function(FunctionCall),
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
133#[serde(untagged)]
134pub enum DynamicStringList {
135 Literal(Vec<String>),
136 Binding(DataBinding),
137 Function(FunctionCall),
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
149#[serde(untagged)]
150pub enum ChildList {
151 Static(Vec<ComponentId>),
153 #[serde(rename_all = "camelCase")]
156 Template {
157 component_id: ComponentId,
158 path: String,
159 },
160}
161
162impl Default for ChildList {
163 fn default() -> Self {
164 Self::Static(Vec::new())
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
176#[serde(untagged)]
177pub enum Action {
178 Event { event: ActionEvent },
180 FunctionCall { function_call: FunctionCall },
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
186pub struct ActionEvent {
187 pub name: String,
188 #[serde(default)]
189 pub context: HashMap<String, DynamicValue>,
190 #[serde(default, skip_serializing_if = "is_false")]
191 pub want_response: bool,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub response_path: Option<String>,
194}
195
196fn is_false(v: &bool) -> bool {
197 !v
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
206pub struct CheckRule {
207 pub condition: DynamicBooleanCondition,
208 pub message: String,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
217pub struct AccessibilityAttributes {
218 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub label: Option<DynamicString>,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub description: Option<DynamicString>,
222}
223
224#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
230#[serde(rename_all = "camelCase")]
231pub enum Justify {
232 Start,
233 Center,
234 End,
235 SpaceBetween,
236 SpaceAround,
237 SpaceEvenly,
238 Stretch,
239}
240
241#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
243#[serde(rename_all = "camelCase")]
244pub enum Align {
245 Start,
246 Center,
247 End,
248 Stretch,
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn childlist_static_array_deserializes() {
257 let json = serde_json::json!(["a", "b", "c"]);
258 let cl: ChildList = serde_json::from_value(json).unwrap();
259 assert_eq!(cl, ChildList::Static(vec!["a".to_string(), "b".to_string(), "c".to_string()]));
260 }
261
262 #[test]
263 fn childlist_template_deserializes_camel_case_component_id() {
264 let json = serde_json::json!({ "path": "/restaurants", "componentId": "restaurant_card" });
268 let cl: ChildList = serde_json::from_value(json).unwrap();
269 match cl {
270 ChildList::Template { component_id, path } => {
271 assert_eq!(component_id, "restaurant_card");
272 assert_eq!(path, "/restaurants");
273 }
274 other => panic!("expected Template, got {other:?}"),
275 }
276 }
277}