Skip to main content

openleadr_wire/
event.rs

1//! Types used for the `event/` endpoint
2
3use crate::{
4    Duration, Identifier, IdentifierError, Unit, interval::IntervalPeriod, program::ProgramId,
5    report::ReportDescriptor, target::Target, values_map::Value,
6};
7use chrono::{DateTime, Utc};
8use iso_currency::Currency;
9use serde::{Deserialize, Serialize};
10use serde_with::{DefaultOnNull, serde_as, skip_serializing_none};
11use std::{
12    fmt::{Display, Formatter},
13    str::FromStr,
14};
15use validator::{Validate, ValidationError};
16
17/// Event object to communicate a Demand Response request to VEN. If intervalPeriod is present, sets
18/// default start time and duration of intervals.
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
20#[serde(rename_all = "camelCase")]
21pub struct Event {
22    /// URL safe VTN assigned object ID.
23    pub id: EventId,
24    /// datetime in ISO 8601 format
25    #[serde(with = "crate::serde_rfc3339")]
26    pub created_date_time: DateTime<Utc>,
27    /// datetime in ISO 8601 format
28    #[serde(with = "crate::serde_rfc3339")]
29    pub modification_date_time: DateTime<Utc>,
30    #[serde(flatten)]
31    #[validate(nested)]
32    pub content: EventRequest,
33}
34
35#[skip_serializing_none]
36#[serde_as]
37#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
38#[serde(rename_all = "camelCase", tag = "objectType", rename = "EVENT")]
39pub struct EventRequest {
40    /// URL safe VTN assigned object ID.
41    #[serde(rename = "programID")]
42    pub program_id: ProgramId,
43    /// User defined string for use in debugging or User Interface.
44    pub event_name: Option<String>,
45    /// Optional duration of event. May be used to loop intervals. See User Guide.
46    pub duration: Option<Duration>,
47    /// Relative priority of event. A lower number is a higher priority.
48    pub priority: Priority,
49    /// A list of targets.
50    #[serde(default)]
51    #[serde_as(deserialize_as = "DefaultOnNull")]
52    pub targets: Vec<Target>,
53    /// A list of reportDescriptor objects. Used to request reports from VEN.
54    pub report_descriptors: Option<Vec<ReportDescriptor>>,
55    /// A list of payloadDescriptor objects.
56    pub payload_descriptors: Option<Vec<EventPayloadDescriptor>>,
57    /// Defines default start and durations of intervals.
58    pub interval_period: Option<IntervalPeriod>,
59    /// A list of interval objects.
60    #[validate(nested)]
61    pub intervals: Option<Vec<EventInterval>>,
62}
63
64impl EventRequest {
65    pub fn new(program_id: ProgramId) -> Self {
66        Self {
67            program_id,
68            event_name: None,
69            duration: None,
70            priority: Priority::UNSPECIFIED,
71            targets: vec![],
72            report_descriptors: None,
73            payload_descriptors: None,
74            interval_period: None,
75            intervals: None,
76        }
77    }
78
79    pub fn with_event_name(mut self, event_name: impl ToString) -> Self {
80        self.event_name = Some(event_name.to_string());
81        self
82    }
83
84    pub fn with_priority(self, priority: Priority) -> Self {
85        Self { priority, ..self }
86    }
87
88    pub fn with_targets(mut self, targets: Vec<Target>) -> Self {
89        self.targets = targets;
90        self
91    }
92
93    pub fn with_report_descriptors(mut self, report_descriptors: Vec<ReportDescriptor>) -> Self {
94        self.report_descriptors = Some(report_descriptors);
95        self
96    }
97
98    pub fn with_payload_descriptors(
99        mut self,
100        payload_descriptors: Vec<EventPayloadDescriptor>,
101    ) -> Self {
102        self.payload_descriptors = Some(payload_descriptors);
103        self
104    }
105
106    pub fn with_interval_period(mut self, interval_period: IntervalPeriod) -> Self {
107        self.interval_period = Some(interval_period);
108        self
109    }
110
111    pub fn with_intervals(mut self, intervals: Vec<EventInterval>) -> Self {
112        self.intervals = Some(intervals);
113        self
114    }
115}
116
117/// URL safe VTN assigned object ID
118#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
119pub struct EventId(pub(crate) Identifier);
120
121impl Display for EventId {
122    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
123        write!(f, "{}", self.0)
124    }
125}
126
127impl EventId {
128    pub fn as_str(&self) -> &str {
129        self.0.as_str()
130    }
131}
132
133impl FromStr for EventId {
134    type Err = IdentifierError;
135
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        Ok(Self(s.parse()?))
138    }
139}
140
141/// Relative priority of an event
142///
143/// `0` indicates the highest priority.
144///
145/// **Interpretation of the specification:** [`Priority::UNSPECIFIED`] has a lower priority than any other value,
146/// i.e., equals to [`Priority::MIN`]
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(transparent)]
149pub struct Priority(Option<u32>);
150
151impl Priority {
152    pub const UNSPECIFIED: Self = Self(None);
153
154    pub const MAX: Self = Self(Some(0));
155    pub const MIN: Self = Self::UNSPECIFIED;
156
157    pub const fn new(val: u32) -> Self {
158        Self(Some(val))
159    }
160}
161
162impl PartialOrd for Priority {
163    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
164        Some(self.cmp(other))
165    }
166}
167
168impl Ord for Priority {
169    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
170        use std::cmp::Ordering;
171
172        match (self.0, other.0) {
173            (None, None) => Ordering::Equal,
174            (None, Some(_)) => Ordering::Less,
175            (Some(_), None) => Ordering::Greater,
176            (Some(s), Some(o)) => s.cmp(&o).reverse(),
177        }
178    }
179}
180
181impl From<Option<i64>> for Priority {
182    fn from(value: Option<i64>) -> Self {
183        Self(value.and_then(|i| i.unsigned_abs().try_into().ok()))
184    }
185}
186
187impl From<Priority> for Option<i64> {
188    fn from(value: Priority) -> Self {
189        value.0.map(|u| u.into())
190    }
191}
192
193/// Contextual information used to interpret event valuesMap values. E.g. a PRICE payload simply
194/// contains a price value, an associated descriptor provides necessary context such as units and
195/// currency.
196#[skip_serializing_none]
197#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct EventPayloadDescriptor {
200    /// Represents the nature of values.
201    ///
202    /// See enumerations in Definitions for defined string values, or use privately defined strings
203    pub payload_type: EventType,
204    /// Units of measure.
205    pub units: Option<Unit>,
206    /// Currency of price payload.
207    pub currency: Option<Currency>,
208}
209
210impl EventPayloadDescriptor {
211    pub fn new(payload_type: EventType) -> Self {
212        Self {
213            payload_type,
214            units: None,
215            currency: None,
216        }
217    }
218}
219
220/// An object defining a temporal window and a list of valuesMaps. if intervalPeriod present may set
221/// temporal aspects of interval or override event.intervalPeriod.
222#[skip_serializing_none]
223#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, Validate)]
224#[serde(rename_all = "camelCase")]
225pub struct EventInterval {
226    /// A client generated number assigned an interval object. Not a sequence number.
227    pub id: i32,
228    /// Defines default start and durations of intervals.
229    pub interval_period: Option<IntervalPeriod>,
230    /// A list of valuesMap objects.
231    #[validate(length(min = 1))]
232    pub payloads: Vec<EventValuesMap>,
233}
234
235impl EventInterval {
236    pub fn new(id: i32, payloads: Vec<EventValuesMap>) -> Self {
237        Self {
238            id,
239            interval_period: None,
240            payloads,
241        }
242    }
243}
244
245/// Represents one or more values associated with a type. E.g. a type of PRICE contains a single float value.
246#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Validate)]
247#[validate(schema(function = "validate_payload"))]
248pub struct EventValuesMap {
249    /// Enumerated or private string signifying the nature of values. E.G. \"PRICE\" indicates value is to be interpreted as a currency.
250    #[serde(rename = "type")]
251    pub value_type: EventType,
252    /// A list of data points. Most often a singular value such as a price.
253    // TODO: The type of Value is actually defined by value_type, see #93
254    pub values: Vec<Value>,
255}
256
257/// Validate each value in the payload matches the given value type.
258///
259/// Errors on the first mistyped value. It might be useful to return all validation errors rather
260/// than just the first one, but the validator crate doesn't seem to support this yet.
261/// See https://github.com/Keats/validator/issues/326
262fn validate_payload(payload: &EventValuesMap) -> Result<(), ValidationError> {
263    for value in &payload.values {
264        validate_value(&payload.value_type, value)?
265    }
266    Ok(())
267}
268
269#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
270#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
271pub enum EventType {
272    Simple,
273    Price,
274    ChargeStateSetpoint,
275    DispatchSetpoint,
276    DispatchSetpointRelative,
277    ControlSetpoint,
278    ExportPrice,
279    #[serde(rename = "GHG")]
280    GHG,
281    Curve,
282    #[serde(rename = "OLS")]
283    OLS,
284    ImportCapacitySubscription,
285    ImportCapacityReservation,
286    ImportCapacityReservationFee,
287    ImportCapacityAvailable,
288    ImportCapacityAvailablePrice,
289    ExportCapacitySubscription,
290    ExportCapacityReservation,
291    ExportCapacityReservationFee,
292    ExportCapacityAvailable,
293    ExportCapacityAvailablePrice,
294    ImportCapacityLimit,
295    ExportCapacityLimit,
296    AlertGridEmergency,
297    AlertBlackStart,
298    AlertPossibleOutage,
299    AlertFlexAlert,
300    AlertFire,
301    AlertFreezing,
302    AlertWind,
303    AlertTsunami,
304    AlertAirQuality,
305    AlertOther,
306    #[serde(rename = "CTA2045_REBOOT")]
307    CTA2045Reboot,
308    #[serde(rename = "CTA2045_SET_OVERRIDE_STATUS")]
309    CTA2045SetOverrideStatus,
310    #[serde(untagged)]
311    #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
312    Private(String),
313}
314
315fn validate_value(value_type: &EventType, value: &Value) -> Result<(), ValidationError> {
316    match (value_type, value) {
317        (EventType::Simple, Value::Integer(_)) => Ok(()), // integer
318        (EventType::Price, Value::Number(_)) => Ok(()),   // float
319        (EventType::ChargeStateSetpoint, Value::Number(_)) => Ok(()),
320        (EventType::DispatchSetpoint, Value::Number(_)) => Ok(()), // float
321        (EventType::DispatchSetpointRelative, Value::Number(_)) => Ok(()), // float
322        (EventType::ControlSetpoint, _) => Ok(()),                 // "depends"
323        (EventType::ExportPrice, Value::Number(_)) => Ok(()),      // float
324        (EventType::GHG, Value::Number(_)) => Ok(()),              // float
325        (EventType::Curve, Value::Point(_)) => Ok(()),             // pairs of floats
326        (EventType::OLS, Value::Number(_)) => Ok(()),              // 0.0 to 1.0
327        (EventType::ImportCapacitySubscription, Value::Number(_)) => Ok(()), // float
328        (EventType::ImportCapacityReservation, Value::Number(_)) => Ok(()), // float
329        (EventType::ImportCapacityReservationFee, Value::Number(_)) => Ok(()), // float
330        (EventType::ImportCapacityAvailable, Value::Number(_)) => Ok(()), // float
331        (EventType::ImportCapacityAvailablePrice, Value::Number(_)) => Ok(()), // float
332        (EventType::ExportCapacitySubscription, Value::Number(_)) => Ok(()), // float
333        (EventType::ExportCapacityReservation, Value::Number(_)) => Ok(()), // float
334        (EventType::ExportCapacityReservationFee, Value::Number(_)) => Ok(()), // float
335        (EventType::ExportCapacityAvailable, Value::Number(_)) => Ok(()), // float
336        (EventType::ExportCapacityAvailablePrice, Value::Number(_)) => Ok(()), // float
337        (EventType::ImportCapacityLimit, Value::Number(_)) => Ok(()), // float
338        (EventType::ExportCapacityLimit, Value::Number(_)) => Ok(()), // float
339        (EventType::AlertGridEmergency, Value::String(_)) => Ok(()), // human-readable string
340        (EventType::AlertBlackStart, Value::String(_)) => Ok(()),  // human-readable string
341        (EventType::AlertPossibleOutage, Value::String(_)) => Ok(()), // human-readable string
342        (EventType::AlertFlexAlert, Value::String(_)) => Ok(()),   // human-readable string
343        (EventType::AlertFire, Value::String(_)) => Ok(()),        // human-readable string
344        (EventType::AlertFreezing, Value::String(_)) => Ok(()),    // human-readable string
345        (EventType::AlertWind, Value::String(_)) => Ok(()),        // human-readable string
346        (EventType::AlertTsunami, Value::String(_)) => Ok(()),     // human-readable string
347        (EventType::AlertAirQuality, Value::String(_)) => Ok(()),  // human-readable string
348        (EventType::AlertOther, Value::String(_)) => Ok(()),       // human-readable string
349        (EventType::CTA2045Reboot, Value::Integer(_)) => Ok(()),   // 0 = SOFT, 1 = HARD
350        (EventType::CTA2045SetOverrideStatus, Value::Integer(_)) => Ok(()), // 0 = No Override, 1 = Override
351        (EventType::Private(_), _) => Ok(()), // Allow all types for private types
352        (value_type, value) => Err(validate_value_error(value_type, value)),
353    }
354}
355
356fn validate_value_error(value_type: &EventType, value: &Value) -> ValidationError {
357    let cow = format!("value {value:?} must match the given type {value_type:?}").into();
358    ValidationError::new("values must match the given type").with_message(cow)
359}
360
361#[cfg(test)]
362mod tests {
363    use crate::{Duration, values_map::Value};
364    use std::borrow::Cow;
365
366    use super::*;
367
368    #[test]
369    fn priority_order() {
370        assert_eq!(Priority::MAX, Priority::new(0));
371        assert!(Priority::MAX > Priority::MIN);
372        assert_eq!(Priority::MIN, Priority::UNSPECIFIED);
373        assert!(Priority::new(5) > Priority::UNSPECIFIED);
374        assert!(Priority::new(5) > Priority::new(6));
375        assert!(Priority::new(u32::MAX) > Priority::UNSPECIFIED);
376    }
377
378    #[test]
379    fn test_event_serialization() {
380        assert_eq!(
381            serde_json::to_string(&EventType::Simple).unwrap(),
382            r#""SIMPLE""#
383        );
384        assert_eq!(
385            serde_json::to_string(&EventType::CTA2045Reboot).unwrap(),
386            r#""CTA2045_REBOOT""#
387        );
388        assert_eq!(
389            serde_json::from_str::<EventType>(r#""GHG""#).unwrap(),
390            EventType::GHG
391        );
392        assert_eq!(
393            serde_json::from_str::<EventType>(r#""something else""#).unwrap(),
394            EventType::Private(String::from("something else"))
395        );
396
397        assert!(serde_json::from_str::<EventType>(r#""""#).is_err());
398        assert!(serde_json::from_str::<EventType>(&format!("\"{}\"", "x".repeat(129))).is_err());
399    }
400
401    #[test]
402    fn parse_minimal() {
403        let example = r#"{"programID":"foo"}"#;
404        assert_eq!(
405            serde_json::from_str::<EventRequest>(example).unwrap(),
406            EventRequest {
407                program_id: ProgramId("foo".parse().unwrap()),
408                event_name: None,
409                duration: None,
410                priority: Priority::MIN,
411                targets: vec![],
412                report_descriptors: None,
413                payload_descriptors: None,
414                interval_period: None,
415                intervals: None,
416            }
417        );
418    }
419
420    #[test]
421    fn example_parses() {
422        let example = r#"[{
423                                    "id": "object-999-foo",
424                                    "createdDateTime": "2023-06-15T09:30:00Z",
425                                    "modificationDateTime": "2023-06-15T09:30:00Z",
426                                    "objectType": "EVENT",
427                                    "programID": "object-999",
428                                    "eventName": "price event 11-18-2022",
429                                    "duration": "PT1H",
430                                    "priority": 0,
431                                    "targets": null,
432                                    "reportDescriptors": null,
433                                    "payloadDescriptors": null,
434                                    "intervalPeriod": {
435                                      "start": "2023-06-15T09:30:00Z",
436                                      "duration": "PT1H",
437                                      "randomizeStart": "PT1H"
438                                    },
439                                    "intervals": [
440                                      {
441                                        "id": 0,
442                                        "intervalPeriod": {
443                                          "start": "2023-06-15T09:30:00Z",
444                                          "duration": "PT1H",
445                                          "randomizeStart": "PT1H"
446                                        },
447                                        "payloads": [
448                                          {
449                                            "type": "PRICE",
450                                            "values": [
451                                              0.17
452                                            ]
453                                          }
454                                        ]
455                                      }
456                                    ]
457                                  }]"#;
458
459        let expected = Event {
460            id: EventId("object-999-foo".parse().unwrap()),
461            created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
462            modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
463            content: EventRequest {
464                program_id: ProgramId("object-999".parse().unwrap()),
465                event_name: Some("price event 11-18-2022".into()),
466                duration: Some(Duration::PT1H),
467                priority: Priority::MAX,
468                targets: Default::default(),
469                report_descriptors: None,
470                payload_descriptors: None,
471                interval_period: Some(IntervalPeriod {
472                    start: "2023-06-15T09:30:00Z".parse().unwrap(),
473                    duration: Some(Duration::PT1H),
474                    randomize_start: Some(Duration::PT1H),
475                }),
476                intervals: Some(vec![EventInterval {
477                    id: 0,
478                    interval_period: Some(IntervalPeriod {
479                        start: "2023-06-15T09:30:00Z".parse().unwrap(),
480                        duration: Some(Duration::PT1H),
481                        randomize_start: Some(Duration::PT1H),
482                    }),
483                    payloads: vec![EventValuesMap {
484                        value_type: EventType::Price,
485                        values: vec![Value::Number(0.17)],
486                    }],
487                }]),
488            },
489        };
490
491        assert_eq!(
492            serde_json::from_str::<Vec<Event>>(example).unwrap()[0],
493            expected
494        );
495    }
496
497    #[test]
498    fn test_currency() {
499        // deserialize
500        let example = r#"{"payloadType":"SIMPLE","currency":"EUR"}"#;
501
502        let expected = EventPayloadDescriptor {
503            payload_type: EventType::Simple,
504            units: None,
505            currency: Some(Currency::EUR),
506        };
507
508        assert_eq!(
509            serde_json::from_str::<EventPayloadDescriptor>(example).unwrap(),
510            expected
511        );
512
513        // round-trip
514        let source = EventPayloadDescriptor {
515            payload_type: EventType::Price,
516            units: Some(Unit::Volts),
517            currency: Some(Currency::USD),
518        };
519
520        let serialized = serde_json::to_string(&source).unwrap();
521
522        assert_eq!(
523            source,
524            serde_json::from_str::<EventPayloadDescriptor>(&serialized).unwrap()
525        );
526    }
527
528    #[test]
529    fn test_validate_value_positive() {
530        let input = r#"{"type":"SIMPLE","values":[1]}"#;
531        let expected = Ok(());
532        let actual = serde_json::from_str::<EventValuesMap>(input)
533            .unwrap()
534            .validate();
535        assert_eq!(actual, expected);
536    }
537
538    #[test]
539    fn validate_private_value() {
540        let input = r#"{"type":"WHATEVER","values":["Private types must accept all values"]}"#;
541        let expected = Ok(());
542        let actual = serde_json::from_str::<EventValuesMap>(input)
543            .unwrap()
544            .validate();
545        assert_eq!(actual, expected);
546
547        let input = r#"{"type":"WHATEVER","values":[1]}"#;
548        let expected = Ok(());
549        let actual = serde_json::from_str::<EventValuesMap>(input)
550            .unwrap()
551            .validate();
552        assert_eq!(actual, expected);
553
554        let input = r#"{"type":"WHATEVER","values":[{"x": 1, "y": 3}]}"#;
555        let expected = Ok(());
556        let actual = serde_json::from_str::<EventValuesMap>(input)
557            .unwrap()
558            .validate();
559        assert_eq!(actual, expected);
560    }
561
562    #[test]
563    fn test_validate_value_negative() {
564        let input = r#"{"type":"SIMPLE","values":["string"]}"#;
565        let expected = {
566            use std::collections::HashMap;
567            use validator::{ValidationErrors, ValidationErrorsKind};
568            let mut hash_map = HashMap::new();
569            let validation_errors_kind = {
570                let value = Value::String("string".to_string());
571                ValidationErrorsKind::Field(vec![validate_value_error(&EventType::Simple, &value)])
572            };
573            hash_map.insert(Cow::from("__all__"), validation_errors_kind);
574            Err(ValidationErrors(hash_map))
575        };
576        let actual = serde_json::from_str::<EventValuesMap>(input)
577            .unwrap()
578            .validate();
579        assert_eq!(actual, expected);
580    }
581}