1use std::{collections::HashMap, fmt::Display};
2
3use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
4use tauri::plugin::PermissionState;
5
6use url::Url;
7
8#[derive(Debug, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct PermissionResponse {
11 pub permission_state: PermissionState,
12}
13
14#[cfg(feature = "push-notifications")]
15#[derive(Debug, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct PushNotificationResponse {
18 pub device_token: String,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct Attachment {
24 id: String,
25 url: Url,
26}
27
28impl Attachment {
29 pub fn new(id: impl Into<String>, url: Url) -> Self {
30 Self { id: id.into(), url }
31 }
32}
33
34#[derive(Debug, Default, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct ScheduleInterval {
37 pub year: Option<u8>,
38 pub month: Option<u8>,
39 pub day: Option<u8>,
40 pub weekday: Option<u8>,
41 pub hour: Option<u8>,
42 pub minute: Option<u8>,
43 pub second: Option<u8>,
44}
45
46#[derive(Debug)]
47pub enum ScheduleEvery {
48 Year,
49 Month,
50 TwoWeeks,
51 Week,
52 Day,
53 Hour,
54 Minute,
55 Second,
56}
57
58impl Display for ScheduleEvery {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(
61 f,
62 "{}",
63 match self {
64 Self::Year => "year",
65 Self::Month => "month",
66 Self::TwoWeeks => "twoWeeks",
67 Self::Week => "week",
68 Self::Day => "day",
69 Self::Hour => "hour",
70 Self::Minute => "minute",
71 Self::Second => "second",
72 }
73 )
74 }
75}
76
77impl Serialize for ScheduleEvery {
78 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
79 where
80 S: Serializer,
81 {
82 serializer.serialize_str(self.to_string().as_ref())
83 }
84}
85
86impl<'de> Deserialize<'de> for ScheduleEvery {
87 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 let s = String::deserialize(deserializer)?;
92 match s.to_lowercase().as_str() {
93 "year" => Ok(Self::Year),
94 "month" => Ok(Self::Month),
95 "twoweeks" => Ok(Self::TwoWeeks),
96 "week" => Ok(Self::Week),
97 "day" => Ok(Self::Day),
98 "hour" => Ok(Self::Hour),
99 "minute" => Ok(Self::Minute),
100 "second" => Ok(Self::Second),
101 _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
102 }
103 }
104}
105
106#[derive(Debug, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub enum Schedule {
109 #[serde(rename_all = "camelCase")]
110 At {
111 #[serde(
112 serialize_with = "iso8601::serialize",
113 deserialize_with = "time::serde::iso8601::deserialize"
114 )]
115 date: time::OffsetDateTime,
116 #[serde(default)]
117 repeating: bool,
118 #[serde(default)]
119 allow_while_idle: bool,
120 },
121 #[serde(rename_all = "camelCase")]
122 Interval {
123 interval: ScheduleInterval,
124 #[serde(default)]
125 allow_while_idle: bool,
126 },
127 #[serde(rename_all = "camelCase")]
128 Every {
129 interval: ScheduleEvery,
130 count: u8,
131 #[serde(default)]
132 allow_while_idle: bool,
133 },
134}
135
136mod iso8601 {
138 use serde::{ser::Error as _, Serialize, Serializer};
139 use time::{
140 format_description::well_known::iso8601::{Config, EncodedConfig},
141 format_description::well_known::Iso8601,
142 OffsetDateTime,
143 };
144
145 const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
146
147 pub fn serialize<S: Serializer>(
148 datetime: &OffsetDateTime,
149 serializer: S,
150 ) -> Result<S::Ok, S::Error> {
151 datetime
152 .format(&Iso8601::<SERDE_CONFIG>)
153 .map_err(S::Error::custom)?
154 .serialize(serializer)
155 }
156}
157
158#[derive(Debug, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct NotificationData {
161 #[serde(default = "default_id")]
162 pub(crate) id: i32,
163 pub(crate) channel_id: Option<String>,
164 pub(crate) title: Option<String>,
165 pub(crate) body: Option<String>,
166 pub(crate) schedule: Option<Schedule>,
167 pub(crate) large_body: Option<String>,
168 pub(crate) summary: Option<String>,
169 pub(crate) action_type_id: Option<String>,
170 pub(crate) group: Option<String>,
171 #[serde(default)]
172 pub(crate) group_summary: bool,
173 pub(crate) sound: Option<String>,
174 #[serde(default)]
175 pub(crate) inbox_lines: Vec<String>,
176 pub(crate) icon: Option<String>,
177 pub(crate) large_icon: Option<String>,
178 pub(crate) icon_color: Option<String>,
179 #[serde(default)]
180 pub(crate) attachments: Vec<Attachment>,
181 #[serde(default)]
182 pub(crate) extra: HashMap<String, serde_json::Value>,
183 #[serde(default)]
184 pub(crate) ongoing: bool,
185 #[serde(default)]
186 pub(crate) auto_cancel: bool,
187 #[serde(default)]
188 pub(crate) silent: bool,
189}
190
191fn default_id() -> i32 {
192 rand::random()
193}
194
195impl Default for NotificationData {
196 fn default() -> Self {
197 Self {
198 id: default_id(),
199 channel_id: None,
200 title: None,
201 body: None,
202 schedule: None,
203 large_body: None,
204 summary: None,
205 action_type_id: None,
206 group: None,
207 group_summary: false,
208 sound: None,
209 inbox_lines: Vec::new(),
210 icon: None,
211 large_icon: None,
212 icon_color: None,
213 attachments: Vec::new(),
214 extra: Default::default(),
215 ongoing: false,
216 auto_cancel: false,
217 silent: false,
218 }
219 }
220}
221
222#[derive(Debug, Deserialize, Serialize)]
223#[serde(rename_all = "camelCase")]
224pub struct PendingNotification {
225 id: i32,
226 title: Option<String>,
227 body: Option<String>,
228 schedule: Schedule,
229}
230
231impl PendingNotification {
232 pub fn id(&self) -> i32 {
233 self.id
234 }
235
236 pub fn title(&self) -> Option<&str> {
237 self.title.as_deref()
238 }
239
240 pub fn body(&self) -> Option<&str> {
241 self.body.as_deref()
242 }
243
244 pub fn schedule(&self) -> &Schedule {
245 &self.schedule
246 }
247}
248
249#[derive(Debug, Deserialize, Serialize)]
250#[serde(rename_all = "camelCase")]
251pub struct ActiveNotification {
252 id: i32,
253 tag: Option<String>,
254 title: Option<String>,
255 body: Option<String>,
256 group: Option<String>,
257 #[serde(default)]
258 group_summary: bool,
259 #[serde(default)]
260 data: HashMap<String, String>,
261 #[serde(default)]
262 extra: HashMap<String, serde_json::Value>,
263 #[serde(default)]
264 attachments: Vec<Attachment>,
265 action_type_id: Option<String>,
266 schedule: Option<Schedule>,
267 sound: Option<String>,
268}
269
270impl ActiveNotification {
271 pub fn id(&self) -> i32 {
272 self.id
273 }
274
275 pub fn tag(&self) -> Option<&str> {
276 self.tag.as_deref()
277 }
278
279 pub fn title(&self) -> Option<&str> {
280 self.title.as_deref()
281 }
282
283 pub fn body(&self) -> Option<&str> {
284 self.body.as_deref()
285 }
286
287 pub fn group(&self) -> Option<&str> {
288 self.group.as_deref()
289 }
290
291 pub fn group_summary(&self) -> bool {
292 self.group_summary
293 }
294
295 pub fn data(&self) -> &HashMap<String, String> {
296 &self.data
297 }
298
299 pub fn extra(&self) -> &HashMap<String, serde_json::Value> {
300 &self.extra
301 }
302
303 pub fn attachments(&self) -> &[Attachment] {
304 &self.attachments
305 }
306
307 pub fn action_type_id(&self) -> Option<&str> {
308 self.action_type_id.as_deref()
309 }
310
311 pub fn schedule(&self) -> Option<&Schedule> {
312 self.schedule.as_ref()
313 }
314
315 pub fn sound(&self) -> Option<&str> {
316 self.sound.as_deref()
317 }
318}
319
320#[derive(Debug, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct ActionType {
323 id: String,
324 actions: Vec<Action>,
325 hidden_previews_body_placeholder: Option<String>,
326 #[serde(default)]
327 custom_dismiss_action: bool,
328 #[serde(default)]
329 allow_in_car_play: bool,
330 #[serde(default)]
331 hidden_previews_show_title: bool,
332 #[serde(default)]
333 hidden_previews_show_subtitle: bool,
334}
335
336#[derive(Debug, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct Action {
339 id: String,
340 title: String,
341 #[serde(default)]
342 requires_authentication: bool,
343 #[serde(default)]
344 foreground: bool,
345 #[serde(default)]
346 destructive: bool,
347 #[serde(default)]
348 input: bool,
349 input_button_title: Option<String>,
350 input_placeholder: Option<String>,
351}
352
353pub use android::*;
354
355mod android {
356 use serde::{Deserialize, Serialize};
357 use serde_repr::{Deserialize_repr, Serialize_repr};
358
359 #[derive(Debug, Default, Clone, Copy, Serialize_repr, Deserialize_repr)]
360 #[repr(u8)]
361 pub enum Importance {
362 None = 0,
363 Min = 1,
364 Low = 2,
365 #[default]
366 Default = 3,
367 High = 4,
368 }
369
370 #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
371 #[repr(i8)]
372 pub enum Visibility {
373 Secret = -1,
374 Private = 0,
375 Public = 1,
376 }
377
378 #[derive(Debug, Serialize, Deserialize)]
379 #[serde(rename_all = "camelCase")]
380 pub struct Channel {
381 id: String,
382 name: String,
383 description: Option<String>,
384 sound: Option<String>,
385 lights: Option<bool>,
386 light_color: Option<String>,
387 vibration: Option<bool>,
388 importance: Option<Importance>,
389 visibility: Option<Visibility>,
390 }
391
392 #[derive(Debug)]
393 pub struct ChannelBuilder(Channel);
394
395 impl Channel {
396 pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
397 ChannelBuilder(Self {
398 id: id.into(),
399 name: name.into(),
400 description: None,
401 sound: None,
402 lights: Some(false),
403 light_color: None,
404 vibration: Some(false),
405 importance: Default::default(),
406 visibility: None,
407 })
408 }
409
410 pub fn id(&self) -> &str {
411 &self.id
412 }
413
414 pub fn name(&self) -> &str {
415 &self.name
416 }
417
418 pub fn description(&self) -> Option<&str> {
419 self.description.as_deref()
420 }
421
422 pub fn sound(&self) -> Option<&str> {
423 self.sound.as_deref()
424 }
425
426 pub fn lights(&self) -> bool {
427 self.lights.unwrap_or(false)
428 }
429
430 pub fn light_color(&self) -> Option<&str> {
431 self.light_color.as_deref()
432 }
433
434 pub fn vibration(&self) -> bool {
435 self.vibration.unwrap_or(false)
436 }
437
438 pub fn importance(&self) -> Importance {
439 self.importance.unwrap_or_default()
440 }
441
442 pub fn visibility(&self) -> Option<Visibility> {
443 self.visibility
444 }
445 }
446
447 impl ChannelBuilder {
448 pub fn description(mut self, description: impl Into<String>) -> Self {
449 self.0.description.replace(description.into());
450 self
451 }
452
453 pub fn sound(mut self, sound: impl Into<String>) -> Self {
454 self.0.sound.replace(sound.into());
455 self
456 }
457
458 pub fn lights(mut self, lights: bool) -> Self {
459 self.0.lights = Some(lights);
460 self
461 }
462
463 pub fn light_color(mut self, color: impl Into<String>) -> Self {
464 self.0.light_color.replace(color.into());
465 self
466 }
467
468 pub fn vibration(mut self, vibration: bool) -> Self {
469 self.0.vibration = Some(vibration);
470 self
471 }
472
473 pub fn importance(mut self, importance: Importance) -> Self {
474 self.0.importance = Some(importance);
475 self
476 }
477
478 pub fn visibility(mut self, visibility: Visibility) -> Self {
479 self.0.visibility.replace(visibility);
480 self
481 }
482
483 pub fn build(self) -> Channel {
484 self.0
485 }
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn test_attachment_creation() {
495 let url = Url::parse("https://example.com/image.png").expect("Failed to parse URL");
496 let attachment = Attachment::new("test_id", url.clone());
497 assert_eq!(attachment.id, "test_id");
498 assert_eq!(attachment.url, url);
499 }
500
501 #[test]
502 fn test_attachment_serialization() {
503 let url = Url::parse("https://example.com/image.png").expect("Failed to parse URL");
504 let attachment = Attachment::new("test_id", url);
505 let json = serde_json::to_string(&attachment).expect("Failed to serialize attachment");
506 assert!(json.contains("test_id"));
507 assert!(json.contains("https://example.com/image.png"));
508 }
509
510 #[test]
511 fn test_attachment_deserialization() {
512 let json = r#"{"id":"test_id","url":"https://example.com/image.png"}"#;
513 let attachment: Attachment =
514 serde_json::from_str(json).expect("Failed to deserialize attachment");
515 assert_eq!(attachment.id, "test_id");
516 assert_eq!(attachment.url.as_str(), "https://example.com/image.png");
517 }
518
519 #[test]
520 fn test_schedule_every_display() {
521 assert_eq!(ScheduleEvery::Year.to_string(), "year");
522 assert_eq!(ScheduleEvery::Month.to_string(), "month");
523 assert_eq!(ScheduleEvery::TwoWeeks.to_string(), "twoWeeks");
524 assert_eq!(ScheduleEvery::Week.to_string(), "week");
525 assert_eq!(ScheduleEvery::Day.to_string(), "day");
526 assert_eq!(ScheduleEvery::Hour.to_string(), "hour");
527 assert_eq!(ScheduleEvery::Minute.to_string(), "minute");
528 assert_eq!(ScheduleEvery::Second.to_string(), "second");
529 }
530
531 #[test]
532 fn test_schedule_every_serialization() {
533 let json = serde_json::to_string(&ScheduleEvery::Day).expect("Failed to serialize Day");
534 assert_eq!(json, "\"day\"");
535
536 let json =
537 serde_json::to_string(&ScheduleEvery::TwoWeeks).expect("Failed to serialize TwoWeeks");
538 assert_eq!(json, "\"twoWeeks\"");
539 }
540
541 #[test]
542 fn test_schedule_every_deserialization() {
543 let every: ScheduleEvery =
544 serde_json::from_str("\"year\"").expect("Failed to deserialize year");
545 assert!(matches!(every, ScheduleEvery::Year));
546
547 let every: ScheduleEvery =
548 serde_json::from_str("\"month\"").expect("Failed to deserialize month");
549 assert!(matches!(every, ScheduleEvery::Month));
550
551 let every: ScheduleEvery =
552 serde_json::from_str("\"twoweeks\"").expect("Failed to deserialize twoweeks");
553 assert!(matches!(every, ScheduleEvery::TwoWeeks));
554
555 let every: ScheduleEvery =
556 serde_json::from_str("\"week\"").expect("Failed to deserialize week");
557 assert!(matches!(every, ScheduleEvery::Week));
558
559 let every: ScheduleEvery =
560 serde_json::from_str("\"day\"").expect("Failed to deserialize day");
561 assert!(matches!(every, ScheduleEvery::Day));
562
563 let every: ScheduleEvery =
564 serde_json::from_str("\"hour\"").expect("Failed to deserialize hour");
565 assert!(matches!(every, ScheduleEvery::Hour));
566
567 let every: ScheduleEvery =
568 serde_json::from_str("\"minute\"").expect("Failed to deserialize minute");
569 assert!(matches!(every, ScheduleEvery::Minute));
570
571 let every: ScheduleEvery =
572 serde_json::from_str("\"second\"").expect("Failed to deserialize second");
573 assert!(matches!(every, ScheduleEvery::Second));
574 }
575
576 #[test]
577 fn test_schedule_every_deserialization_invalid() {
578 let result: Result<ScheduleEvery, _> = serde_json::from_str("\"invalid\"");
579 assert!(result.is_err());
580 }
581
582 #[test]
583 fn test_schedule_interval_default() {
584 let interval = ScheduleInterval::default();
585 assert!(interval.year.is_none());
586 assert!(interval.month.is_none());
587 assert!(interval.day.is_none());
588 assert!(interval.weekday.is_none());
589 assert!(interval.hour.is_none());
590 assert!(interval.minute.is_none());
591 assert!(interval.second.is_none());
592 }
593
594 #[test]
595 fn test_schedule_interval_serialization() {
596 let interval = ScheduleInterval {
597 year: Some(24),
598 month: Some(12),
599 day: Some(25),
600 weekday: Some(1),
601 hour: Some(10),
602 minute: Some(30),
603 second: Some(0),
604 };
605 let json = serde_json::to_string(&interval).expect("Failed to serialize interval");
606 assert!(json.contains("\"year\":24"));
607 assert!(json.contains("\"month\":12"));
608 assert!(json.contains("\"day\":25"));
609 }
610
611 #[test]
612 fn test_notification_data_default() {
613 let data = NotificationData::default();
614 assert!(data.id != 0); assert!(data.channel_id.is_none());
616 assert!(data.title.is_none());
617 assert!(data.body.is_none());
618 assert!(data.schedule.is_none());
619 assert!(!data.group_summary);
620 assert!(!data.ongoing);
621 assert!(!data.auto_cancel);
622 assert!(!data.silent);
623 assert!(data.inbox_lines.is_empty());
624 assert!(data.attachments.is_empty());
625 assert!(data.extra.is_empty());
626 }
627
628 #[test]
629 fn test_notification_data_serialization() {
630 let data = NotificationData {
631 id: 123,
632 title: Some("Test Title".to_string()),
633 body: Some("Test Body".to_string()),
634 ongoing: true,
635 ..Default::default()
636 };
637
638 let json = serde_json::to_string(&data).expect("Failed to serialize notification data");
639 assert!(json.contains("\"id\":123"));
640 assert!(json.contains("\"title\":\"Test Title\""));
641 assert!(json.contains("\"body\":\"Test Body\""));
642 assert!(json.contains("\"ongoing\":true"));
643 }
644
645 #[test]
646 fn test_pending_notification_getters() {
647 let json = r#"{
648 "id": 456,
649 "title": "Pending Title",
650 "body": "Pending Body",
651 "schedule": {"every": {"interval": "day", "count": 1}}
652 }"#;
653 let pending: PendingNotification =
654 serde_json::from_str(json).expect("Failed to deserialize pending notification");
655
656 assert_eq!(pending.id(), 456);
657 assert_eq!(pending.title(), Some("Pending Title"));
658 assert_eq!(pending.body(), Some("Pending Body"));
659 assert!(matches!(pending.schedule(), Schedule::Every { .. }));
660 }
661
662 #[test]
663 fn test_active_notification_getters() {
664 let json = r#"{
665 "id": 789,
666 "title": "Active Title",
667 "body": "Active Body",
668 "group": "test_group",
669 "groupSummary": true
670 }"#;
671 let active: ActiveNotification =
672 serde_json::from_str(json).expect("Failed to deserialize active notification");
673
674 assert_eq!(active.id(), 789);
675 assert_eq!(active.title(), Some("Active Title"));
676 assert_eq!(active.body(), Some("Active Body"));
677 assert_eq!(active.group(), Some("test_group"));
678 assert!(active.group_summary());
679 assert!(active.data().is_empty());
680 assert!(active.extra().is_empty());
681 assert!(active.attachments().is_empty());
682 assert!(active.action_type_id().is_none());
683 assert!(active.schedule().is_none());
684 assert!(active.sound().is_none());
685 }
686
687 #[cfg(target_os = "android")]
688 #[test]
689 fn test_importance_default() {
690 let importance = Importance::default();
691 assert!(matches!(importance, Importance::Default));
692 }
693
694 #[cfg(target_os = "android")]
695 #[test]
696 fn test_importance_serialization() {
697 assert_eq!(
698 serde_json::to_string(&Importance::None).expect("Failed to serialize Importance::None"),
699 "0"
700 );
701 assert_eq!(
702 serde_json::to_string(&Importance::Min).expect("Failed to serialize Importance::Min"),
703 "1"
704 );
705 assert_eq!(
706 serde_json::to_string(&Importance::Low).expect("Failed to serialize Importance::Low"),
707 "2"
708 );
709 assert_eq!(
710 serde_json::to_string(&Importance::Default)
711 .expect("Failed to serialize Importance::Default"),
712 "3"
713 );
714 assert_eq!(
715 serde_json::to_string(&Importance::High).expect("Failed to serialize Importance::High"),
716 "4"
717 );
718 }
719
720 #[cfg(target_os = "android")]
721 #[test]
722 fn test_visibility_serialization() {
723 assert_eq!(
724 serde_json::to_string(&Visibility::Secret)
725 .expect("Failed to serialize Visibility::Secret"),
726 "-1"
727 );
728 assert_eq!(
729 serde_json::to_string(&Visibility::Private)
730 .expect("Failed to serialize Visibility::Private"),
731 "0"
732 );
733 assert_eq!(
734 serde_json::to_string(&Visibility::Public)
735 .expect("Failed to serialize Visibility::Public"),
736 "1"
737 );
738 }
739
740 #[cfg(target_os = "android")]
741 #[test]
742 fn test_channel_builder() {
743 let channel = Channel::builder("test_id", "Test Channel")
744 .description("Test Description")
745 .sound("test_sound")
746 .lights(true)
747 .light_color("#FF0000")
748 .vibration(true)
749 .importance(Importance::High)
750 .visibility(Visibility::Public)
751 .build();
752
753 assert_eq!(channel.id(), "test_id");
754 assert_eq!(channel.name(), "Test Channel");
755 assert_eq!(channel.description(), Some("Test Description"));
756 assert_eq!(channel.sound(), Some("test_sound"));
757 assert!(channel.lights());
758 assert_eq!(channel.light_color(), Some("#FF0000"));
759 assert!(channel.vibration());
760 assert!(matches!(channel.importance(), Importance::High));
761 assert_eq!(channel.visibility(), Some(Visibility::Public));
762 }
763
764 #[cfg(target_os = "android")]
765 #[test]
766 fn test_channel_builder_minimal() {
767 let channel = Channel::builder("minimal_id", "Minimal Channel").build();
768
769 assert_eq!(channel.id(), "minimal_id");
770 assert_eq!(channel.name(), "Minimal Channel");
771 assert_eq!(channel.description(), None);
772 assert_eq!(channel.sound(), None);
773 assert!(!channel.lights());
774 assert_eq!(channel.light_color(), None);
775 assert!(!channel.vibration());
776 assert!(matches!(channel.importance(), Importance::Default));
777 assert_eq!(channel.visibility(), None);
778 }
779
780 #[test]
781 fn test_schedule_at_serialization() {
782 use time::OffsetDateTime;
783
784 let date = OffsetDateTime::now_utc();
785 let schedule = Schedule::At {
786 date,
787 repeating: true,
788 allow_while_idle: false,
789 };
790
791 let json = serde_json::to_string(&schedule).expect("Failed to serialize Schedule::At");
792 assert!(json.contains("\"at\""));
793 assert!(json.contains("\"date\""));
794 assert!(json.contains("\"repeating\":true"));
795 assert!(json.contains("\"allowWhileIdle\":false"));
796 }
797
798 #[test]
799 fn test_schedule_interval_variant() {
800 let schedule = Schedule::Interval {
801 interval: ScheduleInterval {
802 hour: Some(10),
803 minute: Some(30),
804 ..Default::default()
805 },
806 allow_while_idle: true,
807 };
808
809 let json =
810 serde_json::to_string(&schedule).expect("Failed to serialize Schedule::Interval");
811 assert!(json.contains("\"interval\""));
812 assert!(json.contains("\"hour\":10"));
813 assert!(json.contains("\"minute\":30"));
814 assert!(json.contains("\"allowWhileIdle\":true"));
815 }
816
817 #[test]
818 fn test_schedule_every_variant() {
819 let schedule = Schedule::Every {
820 interval: ScheduleEvery::Day,
821 count: 5,
822 allow_while_idle: false,
823 };
824
825 let json = serde_json::to_string(&schedule).expect("Failed to serialize Schedule::Every");
826 assert!(json.contains("\"every\""));
827 assert!(json.contains("\"interval\":\"day\""));
828 assert!(json.contains("\"count\":5"));
829 }
830}