openleadr_wire/
lib.rs

1//! Wire format definitions for OpenADR endpoints
2//!
3//! The types in this module model the messages sent over the wire in OpenADR 3.0.
4//! Most types are originally generated from the OpenAPI specification of OpenADR
5//! and manually modified to be more idiomatic.
6
7use 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/// A string that matches `/^[a-zA-Z0-9_-]*$/` with length in 1..=128
75#[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/// An ISO 8601 formatted duration
134#[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    /// Because iso8601 durations can include months and years, they don't independently have a
163    /// fixed duration. Their real duration (in real units like seconds) can only be determined
164    /// when a starting time is given.
165    ///
166    /// NOTE: does not consider leap seconds!
167    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    /// One (1) hour
175    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    /// Indicates that an event's intervals continue indefinitely into the future until the event is
185    /// deleted or modified. This effectively represents an infinite duration.
186    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    /// No known reasons to doubt the data.
265    Ok,
266    /// The data item is currently unavailable.
267    Missing,
268    /// The data item has been estimated from other available information.
269    Estimated,
270    /// The data item is suspected to be bad or is known to be.
271    Bad,
272    /// An application specific privately defined data quality setting.
273    #[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    /// Describes a single geographic point. Values contains 2 floats, generally
281    /// representing longitude and latitude. Demand Response programs may define
282    /// their own use of these fields.
283    Location,
284    /// Describes a geographic area. Application specific data. Demand Response
285    /// programs may define their own use of these fields, such as GeoJSON
286    /// polygon data.
287    Area,
288    /// The maximum consumption as a float, in kiloWatts.
289    MaxPowerConsumption,
290    /// The maximum power the device can export as a float, in kiloWatts.
291    MaxPowerExport,
292    /// A free-form short description of a VEN or resource.
293    Description,
294    /// An application specific privately defined attribute.
295    #[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    /// Kilowatt-hours (kWh)
303    #[serde(rename = "KWH")]
304    KWH,
305    /// Greenhouse gas emissions (g/kWh)
306    #[serde(rename = "GHG")]
307    GHG,
308    /// Voltage (V)
309    Volts,
310    /// Current (A)
311    Amps,
312    /// Temperature (C)
313    Celcius,
314    /// Temperature (F)
315    Fahrenheit,
316    /// Percentage (%)
317    Percent,
318    /// Kilowatts
319    #[serde(rename = "KW")]
320    KW,
321    /// Kilovolt-ampere hours (kVAh)
322    #[serde(rename = "KVAH")]
323    KVAH,
324    /// Kilovolt-amperes reactive hours (kVARh)
325    #[serde(rename = "KVARH")]
326    KVARH,
327    /// Kilovolt-amperes (kVA)
328    #[serde(rename = "KVA")]
329    KVA,
330    /// Kilovolt-amperes reactive (kVAR)
331    #[serde(rename = "KVAR")]
332    KVAR,
333    /// An application specific privately defined unit.
334    #[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            // the iso8601_duration library uses an f32 to store the values, which starts losing
424            // precision at 24-bit integers.
425            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}