Skip to main content

openleadr_wire/
lib.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2#![deny(rustdoc::private_intra_doc_links)]
3
4//! Wire format definitions for OpenADR endpoints
5//!
6//! The types in this module model the messages sent over the wire in OpenADR 3.0.
7//! Most types are originally generated from the OpenAPI specification of OpenADR
8//! and manually modified to be more idiomatic.
9
10pub 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/// A string that matches `/^[a-zA-Z0-9_-]*$/` with length in 1..=128
78#[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/// An ISO 8601 formatted duration
157#[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    /// Because iso8601 durations can include months and years, they don't independently have a
186    /// fixed duration. Their real duration (in real units like seconds) can only be determined
187    /// when a starting time is given.
188    ///
189    /// NOTE: does not consider leap seconds!
190    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    /// One (1) hour
198    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    /// Indicates that an event's intervals continue indefinitely into the future until the event is
208    /// deleted or modified. This effectively represents an infinite duration.
209    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    /// No known reasons to doubt the data.
288    Ok,
289    /// The data item is currently unavailable.
290    Missing,
291    /// The data item has been estimated from other available information.
292    Estimated,
293    /// The data item is suspected to be bad or is known to be.
294    Bad,
295    /// An application specific privately defined data quality setting.
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 Attribute {
303    /// Describes a single geographic point. Values contains 2 floats, generally
304    /// representing longitude and latitude. Demand Response programs may define
305    /// their own use of these fields.
306    Location,
307    /// Describes a geographic area. Application specific data. Demand Response
308    /// programs may define their own use of these fields, such as GeoJSON
309    /// polygon data.
310    Area,
311    /// The maximum consumption as a float, in kiloWatts.
312    MaxPowerConsumption,
313    /// The maximum power the device can export as a float, in kiloWatts.
314    MaxPowerExport,
315    /// A free-form short description of a VEN or resource.
316    Description,
317    /// An application specific privately defined attribute.
318    #[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    /// Kilowatt-hours (kWh)
326    #[serde(rename = "KWH")]
327    KWH,
328    /// Greenhouse gas emissions (g/kWh)
329    #[serde(rename = "GHG")]
330    GHG,
331    /// Voltage (V)
332    Volts,
333    /// Current (A)
334    Amps,
335    /// Temperature (C)
336    Celcius,
337    /// Temperature (F)
338    Fahrenheit,
339    /// Percentage (%)
340    Percent,
341    /// Kilowatts
342    #[serde(rename = "KW")]
343    KW,
344    /// Kilovolt-ampere hours (kVAh)
345    #[serde(rename = "KVAH")]
346    KVAH,
347    /// Kilovolt-amperes reactive hours (kVARh)
348    #[serde(rename = "KVARH")]
349    KVARH,
350    /// Kilovolt-amperes (kVA)
351    #[serde(rename = "KVA")]
352    KVA,
353    /// Kilovolt-amperes reactive (kVAR)
354    #[serde(rename = "KVAR")]
355    KVAR,
356    /// An application specific privately defined unit.
357    #[serde(untagged)]
358    Private(String),
359}
360
361// example: 249rj49jiej
362#[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            // the iso8601_duration library uses an f32 to store the values, which starts losing
477            // precision at 24-bit integers.
478            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        // Thanks to https://tc39.es/proposal-uniform-interchange-date-parsing/cases.html
564
565        #[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}