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