1use derive_builder::Builder;
2use hex_color::HexColor;
3use indexmap::IndexSet;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
14pub struct Data {
15 #[builder(setter(into))]
20 pub(crate) id: String,
21
22 #[serde(flatten)]
23 pub(crate) format: DataFormat,
24}
25
26impl Data {
27 pub fn builder() -> DataBuilder {
28 DataBuilder::default()
29 }
30}
31
32#[derive(Debug, Clone, Deserialize, Serialize)]
33#[serde(tag = "type")]
34#[serde(rename_all = "camelCase")]
35#[non_exhaustive]
36pub enum DataFormat {
37 Text(TextData),
39 Number(NumberData),
41 Switch(SwitchData),
43 Choice(ChoiceData),
45 File(FileData),
47 Folder(FolderData),
49 Color(ColorData),
53 LowerBound(BoundData),
70 UpperBound(BoundData),
88 }
90
91#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct TextData {
94 #[builder(setter(into), default)]
96 #[serde(rename = "default")]
97 #[doc(alias = "default")]
98 pub(crate) initial: String,
99}
100
101impl TextData {
102 pub fn builder() -> TextDataBuilder {
103 TextDataBuilder::default()
104 }
105}
106
107fn bool_is_true(b: &bool) -> bool {
108 *b
109}
110
111#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
112#[builder(build_fn(validate = "Self::validate"))]
113#[serde(rename_all = "camelCase")]
114pub struct NumberData {
115 #[serde(rename = "default")]
117 #[doc(alias = "default")]
118 pub(crate) initial: f64,
119
120 #[builder(default = true)]
124 #[serde(skip_serializing_if = "bool_is_true")]
125 pub(crate) allow_decimals: bool,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
132 #[builder(setter(into, strip_option), default)]
133 pub(crate) min_value: Option<f64>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
140 #[builder(setter(into, strip_option), default)]
141 pub(crate) max_value: Option<f64>,
142}
143
144impl NumberData {
145 pub fn builder() -> NumberDataBuilder {
146 NumberDataBuilder::default()
147 }
148}
149
150impl NumberDataBuilder {
151 fn validate(&self) -> Result<(), String> {
152 let initial = self.initial.expect("initial is required");
153 let min = self.min_value.flatten();
154 let max = self.max_value.flatten();
155
156 if let Some(min_val) = min
157 && initial < min_val
158 {
159 if let Some(max_val) = max {
160 return Err(format!(
161 "initial value {} is outside the allowed range [{}, {}]",
162 initial, min_val, max_val
163 ));
164 } else {
165 return Err(format!(
166 "initial value {} is below the minimum allowed value {}",
167 initial, min_val
168 ));
169 }
170 }
171
172 if let Some(max_val) = max
173 && initial > max_val
174 {
175 if let Some(min_val) = min {
176 return Err(format!(
177 "initial value {} is outside the allowed range [{}, {}]",
178 initial, min_val, max_val
179 ));
180 } else {
181 return Err(format!(
182 "initial value {} is above the maximum allowed value {}",
183 initial, max_val
184 ));
185 }
186 }
187
188 Ok(())
189 }
190}
191
192#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
193#[serde(rename_all = "camelCase")]
194pub struct SwitchData {
195 #[serde(rename = "default")]
197 #[doc(alias = "default")]
198 pub(crate) initial: bool,
199}
200
201impl SwitchData {
202 pub fn builder() -> SwitchDataBuilder {
203 SwitchDataBuilder::default()
204 }
205}
206
207#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct ChoiceData {
210 #[builder(setter(into))]
212 #[serde(rename = "default")]
213 #[doc(alias = "default")]
214 pub(crate) initial: String,
215
216 #[builder(setter(each(name = "choice", into)))]
217 pub(crate) value_choices: Vec<String>,
218}
219
220impl ChoiceData {
221 pub fn builder() -> ChoiceDataBuilder {
222 ChoiceDataBuilder::default()
223 }
224}
225
226#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
227#[serde(rename_all = "camelCase")]
228pub struct FileData {
229 #[builder(setter(into))]
231 #[serde(rename = "default")]
232 #[doc(alias = "default")]
233 pub(crate) initial: String,
234
235 #[builder(setter(each(name = "extension")), default)]
239 #[serde(skip_serializing_if = "IndexSet::is_empty")]
240 pub(crate) extensions: IndexSet<String>,
241}
242
243impl FileData {
244 pub fn builder() -> FileDataBuilder {
245 FileDataBuilder::default()
246 }
247}
248
249#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
250#[serde(rename_all = "camelCase")]
251pub struct FolderData {
252 #[builder(setter(into))]
254 #[serde(rename = "default")]
255 #[doc(alias = "default")]
256 pub(crate) initial: String,
257}
258
259impl FolderData {
260 pub fn builder() -> FolderDataBuilder {
261 FolderDataBuilder::default()
262 }
263}
264
265#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
266#[serde(rename_all = "camelCase")]
267pub struct ColorData {
268 #[builder(setter(into))]
270 #[serde(rename = "default")]
271 #[doc(alias = "default")]
272 pub(crate) initial: HexColor,
273}
274
275impl ColorData {
276 pub fn builder() -> ColorDataBuilder {
277 ColorDataBuilder::default()
278 }
279}
280
281#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
282#[serde(rename_all = "camelCase")]
283pub struct BoundData {
284 #[serde(rename = "default")]
286 #[doc(alias = "default")]
287 pub(crate) initial: i64,
288
289 #[serde(skip_serializing_if = "Option::is_none")]
294 #[builder(setter(into, strip_option), default)]
295 pub(crate) min_value: Option<i64>,
296
297 #[serde(skip_serializing_if = "Option::is_none")]
302 #[builder(setter(into, strip_option), default)]
303 pub(crate) max_value: Option<i64>,
304}
305
306impl BoundData {
307 pub fn builder() -> BoundDataBuilder {
308 BoundDataBuilder::default()
309 }
310}
311
312#[test]
313fn serialize_example_action_data_text() {
314 assert_eq!(
315 serde_json::to_value(
316 Data::builder()
317 .id("actiondata001")
318 .format(DataFormat::Text(
319 TextData::builder().initial("any text").build().unwrap()
320 ))
321 .build()
322 .unwrap()
323 )
324 .unwrap(),
325 serde_json::json! {{
326 "id":"actiondata001",
327 "type":"text",
328 "default":"any text"
329 }}
330 );
331}
332
333#[test]
334fn serialize_example_action_data_number() {
335 assert_eq!(
336 serde_json::to_value(
337 Data::builder()
338 .id("first")
339 .format(DataFormat::Number(
340 NumberData::builder()
341 .initial(200.)
342 .min_value(100.)
343 .max_value(350.)
344 .build()
345 .unwrap()
346 ))
347 .build()
348 .unwrap()
349 )
350 .unwrap(),
351 serde_json::json! { {
352 "id":"first",
353 "type":"number",
354 "default":200.0,
355 "minValue":100.0,
356 "maxValue":350.0,
357 }}
358 );
359}
360
361#[test]
362fn serialize_example_action_data_choice() {
363 assert_eq!(
364 serde_json::to_value(
365 Data::builder()
366 .id("second")
367 .format(DataFormat::Choice(
368 ChoiceData::builder()
369 .initial("200")
370 .choice("200")
371 .choice("400")
372 .choice("600")
373 .choice("800")
374 .build()
375 .unwrap()
376 ))
377 .build()
378 .unwrap()
379 )
380 .unwrap(),
381 serde_json::json! {{
382 "id":"second",
383 "type":"choice",
384 "default":"200",
385 "valueChoices": [
386 "200",
387 "400",
388 "600",
389 "800"
390 ]
391 }}
392 );
393}
394
395#[test]
396fn serialize_example_action_data_switch() {
397 assert_eq!(
398 serde_json::to_value(
399 Data::builder()
400 .id("actiondata003")
401 .format(DataFormat::Switch(
402 SwitchData::builder().initial(true).build().unwrap()
403 ))
404 .build()
405 .unwrap()
406 )
407 .unwrap(),
408 serde_json::json! {{
409 "id":"actiondata003",
410 "type":"switch",
411 "default":true
412 }}
413 );
414}