use std::collections::HashMap;
use serde::{Deserialize, Serialize};
pub type ComponentId = String;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DataBinding {
pub path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FunctionCall {
pub call: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub args: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum DynamicString {
Literal(String),
Binding(DataBinding),
Function(FunctionCall),
}
impl DynamicString {
pub fn is_literal(&self) -> bool {
matches!(self, Self::Literal(_))
}
pub fn as_literal(&self) -> Option<&str> {
match self {
Self::Literal(s) => Some(s),
_ => None,
}
}
}
impl Default for DynamicString {
fn default() -> Self {
Self::Literal(String::new())
}
}
impl From<String> for DynamicString {
fn from(s: String) -> Self {
Self::Literal(s)
}
}
impl From<&str> for DynamicString {
fn from(s: &str) -> Self {
Self::Literal(s.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum DynamicNumber {
Literal(f64),
Binding(DataBinding),
Function(FunctionCall),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum DynamicBoolean {
Literal(bool),
Binding(DataBinding),
Function(FunctionCall),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum DynamicBooleanCondition {
Literal(bool),
Binding(DataBinding),
Function(FunctionCall),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum DynamicValue {
String(String),
Number(f64),
Boolean(bool),
Array(Vec<serde_json::Value>),
Binding(DataBinding),
Function(FunctionCall),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum DynamicStringList {
Literal(Vec<String>),
Binding(DataBinding),
Function(FunctionCall),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum ChildList {
Static(Vec<ComponentId>),
#[serde(rename_all = "camelCase")]
Template {
component_id: ComponentId,
path: String,
},
}
impl Default for ChildList {
fn default() -> Self {
Self::Static(Vec::new())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Action {
Event { event: ActionEvent },
FunctionCall { function_call: FunctionCall },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ActionEvent {
pub name: String,
#[serde(default)]
pub context: HashMap<String, DynamicValue>,
#[serde(default, skip_serializing_if = "is_false")]
pub want_response: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_path: Option<String>,
}
fn is_false(v: &bool) -> bool {
!v
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CheckRule {
pub condition: DynamicBooleanCondition,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AccessibilityAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<DynamicString>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<DynamicString>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum Justify {
Start,
Center,
End,
SpaceBetween,
SpaceAround,
SpaceEvenly,
Stretch,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum Align {
Start,
Center,
End,
Stretch,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn childlist_static_array_deserializes() {
let json = serde_json::json!(["a", "b", "c"]);
let cl: ChildList = serde_json::from_value(json).unwrap();
assert_eq!(cl, ChildList::Static(vec!["a".to_string(), "b".to_string(), "c".to_string()]));
}
#[test]
fn childlist_template_deserializes_camel_case_component_id() {
let json = serde_json::json!({ "path": "/restaurants", "componentId": "restaurant_card" });
let cl: ChildList = serde_json::from_value(json).unwrap();
match cl {
ChildList::Template { component_id, path } => {
assert_eq!(component_id, "restaurant_card");
assert_eq!(path, "/restaurants");
}
other => panic!("expected Template, got {other:?}"),
}
}
}