1use std::fmt::Display;
8
9pub use event::Event;
10pub use program::Program;
11pub use report::Report;
12use serde::{de::Unexpected, Deserialize, Deserializer, Serialize, Serializer};
13pub use ven::Ven;
14
15pub mod event;
16pub mod interval;
17pub mod oauth;
18pub mod problem;
19pub mod program;
20pub mod report;
21pub mod resource;
22pub mod target;
23pub mod values_map;
24pub mod ven;
25
26pub mod serde_rfc3339 {
27 use super::*;
28
29 use chrono::{DateTime, TimeZone, Utc};
30
31 pub fn serialize<S, Tz>(time: &DateTime<Tz>, serializer: S) -> Result<S::Ok, S::Error>
32 where
33 S: Serializer,
34 Tz: TimeZone,
35 {
36 serializer.serialize_str(&time.to_rfc3339())
37 }
38
39 pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
40 where
41 D: Deserializer<'de>,
42 {
43 let rfc_str = <String as Deserialize>::deserialize(deserializer)?;
44
45 match DateTime::parse_from_rfc3339(&rfc_str) {
46 Ok(datetime) => Ok(datetime.into()),
47 Err(_) => Err(serde::de::Error::invalid_value(
48 Unexpected::Str(&rfc_str),
49 &"Invalid RFC3339 string",
50 )),
51 }
52 }
53}
54
55pub fn string_within_range_inclusive<'de, const MIN: usize, const MAX: usize, D>(
56 deserializer: D,
57) -> Result<String, D::Error>
58where
59 D: Deserializer<'de>,
60{
61 let string = <String as Deserialize>::deserialize(deserializer)?;
62 let len = string.len();
63
64 if (MIN..=MAX).contains(&len) {
65 Ok(string.to_string())
66 } else {
67 Err(serde::de::Error::invalid_value(
68 Unexpected::Str(&string),
69 &IdentifierError::InvalidLength(len).to_string().as_str(),
70 ))
71 }
72}
73
74#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
76pub struct Identifier(#[serde(deserialize_with = "identifier")] String);
77
78impl<'de> Deserialize<'de> for Identifier {
79 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80 where
81 D: Deserializer<'de>,
82 {
83 let borrowed_str = <&str as Deserialize>::deserialize(deserializer)?;
84
85 borrowed_str.parse::<Identifier>().map_err(|e| {
86 serde::de::Error::invalid_value(Unexpected::Str(borrowed_str), &e.to_string().as_str())
87 })
88 }
89}
90
91#[derive(thiserror::Error, Debug)]
92pub enum IdentifierError {
93 #[error("string length {0} outside of allowed range 1..=128")]
94 InvalidLength(usize),
95 #[error("identifier contains characters besides [a-zA-Z0-9_-]")]
96 InvalidCharacter,
97 #[error("this identifier name is not allowed: {0}")]
98 ForbiddenName(String),
99}
100
101const FORBIDDEN_NAMES: &[&str] = &["null"];
102
103impl std::str::FromStr for Identifier {
104 type Err = IdentifierError;
105
106 fn from_str(s: &str) -> Result<Self, Self::Err> {
107 let is_valid_character = |b: u8| b.is_ascii_alphanumeric() || b == b'_' || b == b'-';
108
109 if !(1..=128).contains(&s.len()) {
110 Err(IdentifierError::InvalidLength(s.len()))
111 } else if !s.bytes().all(is_valid_character) {
112 Err(IdentifierError::InvalidCharacter)
113 } else if FORBIDDEN_NAMES.contains(&s.to_ascii_lowercase().as_str()) {
114 Err(IdentifierError::ForbiddenName(s.to_string()))
115 } else {
116 Ok(Identifier(s.to_string()))
117 }
118 }
119}
120
121impl Identifier {
122 pub fn as_str(&self) -> &str {
123 &self.0
124 }
125}
126
127impl Display for Identifier {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 write!(f, "{}", self.0)
130 }
131}
132
133#[derive(Clone, Debug, PartialEq)]
135pub struct Duration(iso8601_duration::Duration);
136
137impl<'de> Deserialize<'de> for Duration {
138 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139 where
140 D: Deserializer<'de>,
141 {
142 let raw = String::deserialize(deserializer)?;
143 let duration = raw
144 .parse::<iso8601_duration::Duration>()
145 .map_err(|_| "iso8601_duration::ParseDurationError")
146 .map_err(serde::de::Error::custom)?;
147
148 Ok(Self(duration))
149 }
150}
151
152impl Serialize for Duration {
153 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: Serializer,
156 {
157 self.to_string().serialize(serializer)
158 }
159}
160
161impl Duration {
162 pub fn to_chrono_at_datetime<Tz: chrono::TimeZone>(
168 &self,
169 at: chrono::DateTime<Tz>,
170 ) -> chrono::Duration {
171 self.0.to_chrono_at_datetime(at)
172 }
173
174 pub const PT1H: Self = Self(iso8601_duration::Duration {
176 year: 0.0,
177 month: 0.0,
178 day: 0.0,
179 hour: 1.0,
180 minute: 0.0,
181 second: 0.0,
182 });
183
184 pub const P999Y: Self = Self(iso8601_duration::Duration {
187 year: 9999.0,
188 month: 0.0,
189 day: 0.0,
190 hour: 0.0,
191 minute: 0.0,
192 second: 0.0,
193 });
194
195 pub const PT0S: Self = Self(iso8601_duration::Duration {
196 year: 0.0,
197 month: 0.0,
198 day: 0.0,
199 hour: 0.0,
200 minute: 0.0,
201 second: 0.0,
202 });
203
204 pub const fn hours(hour: f32) -> Self {
205 Self(iso8601_duration::Duration {
206 year: 0.0,
207 month: 0.0,
208 day: 0.0,
209 hour,
210 minute: 0.0,
211 second: 0.0,
212 })
213 }
214}
215
216impl std::str::FromStr for Duration {
217 type Err = iso8601_duration::ParseDurationError;
218
219 fn from_str(s: &str) -> Result<Self, Self::Err> {
220 let duration = s.parse::<iso8601_duration::Duration>()?;
221 Ok(Self(duration))
222 }
223}
224
225impl Display for Duration {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 let iso8601_duration::Duration {
228 year,
229 month,
230 day,
231 hour,
232 minute,
233 second,
234 } = self.0;
235
236 f.write_fmt(format_args!(
237 "P{year}Y{month}M{day}DT{hour}H{minute}M{second}S",
238 ))
239 }
240}
241
242#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
243#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
244pub enum OperatingState {
245 Normal,
246 Error,
247 IdleNormal,
248 RunningNormal,
249 RunningCurtailed,
250 RunningHeightened,
251 IdleCurtailed,
252 #[serde(rename = "SGD_ERROR_CONDITION")]
253 SGDErrorCondition,
254 IdleHeightened,
255 IdleOptedOut,
256 RunningOptedOut,
257 #[serde(untagged)]
258 Private(String),
259}
260
261#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
262#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
263pub enum DataQuality {
264 Ok,
266 Missing,
268 Estimated,
270 Bad,
272 #[serde(untagged)]
274 Private(String),
275}
276
277#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
278#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
279pub enum Attribute {
280 Location,
284 Area,
288 MaxPowerConsumption,
290 MaxPowerExport,
292 Description,
294 #[serde(untagged)]
296 Private(String),
297}
298
299#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
300#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
301pub enum Unit {
302 #[serde(rename = "KWH")]
304 KWH,
305 #[serde(rename = "GHG")]
307 GHG,
308 Volts,
310 Amps,
312 Celcius,
314 Fahrenheit,
316 Percent,
318 #[serde(rename = "KW")]
320 KW,
321 #[serde(rename = "KVAH")]
323 KVAH,
324 #[serde(rename = "KVARH")]
326 KVARH,
327 #[serde(rename = "KVA")]
329 KVA,
330 #[serde(rename = "KVAR")]
332 KVAR,
333 #[serde(untagged)]
335 Private(String),
336}
337
338#[cfg(test)]
339mod tests {
340 use crate::{Attribute, DataQuality, Identifier, OperatingState, Unit};
341
342 #[test]
343 fn test_operating_state_serialization() {
344 assert_eq!(
345 serde_json::to_string(&OperatingState::SGDErrorCondition).unwrap(),
346 r#""SGD_ERROR_CONDITION""#
347 );
348 assert_eq!(
349 serde_json::to_string(&OperatingState::Error).unwrap(),
350 r#""ERROR""#
351 );
352 assert_eq!(
353 serde_json::to_string(&OperatingState::Private(String::from("something else")))
354 .unwrap(),
355 r#""something else""#
356 );
357 assert_eq!(
358 serde_json::from_str::<OperatingState>(r#""NORMAL""#).unwrap(),
359 OperatingState::Normal
360 );
361 assert_eq!(
362 serde_json::from_str::<OperatingState>(r#""something else""#).unwrap(),
363 OperatingState::Private(String::from("something else"))
364 );
365 }
366
367 #[test]
368 fn test_data_quality_serialization() {
369 assert_eq!(serde_json::to_string(&DataQuality::Ok).unwrap(), r#""OK""#);
370 assert_eq!(
371 serde_json::to_string(&DataQuality::Private(String::from("something else"))).unwrap(),
372 r#""something else""#
373 );
374 assert_eq!(
375 serde_json::from_str::<DataQuality>(r#""MISSING""#).unwrap(),
376 DataQuality::Missing
377 );
378 assert_eq!(
379 serde_json::from_str::<DataQuality>(r#""something else""#).unwrap(),
380 DataQuality::Private(String::from("something else"))
381 );
382 }
383
384 #[test]
385 fn test_attribute_serialization() {
386 assert_eq!(
387 serde_json::to_string(&Attribute::Area).unwrap(),
388 r#""AREA""#
389 );
390 assert_eq!(
391 serde_json::to_string(&Attribute::Private(String::from("something else"))).unwrap(),
392 r#""something else""#
393 );
394 assert_eq!(
395 serde_json::from_str::<Attribute>(r#""MAX_POWER_EXPORT""#).unwrap(),
396 Attribute::MaxPowerExport
397 );
398 assert_eq!(
399 serde_json::from_str::<Attribute>(r#""something else""#).unwrap(),
400 Attribute::Private(String::from("something else"))
401 );
402 }
403
404 #[test]
405 fn test_unit_serialization() {
406 assert_eq!(serde_json::to_string(&Unit::KVARH).unwrap(), r#""KVARH""#);
407 assert_eq!(
408 serde_json::to_string(&Unit::Private(String::from("something else"))).unwrap(),
409 r#""something else""#
410 );
411 assert_eq!(
412 serde_json::from_str::<Unit>(r#""CELCIUS""#).unwrap(),
413 Unit::Celcius
414 );
415 assert_eq!(
416 serde_json::from_str::<Unit>(r#""something else""#).unwrap(),
417 Unit::Private(String::from("something else"))
418 );
419 }
420
421 impl quickcheck::Arbitrary for super::Duration {
422 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
423 super::Duration(iso8601_duration::Duration {
426 year: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
427 month: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
428 day: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
429 hour: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
430 minute: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
431 second: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
432 })
433 }
434 }
435
436 #[test]
437 fn duration_to_string_from_str_roundtrip() {
438 quickcheck::quickcheck(test as fn(_) -> bool);
439
440 fn test(input: super::Duration) -> bool {
441 let roundtrip = input.to_string().parse::<super::Duration>().unwrap();
442
443 assert_eq!(input.0, roundtrip.0);
444
445 input.0 == roundtrip.0
446 }
447 }
448
449 #[test]
450 fn deserialize_identifier() {
451 assert_eq!(
452 serde_json::from_str::<Identifier>(r#""example-999""#).unwrap(),
453 Identifier("example-999".to_string())
454 );
455 assert!(serde_json::from_str::<Identifier>(r#""þingvellir-999""#)
456 .unwrap_err()
457 .to_string()
458 .contains("identifier contains characters besides"));
459
460 let long = "x".repeat(128);
461 assert_eq!(
462 serde_json::from_str::<Identifier>(&format!("\"{long}\"")).unwrap(),
463 Identifier(long)
464 );
465
466 let too_long = "x".repeat(129);
467 assert!(
468 serde_json::from_str::<Identifier>(&format!("\"{too_long}\""))
469 .unwrap_err()
470 .to_string()
471 .contains("string length 129 outside of allowed range 1..=128")
472 );
473
474 assert!(serde_json::from_str::<Identifier>("\"\"")
475 .unwrap_err()
476 .to_string()
477 .contains("string length 0 outside of allowed range 1..=128"));
478 }
479
480 #[test]
481 fn deserialize_string_within_range_inclusive() {
482 use serde::Deserialize;
483
484 #[derive(Debug, Deserialize, PartialEq, Eq)]
485 struct Test(
486 #[serde(deserialize_with = "super::string_within_range_inclusive::<1, 128, _>")] String,
487 );
488
489 let long = "x".repeat(128);
490 assert_eq!(
491 serde_json::from_str::<Test>(&format!("\"{long}\"")).unwrap(),
492 Test(long)
493 );
494
495 let too_long = "x".repeat(129);
496 assert!(serde_json::from_str::<Test>(&format!("\"{too_long}\""))
497 .unwrap_err()
498 .to_string()
499 .contains("string length 129 outside of allowed range 1..=128"));
500
501 assert!(serde_json::from_str::<Test>("\"\"")
502 .unwrap_err()
503 .to_string()
504 .contains("string length 0 outside of allowed range 1..=128"));
505 }
506}