1use crate::{
4 Identifier, IdentifierError, event::EventPayloadDescriptor, interval::IntervalPeriod,
5 report::ReportPayloadDescriptor, target::Target, values_map::ValuesMap,
6};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use serde_with::{DefaultOnNull, serde_as, skip_serializing_none};
10use std::{fmt::Display, str::FromStr};
11use validator::Validate;
12
13pub type Programs = Vec<Program>;
14
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
17#[serde(rename_all = "camelCase")]
18pub struct Program {
19 pub id: ProgramId,
23
24 #[serde(with = "crate::serde_rfc3339")]
28 pub created_date_time: DateTime<Utc>,
29
30 #[serde(with = "crate::serde_rfc3339")]
34 pub modification_date_time: DateTime<Utc>,
35
36 #[serde(flatten)]
37 #[validate(nested)]
38 pub content: ProgramRequest,
39}
40
41#[skip_serializing_none]
42#[serde_as]
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
44#[serde(rename_all = "camelCase", tag = "objectType", rename = "PROGRAM")]
45pub struct ProgramRequest {
46 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
48 pub program_name: String,
49 pub interval_period: Option<IntervalPeriod>,
51 #[validate(nested)]
53 pub program_descriptions: Option<Vec<ProgramDescription>>,
54 pub payload_descriptors: Option<Vec<PayloadDescriptor>>,
56 pub attributes: Option<Vec<ValuesMap>>,
57 #[serde(default)]
59 #[serde_as(deserialize_as = "DefaultOnNull")]
60 pub targets: Vec<Target>,
61}
62
63impl ProgramRequest {
64 pub fn new(name: impl ToString) -> ProgramRequest {
65 ProgramRequest {
66 program_name: name.to_string(),
67 interval_period: Default::default(),
68 program_descriptions: Default::default(),
69 payload_descriptors: Default::default(),
70 attributes: Default::default(),
71 targets: Default::default(),
72 }
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
78pub struct ProgramId(pub(crate) Identifier);
79
80impl Display for ProgramId {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 write!(f, "{}", self.0)
83 }
84}
85
86impl ProgramId {
87 pub fn as_str(&self) -> &str {
88 self.0.as_str()
89 }
90
91 pub fn new(identifier: &str) -> Option<Self> {
92 Some(Self(identifier.parse().ok()?))
93 }
94}
95
96impl FromStr for ProgramId {
97 type Err = IdentifierError;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Ok(Self(s.parse()?))
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, Validate)]
105pub struct ProgramDescription {
106 #[serde(rename = "URL")]
108 #[validate(url)]
109 pub url: String,
110}
111
112#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
113#[serde(tag = "objectType", rename_all = "SCREAMING_SNAKE_CASE")]
114pub enum PayloadDescriptor {
115 EventPayloadDescriptor(EventPayloadDescriptor),
116 ReportPayloadDescriptor(ReportPayloadDescriptor),
117}
118
119#[cfg(test)]
120mod test {
121 use crate::Duration;
122
123 use super::*;
124
125 #[test]
126 fn example_parses() {
127 let example = r#"[
128 {
129 "id": "object-999",
130 "createdDateTime": "2023-06-15T09:30:00Z",
131 "modificationDateTime": "2023-06-15T09:30:00Z",
132 "objectType": "PROGRAM",
133 "programName": "ResTOU",
134 "intervalPeriod": {
135 "start": "2023-06-15T09:30:00Z",
136 "duration": "PT1H",
137 "randomizeStart": "PT1H"
138 },
139 "programDescriptions": null,
140 "payloadDescriptors": null,
141 "attributes": null,
142 "targets": null
143 }
144 ]"#;
145
146 let parsed = serde_json::from_str::<Programs>(example).unwrap();
147
148 let expected = vec![Program {
149 id: ProgramId("object-999".parse().unwrap()),
150 created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
151 modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
152 content: ProgramRequest {
153 program_name: "ResTOU".into(),
154 interval_period: Some(IntervalPeriod {
155 start: "2023-06-15T09:30:00Z".parse().unwrap(),
156 duration: Some(Duration::PT1H),
157 randomize_start: Some(Duration::PT1H),
158 }),
159 program_descriptions: None,
160 payload_descriptors: None,
161 attributes: None,
162 targets: vec![],
163 },
164 }];
165
166 assert_eq!(expected, parsed);
167 }
168
169 #[test]
170 fn parses_minimal() {
171 let example = r#"{"programName":"test"}"#;
172
173 assert_eq!(
174 serde_json::from_str::<ProgramRequest>(example).unwrap(),
175 ProgramRequest {
176 program_name: "test".to_string(),
177 interval_period: None,
178 program_descriptions: None,
179 payload_descriptors: None,
180 attributes: None,
181 targets: vec![],
182 }
183 );
184 }
185}