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