tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1// Copyright 2024 the Velato Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::schema::helpers::slottable_object::SlottableObject;
5
6use super::file_asset::FileAsset;
7use serde::de::Deserializer;
8use serde::{Deserialize, Serialize, Serializer};
9
10/// External image
11#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
12pub struct Image {
13 /// File asset properties
14 #[serde(flatten)]
15 pub file_asset: FileAsset,
16 /// Slottable object properties
17 #[serde(flatten)]
18 pub slottable_object: SlottableObject,
19 /// Width of the image
20 #[serde(rename = "w")]
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub width: Option<f64>,
23 /// Height of the image
24 #[serde(rename = "h")]
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub height: Option<f64>,
27 /// Mark as part of an image sequence if present.
28 #[serde(
29 rename = "t",
30 deserialize_with = "seq_from_str",
31 serialize_with = "seq_to_str",
32 default
33 )]
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub sequence: Option<bool>,
36}
37
38pub fn seq_from_str<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
39where
40 D: Deserializer<'de>,
41{
42 let v = String::deserialize(deserializer)?;
43 if v == *"seq" {
44 Ok(Some(true))
45 } else {
46 Ok(None)
47 }
48}
49
50// Function signature must match Serde's Serialize trait, so need to suppress Clippy.
51pub fn seq_to_str<S>(v: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
52where
53 S: Serializer,
54{
55 match v {
56 Some(true) => Serializer::serialize_str(serializer, "seq"),
57 Some(false) => Serializer::serialize_str(serializer, ""),
58 None => unimplemented!("serializer should skip if none"),
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::Image;
65 use crate::schema::{
66 assets::{asset::Asset, file_asset::FileAsset},
67 helpers::{
68 int_boolean::BoolInt, slottable_object::SlottableObject, visual_object::VisualObject,
69 },
70 };
71 use serde_json::json;
72 use std::sync::LazyLock;
73
74 static JSON: LazyLock<serde_json::Value> = LazyLock::new(|| {
75 json!(
76 {
77 "id": "my image",
78 "h": 512,
79 "w": 512,
80 "e": 1,
81 "p": "data:image/png;base64,..."
82 }
83 )
84 });
85 static IMAGE: LazyLock<Image> = LazyLock::new(|| Image {
86 slottable_object: SlottableObject::default(),
87 file_asset: FileAsset {
88 asset: Asset {
89 id: "my image".to_string(),
90 visual_object: VisualObject {
91 name: None,
92 match_name: None,
93 },
94 },
95 dir: None,
96 file_name: "data:image/png;base64,...".to_string(),
97 embedded: Some(BoolInt::True),
98 },
99 height: Some(512.0),
100 width: Some(512.0),
101 sequence: None,
102 });
103
104 #[test]
105 fn test_deserialize() {
106 let actual = serde_json::from_value(JSON.to_owned());
107
108 match actual {
109 Ok(actual) => assert_eq!(*IMAGE, actual),
110 Err(e) => panic!("{e}"),
111 }
112 }
113
114 #[test]
115 fn test_can_serialize() {
116 serde_json::to_value(&*IMAGE).unwrap();
117 }
118}