use crate::{
Duration, Identifier, IdentifierError, Unit, interval::IntervalPeriod, program::ProgramId,
report::ReportDescriptor, target::Target, values_map::Value,
};
use chrono::{DateTime, Utc};
use iso_currency::Currency;
use serde::{Deserialize, Serialize};
use serde_with::{DefaultOnNull, serde_as, skip_serializing_none};
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use validator::{Validate, ValidationError};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct Event {
pub id: EventId,
#[serde(with = "crate::serde_rfc3339")]
pub created_date_time: DateTime<Utc>,
#[serde(with = "crate::serde_rfc3339")]
pub modification_date_time: DateTime<Utc>,
#[serde(flatten)]
#[validate(nested)]
pub content: EventRequest,
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase", tag = "objectType", rename = "EVENT")]
pub struct EventRequest {
#[serde(rename = "programID")]
pub program_id: ProgramId,
pub event_name: Option<String>,
pub duration: Option<Duration>,
pub priority: Priority,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnNull")]
pub targets: Vec<Target>,
pub report_descriptors: Option<Vec<ReportDescriptor>>,
pub payload_descriptors: Option<Vec<EventPayloadDescriptor>>,
pub interval_period: Option<IntervalPeriod>,
#[validate(nested)]
pub intervals: Option<Vec<EventInterval>>,
}
impl EventRequest {
pub fn new(program_id: ProgramId) -> Self {
Self {
program_id,
event_name: None,
duration: None,
priority: Priority::UNSPECIFIED,
targets: vec![],
report_descriptors: None,
payload_descriptors: None,
interval_period: None,
intervals: None,
}
}
pub fn with_event_name(mut self, event_name: impl ToString) -> Self {
self.event_name = Some(event_name.to_string());
self
}
pub fn with_priority(self, priority: Priority) -> Self {
Self { priority, ..self }
}
pub fn with_targets(mut self, targets: Vec<Target>) -> Self {
self.targets = targets;
self
}
pub fn with_report_descriptors(mut self, report_descriptors: Vec<ReportDescriptor>) -> Self {
self.report_descriptors = Some(report_descriptors);
self
}
pub fn with_payload_descriptors(
mut self,
payload_descriptors: Vec<EventPayloadDescriptor>,
) -> Self {
self.payload_descriptors = Some(payload_descriptors);
self
}
pub fn with_interval_period(mut self, interval_period: IntervalPeriod) -> Self {
self.interval_period = Some(interval_period);
self
}
pub fn with_intervals(mut self, intervals: Vec<EventInterval>) -> Self {
self.intervals = Some(intervals);
self
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
pub struct EventId(pub(crate) Identifier);
impl Display for EventId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl EventId {
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl FromStr for EventId {
type Err = IdentifierError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Priority(Option<u32>);
impl Priority {
pub const UNSPECIFIED: Self = Self(None);
pub const MAX: Self = Self(Some(0));
pub const MIN: Self = Self::UNSPECIFIED;
pub const fn new(val: u32) -> Self {
Self(Some(val))
}
}
impl PartialOrd for Priority {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Priority {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (self.0, other.0) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(s), Some(o)) => s.cmp(&o).reverse(),
}
}
}
impl From<Option<i64>> for Priority {
fn from(value: Option<i64>) -> Self {
Self(value.and_then(|i| i.unsigned_abs().try_into().ok()))
}
}
impl From<Priority> for Option<i64> {
fn from(value: Priority) -> Self {
value.0.map(|u| u.into())
}
}
#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EventPayloadDescriptor {
pub payload_type: EventType,
pub units: Option<Unit>,
pub currency: Option<Currency>,
}
impl EventPayloadDescriptor {
pub fn new(payload_type: EventType) -> Self {
Self {
payload_type,
units: None,
currency: None,
}
}
}
#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct EventInterval {
pub id: i32,
pub interval_period: Option<IntervalPeriod>,
#[validate(length(min = 1))]
pub payloads: Vec<EventValuesMap>,
}
impl EventInterval {
pub fn new(id: i32, payloads: Vec<EventValuesMap>) -> Self {
Self {
id,
interval_period: None,
payloads,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_payload"))]
pub struct EventValuesMap {
#[serde(rename = "type")]
pub value_type: EventType,
pub values: Vec<Value>,
}
fn validate_payload(payload: &EventValuesMap) -> Result<(), ValidationError> {
for value in &payload.values {
validate_value(&payload.value_type, value)?
}
Ok(())
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EventType {
Simple,
Price,
ChargeStateSetpoint,
DispatchSetpoint,
DispatchSetpointRelative,
ControlSetpoint,
ExportPrice,
#[serde(rename = "GHG")]
GHG,
Curve,
#[serde(rename = "OLS")]
OLS,
ImportCapacitySubscription,
ImportCapacityReservation,
ImportCapacityReservationFee,
ImportCapacityAvailable,
ImportCapacityAvailablePrice,
ExportCapacitySubscription,
ExportCapacityReservation,
ExportCapacityReservationFee,
ExportCapacityAvailable,
ExportCapacityAvailablePrice,
ImportCapacityLimit,
ExportCapacityLimit,
AlertGridEmergency,
AlertBlackStart,
AlertPossibleOutage,
AlertFlexAlert,
AlertFire,
AlertFreezing,
AlertWind,
AlertTsunami,
AlertAirQuality,
AlertOther,
#[serde(rename = "CTA2045_REBOOT")]
CTA2045Reboot,
#[serde(rename = "CTA2045_SET_OVERRIDE_STATUS")]
CTA2045SetOverrideStatus,
#[serde(untagged)]
#[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
Private(String),
}
fn validate_value(value_type: &EventType, value: &Value) -> Result<(), ValidationError> {
match (value_type, value) {
(EventType::Simple, Value::Integer(_)) => Ok(()), (EventType::Price, Value::Number(_)) => Ok(()), (EventType::ChargeStateSetpoint, Value::Number(_)) => Ok(()),
(EventType::DispatchSetpoint, Value::Number(_)) => Ok(()), (EventType::DispatchSetpointRelative, Value::Number(_)) => Ok(()), (EventType::ControlSetpoint, _) => Ok(()), (EventType::ExportPrice, Value::Number(_)) => Ok(()), (EventType::GHG, Value::Number(_)) => Ok(()), (EventType::Curve, Value::Point(_)) => Ok(()), (EventType::OLS, Value::Number(_)) => Ok(()), (EventType::ImportCapacitySubscription, Value::Number(_)) => Ok(()), (EventType::ImportCapacityReservation, Value::Number(_)) => Ok(()), (EventType::ImportCapacityReservationFee, Value::Number(_)) => Ok(()), (EventType::ImportCapacityAvailable, Value::Number(_)) => Ok(()), (EventType::ImportCapacityAvailablePrice, Value::Number(_)) => Ok(()), (EventType::ExportCapacitySubscription, Value::Number(_)) => Ok(()), (EventType::ExportCapacityReservation, Value::Number(_)) => Ok(()), (EventType::ExportCapacityReservationFee, Value::Number(_)) => Ok(()), (EventType::ExportCapacityAvailable, Value::Number(_)) => Ok(()), (EventType::ExportCapacityAvailablePrice, Value::Number(_)) => Ok(()), (EventType::ImportCapacityLimit, Value::Number(_)) => Ok(()), (EventType::ExportCapacityLimit, Value::Number(_)) => Ok(()), (EventType::AlertGridEmergency, Value::String(_)) => Ok(()), (EventType::AlertBlackStart, Value::String(_)) => Ok(()), (EventType::AlertPossibleOutage, Value::String(_)) => Ok(()), (EventType::AlertFlexAlert, Value::String(_)) => Ok(()), (EventType::AlertFire, Value::String(_)) => Ok(()), (EventType::AlertFreezing, Value::String(_)) => Ok(()), (EventType::AlertWind, Value::String(_)) => Ok(()), (EventType::AlertTsunami, Value::String(_)) => Ok(()), (EventType::AlertAirQuality, Value::String(_)) => Ok(()), (EventType::AlertOther, Value::String(_)) => Ok(()), (EventType::CTA2045Reboot, Value::Integer(_)) => Ok(()), (EventType::CTA2045SetOverrideStatus, Value::Integer(_)) => Ok(()), (EventType::Private(_), _) => Ok(()), (value_type, value) => Err(validate_value_error(value_type, value)),
}
}
fn validate_value_error(value_type: &EventType, value: &Value) -> ValidationError {
let cow = format!("value {value:?} must match the given type {value_type:?}").into();
ValidationError::new("values must match the given type").with_message(cow)
}
#[cfg(test)]
mod tests {
use crate::{Duration, values_map::Value};
use std::borrow::Cow;
use super::*;
#[test]
fn priority_order() {
assert_eq!(Priority::MAX, Priority::new(0));
assert!(Priority::MAX > Priority::MIN);
assert_eq!(Priority::MIN, Priority::UNSPECIFIED);
assert!(Priority::new(5) > Priority::UNSPECIFIED);
assert!(Priority::new(5) > Priority::new(6));
assert!(Priority::new(u32::MAX) > Priority::UNSPECIFIED);
}
#[test]
fn test_event_serialization() {
assert_eq!(
serde_json::to_string(&EventType::Simple).unwrap(),
r#""SIMPLE""#
);
assert_eq!(
serde_json::to_string(&EventType::CTA2045Reboot).unwrap(),
r#""CTA2045_REBOOT""#
);
assert_eq!(
serde_json::from_str::<EventType>(r#""GHG""#).unwrap(),
EventType::GHG
);
assert_eq!(
serde_json::from_str::<EventType>(r#""something else""#).unwrap(),
EventType::Private(String::from("something else"))
);
assert!(serde_json::from_str::<EventType>(r#""""#).is_err());
assert!(serde_json::from_str::<EventType>(&format!("\"{}\"", "x".repeat(129))).is_err());
}
#[test]
fn parse_minimal() {
let example = r#"{"programID":"foo"}"#;
assert_eq!(
serde_json::from_str::<EventRequest>(example).unwrap(),
EventRequest {
program_id: ProgramId("foo".parse().unwrap()),
event_name: None,
duration: None,
priority: Priority::MIN,
targets: vec![],
report_descriptors: None,
payload_descriptors: None,
interval_period: None,
intervals: None,
}
);
}
#[test]
fn example_parses() {
let example = r#"[{
"id": "object-999-foo",
"createdDateTime": "2023-06-15T09:30:00Z",
"modificationDateTime": "2023-06-15T09:30:00Z",
"objectType": "EVENT",
"programID": "object-999",
"eventName": "price event 11-18-2022",
"duration": "PT1H",
"priority": 0,
"targets": null,
"reportDescriptors": null,
"payloadDescriptors": null,
"intervalPeriod": {
"start": "2023-06-15T09:30:00Z",
"duration": "PT1H",
"randomizeStart": "PT1H"
},
"intervals": [
{
"id": 0,
"intervalPeriod": {
"start": "2023-06-15T09:30:00Z",
"duration": "PT1H",
"randomizeStart": "PT1H"
},
"payloads": [
{
"type": "PRICE",
"values": [
0.17
]
}
]
}
]
}]"#;
let expected = Event {
id: EventId("object-999-foo".parse().unwrap()),
created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
content: EventRequest {
program_id: ProgramId("object-999".parse().unwrap()),
event_name: Some("price event 11-18-2022".into()),
duration: Some(Duration::PT1H),
priority: Priority::MAX,
targets: Default::default(),
report_descriptors: None,
payload_descriptors: None,
interval_period: Some(IntervalPeriod {
start: "2023-06-15T09:30:00Z".parse().unwrap(),
duration: Some(Duration::PT1H),
randomize_start: Some(Duration::PT1H),
}),
intervals: Some(vec![EventInterval {
id: 0,
interval_period: Some(IntervalPeriod {
start: "2023-06-15T09:30:00Z".parse().unwrap(),
duration: Some(Duration::PT1H),
randomize_start: Some(Duration::PT1H),
}),
payloads: vec![EventValuesMap {
value_type: EventType::Price,
values: vec![Value::Number(0.17)],
}],
}]),
},
};
assert_eq!(
serde_json::from_str::<Vec<Event>>(example).unwrap()[0],
expected
);
}
#[test]
fn test_currency() {
let example = r#"{"payloadType":"SIMPLE","currency":"EUR"}"#;
let expected = EventPayloadDescriptor {
payload_type: EventType::Simple,
units: None,
currency: Some(Currency::EUR),
};
assert_eq!(
serde_json::from_str::<EventPayloadDescriptor>(example).unwrap(),
expected
);
let source = EventPayloadDescriptor {
payload_type: EventType::Price,
units: Some(Unit::Volts),
currency: Some(Currency::USD),
};
let serialized = serde_json::to_string(&source).unwrap();
assert_eq!(
source,
serde_json::from_str::<EventPayloadDescriptor>(&serialized).unwrap()
);
}
#[test]
fn test_validate_value_positive() {
let input = r#"{"type":"SIMPLE","values":[1]}"#;
let expected = Ok(());
let actual = serde_json::from_str::<EventValuesMap>(input)
.unwrap()
.validate();
assert_eq!(actual, expected);
}
#[test]
fn validate_private_value() {
let input = r#"{"type":"WHATEVER","values":["Private types must accept all values"]}"#;
let expected = Ok(());
let actual = serde_json::from_str::<EventValuesMap>(input)
.unwrap()
.validate();
assert_eq!(actual, expected);
let input = r#"{"type":"WHATEVER","values":[1]}"#;
let expected = Ok(());
let actual = serde_json::from_str::<EventValuesMap>(input)
.unwrap()
.validate();
assert_eq!(actual, expected);
let input = r#"{"type":"WHATEVER","values":[{"x": 1, "y": 3}]}"#;
let expected = Ok(());
let actual = serde_json::from_str::<EventValuesMap>(input)
.unwrap()
.validate();
assert_eq!(actual, expected);
}
#[test]
fn test_validate_value_negative() {
let input = r#"{"type":"SIMPLE","values":["string"]}"#;
let expected = {
use std::collections::HashMap;
use validator::{ValidationErrors, ValidationErrorsKind};
let mut hash_map = HashMap::new();
let validation_errors_kind = {
let value = Value::String("string".to_string());
ValidationErrorsKind::Field(vec![validate_value_error(&EventType::Simple, &value)])
};
hash_map.insert(Cow::from("__all__"), validation_errors_kind);
Err(ValidationErrors(hash_map))
};
let actual = serde_json::from_str::<EventValuesMap>(input)
.unwrap()
.validate();
assert_eq!(actual, expected);
}
}