1#![deny(rustdoc::broken_intra_doc_links)]
2#![deny(rustdoc::private_intra_doc_links)]
3
4pub use event::Event;
11pub use program::Program;
12pub use report::Report;
13use serde::{de::Unexpected, Deserialize, Deserializer, Serialize, Serializer};
14use std::{fmt::Display, str::FromStr};
15pub use ven::Ven;
16
17pub mod event;
18pub mod interval;
19pub mod oauth;
20pub mod problem;
21pub mod program;
22pub mod report;
23pub mod resource;
24pub mod subscription;
25pub mod target;
26pub mod values_map;
27pub mod ven;
28
29pub mod serde_rfc3339 {
30 use super::*;
31
32 use chrono::{DateTime, TimeZone, Utc};
33
34 pub fn serialize<S, Tz>(time: &DateTime<Tz>, serializer: S) -> Result<S::Ok, S::Error>
35 where
36 S: Serializer,
37 Tz: TimeZone,
38 {
39 serializer.serialize_str(&time.to_rfc3339())
40 }
41
42 pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
43 where
44 D: Deserializer<'de>,
45 {
46 let rfc_str = <String as Deserialize>::deserialize(deserializer)?;
47
48 match DateTime::parse_from_rfc3339(&rfc_str) {
49 Ok(datetime) => Ok(datetime.into()),
50 Err(_) => Err(serde::de::Error::invalid_value(
51 Unexpected::Str(&rfc_str),
52 &"Invalid RFC3339 string",
53 )),
54 }
55 }
56}
57
58pub fn string_within_range_inclusive<'de, const MIN: usize, const MAX: usize, D>(
59 deserializer: D,
60) -> Result<String, D::Error>
61where
62 D: Deserializer<'de>,
63{
64 let string = <String as Deserialize>::deserialize(deserializer)?;
65 let len = string.len();
66
67 if (MIN..=MAX).contains(&len) {
68 Ok(string.to_string())
69 } else {
70 Err(serde::de::Error::invalid_value(
71 Unexpected::Str(&string),
72 &IdentifierError::InvalidLength(len).to_string().as_str(),
73 ))
74 }
75}
76
77#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord, sqlx::Type)]
79#[sqlx(transparent)]
80pub struct Identifier(#[serde(deserialize_with = "identifier")] String);
81
82impl<'de> Deserialize<'de> for Identifier {
83 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84 where
85 D: Deserializer<'de>,
86 {
87 let s: String = Deserialize::deserialize(deserializer)?;
88
89 match Self::validate(&s) {
90 Ok(()) => Ok(Identifier(s)),
91 Err(e) => Err(serde::de::Error::invalid_value(
92 Unexpected::Str(&s),
93 &e.to_string().as_str(),
94 )),
95 }
96 }
97}
98
99#[derive(thiserror::Error, Debug)]
100pub enum IdentifierError {
101 #[error("string length {0} outside of allowed range 1..=128")]
102 InvalidLength(usize),
103 #[error("identifier contains characters besides [a-zA-Z0-9_-]: {0}")]
104 InvalidCharacter(String),
105 #[error("this identifier name is not allowed: {0}")]
106 ForbiddenName(String),
107}
108
109const FORBIDDEN_NAMES: &[&str] = &["null"];
110
111impl FromStr for Identifier {
112 type Err = IdentifierError;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 Self::validate(s).map(|()| Identifier(s.to_string()))
116 }
117}
118
119impl Identifier {
120 fn validate(s: &str) -> Result<(), IdentifierError> {
121 let is_valid_character = |b: u8| b.is_ascii_alphanumeric() || b == b'_' || b == b'-';
122
123 if !(1..=128).contains(&s.len()) {
124 Err(IdentifierError::InvalidLength(s.len()))
125 } else if !s.bytes().all(is_valid_character) {
126 Err(IdentifierError::InvalidCharacter(s.to_string()))
127 } else if FORBIDDEN_NAMES.contains(&s.to_ascii_lowercase().as_str()) {
128 Err(IdentifierError::ForbiddenName(s.to_string()))
129 } else {
130 Ok(())
131 }
132 }
133
134 pub fn as_str(&self) -> &str {
135 &self.0
136 }
137}
138
139impl Display for Identifier {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 write!(f, "{}", self.0)
142 }
143}
144
145#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
146#[serde(rename_all = "UPPERCASE")]
147pub enum ObjectType {
148 Program,
149 Event,
150 Report,
151 Subscription,
152 Ven,
153 Resource,
154}
155
156#[derive(Clone, Debug, PartialEq)]
158pub struct Duration(iso8601_duration::Duration);
159
160impl<'de> Deserialize<'de> for Duration {
161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162 where
163 D: Deserializer<'de>,
164 {
165 let raw = String::deserialize(deserializer)?;
166 let duration = raw
167 .parse::<iso8601_duration::Duration>()
168 .map_err(|_| "iso8601_duration::ParseDurationError")
169 .map_err(serde::de::Error::custom)?;
170
171 Ok(Self(duration))
172 }
173}
174
175impl Serialize for Duration {
176 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: Serializer,
179 {
180 self.to_string().serialize(serializer)
181 }
182}
183
184impl Duration {
185 pub fn to_chrono_at_datetime<Tz: chrono::TimeZone>(
191 &self,
192 at: chrono::DateTime<Tz>,
193 ) -> chrono::Duration {
194 self.0.to_chrono_at_datetime(at)
195 }
196
197 pub const PT1H: Self = Self(iso8601_duration::Duration {
199 year: 0.0,
200 month: 0.0,
201 day: 0.0,
202 hour: 1.0,
203 minute: 0.0,
204 second: 0.0,
205 });
206
207 pub const P999Y: Self = Self(iso8601_duration::Duration {
210 year: 9999.0,
211 month: 0.0,
212 day: 0.0,
213 hour: 0.0,
214 minute: 0.0,
215 second: 0.0,
216 });
217
218 pub const PT0S: Self = Self(iso8601_duration::Duration {
219 year: 0.0,
220 month: 0.0,
221 day: 0.0,
222 hour: 0.0,
223 minute: 0.0,
224 second: 0.0,
225 });
226
227 pub const fn hours(hour: f32) -> Self {
228 Self(iso8601_duration::Duration {
229 year: 0.0,
230 month: 0.0,
231 day: 0.0,
232 hour,
233 minute: 0.0,
234 second: 0.0,
235 })
236 }
237}
238
239impl std::str::FromStr for Duration {
240 type Err = iso8601_duration::ParseDurationError;
241
242 fn from_str(s: &str) -> Result<Self, Self::Err> {
243 let duration = s.parse::<iso8601_duration::Duration>()?;
244 Ok(Self(duration))
245 }
246}
247
248impl Display for Duration {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 let iso8601_duration::Duration {
251 year,
252 month,
253 day,
254 hour,
255 minute,
256 second,
257 } = self.0;
258
259 f.write_fmt(format_args!(
260 "P{year}Y{month}M{day}DT{hour}H{minute}M{second}S",
261 ))
262 }
263}
264
265#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
266#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
267pub enum OperatingState {
268 Normal,
269 Error,
270 IdleNormal,
271 RunningNormal,
272 RunningCurtailed,
273 RunningHeightened,
274 IdleCurtailed,
275 #[serde(rename = "SGD_ERROR_CONDITION")]
276 SGDErrorCondition,
277 IdleHeightened,
278 IdleOptedOut,
279 RunningOptedOut,
280 #[serde(untagged)]
281 Private(String),
282}
283
284#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
285#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
286pub enum DataQuality {
287 Ok,
289 Missing,
291 Estimated,
293 Bad,
295 #[serde(untagged)]
297 Private(String),
298}
299
300#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
301#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
302pub enum Attribute {
303 Location,
307 Area,
311 MaxPowerConsumption,
313 MaxPowerExport,
315 Description,
317 #[serde(untagged)]
319 Private(String),
320}
321
322#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
323#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
324pub enum Unit {
325 #[serde(rename = "KWH")]
327 KWH,
328 #[serde(rename = "GHG")]
330 GHG,
331 Volts,
333 Amps,
335 Celcius,
337 Fahrenheit,
339 Percent,
341 #[serde(rename = "KW")]
343 KW,
344 #[serde(rename = "KVAH")]
346 KVAH,
347 #[serde(rename = "KVARH")]
349 KVARH,
350 #[serde(rename = "KVA")]
352 KVA,
353 #[serde(rename = "KVAR")]
355 KVAR,
356 #[serde(untagged)]
358 Private(String),
359}
360
361#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq, sqlx::Type)]
363#[sqlx(transparent)]
364pub struct ClientId(pub(crate) Identifier);
365
366impl Display for ClientId {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 write!(f, "{}", self.0)
369 }
370}
371
372impl FromStr for ClientId {
373 type Err = IdentifierError;
374
375 fn from_str(s: &str) -> Result<Self, Self::Err> {
376 Ok(Self(s.parse()?))
377 }
378}
379
380impl ClientId {
381 pub fn as_str(&self) -> &str {
382 self.0.as_str()
383 }
384
385 pub fn new(identifier: &str) -> Option<Self> {
386 Some(Self(identifier.parse().ok()?))
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use crate::{Attribute, DataQuality, Identifier, OperatingState, Unit};
393 use chrono::{DateTime, Utc};
394
395 #[test]
396 fn test_operating_state_serialization() {
397 assert_eq!(
398 serde_json::to_string(&OperatingState::SGDErrorCondition).unwrap(),
399 r#""SGD_ERROR_CONDITION""#
400 );
401 assert_eq!(
402 serde_json::to_string(&OperatingState::Error).unwrap(),
403 r#""ERROR""#
404 );
405 assert_eq!(
406 serde_json::to_string(&OperatingState::Private(String::from("something else")))
407 .unwrap(),
408 r#""something else""#
409 );
410 assert_eq!(
411 serde_json::from_str::<OperatingState>(r#""NORMAL""#).unwrap(),
412 OperatingState::Normal
413 );
414 assert_eq!(
415 serde_json::from_str::<OperatingState>(r#""something else""#).unwrap(),
416 OperatingState::Private(String::from("something else"))
417 );
418 }
419
420 #[test]
421 fn test_data_quality_serialization() {
422 assert_eq!(serde_json::to_string(&DataQuality::Ok).unwrap(), r#""OK""#);
423 assert_eq!(
424 serde_json::to_string(&DataQuality::Private(String::from("something else"))).unwrap(),
425 r#""something else""#
426 );
427 assert_eq!(
428 serde_json::from_str::<DataQuality>(r#""MISSING""#).unwrap(),
429 DataQuality::Missing
430 );
431 assert_eq!(
432 serde_json::from_str::<DataQuality>(r#""something else""#).unwrap(),
433 DataQuality::Private(String::from("something else"))
434 );
435 }
436
437 #[test]
438 fn test_attribute_serialization() {
439 assert_eq!(
440 serde_json::to_string(&Attribute::Area).unwrap(),
441 r#""AREA""#
442 );
443 assert_eq!(
444 serde_json::to_string(&Attribute::Private(String::from("something else"))).unwrap(),
445 r#""something else""#
446 );
447 assert_eq!(
448 serde_json::from_str::<Attribute>(r#""MAX_POWER_EXPORT""#).unwrap(),
449 Attribute::MaxPowerExport
450 );
451 assert_eq!(
452 serde_json::from_str::<Attribute>(r#""something else""#).unwrap(),
453 Attribute::Private(String::from("something else"))
454 );
455 }
456
457 #[test]
458 fn test_unit_serialization() {
459 assert_eq!(serde_json::to_string(&Unit::KVARH).unwrap(), r#""KVARH""#);
460 assert_eq!(
461 serde_json::to_string(&Unit::Private(String::from("something else"))).unwrap(),
462 r#""something else""#
463 );
464 assert_eq!(
465 serde_json::from_str::<Unit>(r#""CELCIUS""#).unwrap(),
466 Unit::Celcius
467 );
468 assert_eq!(
469 serde_json::from_str::<Unit>(r#""something else""#).unwrap(),
470 Unit::Private(String::from("something else"))
471 );
472 }
473
474 impl quickcheck::Arbitrary for super::Duration {
475 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
476 super::Duration(iso8601_duration::Duration {
479 year: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
480 month: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
481 day: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
482 hour: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
483 minute: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
484 second: (<u32 as quickcheck::Arbitrary>::arbitrary(g) & 0x00FF_FFFF) as f32,
485 })
486 }
487 }
488
489 #[test]
490 fn duration_to_string_from_str_roundtrip() {
491 quickcheck::quickcheck(test as fn(_) -> bool);
492
493 fn test(input: super::Duration) -> bool {
494 let roundtrip = input.to_string().parse::<super::Duration>().unwrap();
495
496 assert_eq!(input.0, roundtrip.0);
497
498 input.0 == roundtrip.0
499 }
500 }
501
502 #[test]
503 fn deserialize_identifier() {
504 assert_eq!(
505 serde_json::from_str::<Identifier>(r#""example-999""#).unwrap(),
506 Identifier("example-999".to_string())
507 );
508 assert!(serde_json::from_str::<Identifier>(r#""þingvellir-999""#)
509 .unwrap_err()
510 .to_string()
511 .contains("identifier contains characters besides"));
512
513 let long = "x".repeat(128);
514 assert_eq!(
515 serde_json::from_str::<Identifier>(&format!("\"{long}\"")).unwrap(),
516 Identifier(long)
517 );
518
519 let too_long = "x".repeat(129);
520 assert!(
521 serde_json::from_str::<Identifier>(&format!("\"{too_long}\""))
522 .unwrap_err()
523 .to_string()
524 .contains("string length 129 outside of allowed range 1..=128")
525 );
526
527 assert!(serde_json::from_str::<Identifier>("\"\"")
528 .unwrap_err()
529 .to_string()
530 .contains("string length 0 outside of allowed range 1..=128"));
531 }
532
533 #[test]
534 fn deserialize_string_within_range_inclusive() {
535 use serde::Deserialize;
536
537 #[derive(Debug, Deserialize, PartialEq, Eq)]
538 struct Test(
539 #[serde(deserialize_with = "super::string_within_range_inclusive::<1, 128, _>")] String,
540 );
541
542 let long = "x".repeat(128);
543 assert_eq!(
544 serde_json::from_str::<Test>(&format!("\"{long}\"")).unwrap(),
545 Test(long)
546 );
547
548 let too_long = "x".repeat(129);
549 assert!(serde_json::from_str::<Test>(&format!("\"{too_long}\""))
550 .unwrap_err()
551 .to_string()
552 .contains("string length 129 outside of allowed range 1..=128"));
553
554 assert!(serde_json::from_str::<Test>("\"\"")
555 .unwrap_err()
556 .to_string()
557 .contains("string length 0 outside of allowed range 1..=128"));
558 }
559
560 #[test]
561 fn deserialize_datetime() {
562 use serde::Deserialize;
563 #[derive(Debug, Deserialize, PartialEq, Eq)]
566 struct Test(#[serde(with = "super::serde_rfc3339")] DateTime<Utc>);
567
568 let valid_dates = [
569 "1972-06-30T23:59:60Z",
570 "2019-03-26T14:00:00.9Z",
571 "2019-03-26T14:00:00.4999Z",
572 "1969-03-26T14:00:00.4999Z",
573 ];
574
575 for valid in valid_dates {
576 assert_eq!(
577 serde_json::from_str::<Test>(&format!("\"{valid}\"")).unwrap(),
578 Test(valid.parse().unwrap())
579 );
580 }
581
582 let invalid_dates = [
583 "2019-03-26T14:00:00,999Z",
584 "2019-03-26T10:00-04",
585 "2019-03-26T14:00.9Z",
586 "20190326T1400Z",
587 "2019-02-30",
588 "2019-03-25T24:01Z",
589 "2019-03-26T14:00+24:00",
590 "2019-03-26Z",
591 "2019-03-26+01:00",
592 "2019-03-26-04:00",
593 "2019-03-26T10:00-0400",
594 "+0002019-03-26T14:00Z",
595 "+2019-03-26T14:00Z",
596 "002019-03-26T14:00Z",
597 "019-03-26T14:00Z",
598 "2019-03-26T10:00Q",
599 "2019-03-26T10:00T",
600 "2019-03-26Q",
601 "2019-03-26T",
602 "2019-03-26 14:00Z",
603 "2019-03-26T14:00:00.",
604 ];
605
606 for invalid in invalid_dates {
607 assert!(serde_json::from_str::<Test>(&format!("\"{invalid}\""))
608 .unwrap_err()
609 .to_string()
610 .contains("Invalid RFC3339 string"));
611 }
612 }
613}