use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none};
use validator::Validate;
use crate::{
ClientId, Event, Identifier, IdentifierError, ObjectType, Program, Report, Ven,
program::ProgramId, resource::Resource, resource_group::ResourceGroup,
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct Subscription {
pub id: SubscriptionId,
#[serde(with = "crate::serde_rfc3339")]
pub created_date_time: DateTime<Utc>,
#[serde(with = "crate::serde_rfc3339")]
pub modification_date_time: DateTime<Utc>,
pub client_id: ClientId,
#[serde(flatten)]
#[validate(nested)]
pub content: SubscriptionRequest,
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct SubscriptionRequest {
#[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
pub client_name: String,
#[serde(rename = "programID")]
pub program_id: Option<ProgramId>,
#[validate(length(min = 1, max = 15))]
pub object_operations: Vec<SubscriptionObjectOperation>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct SubscriptionObjectOperation {
pub objects: Vec<ObjectType>,
pub operations: Vec<Operation>,
#[serde(default)]
pub mechanism: NotificationMechanism,
pub callback_url: Option<String>,
pub bearer_token: Option<String>,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Operation {
Create,
Update,
Delete,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum NotificationMechanism {
#[default]
Webhook,
Websocket,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
pub struct SubscriptionId(pub(crate) Identifier);
impl Display for SubscriptionId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl SubscriptionId {
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl FromStr for SubscriptionId {
type Err = IdentifierError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct Notification {
pub id: Identifier,
pub operation: Operation,
#[serde(flatten)]
pub object: AnyObject,
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct MqttPushNotification {
#[serde(rename = "ID")]
pub id: Identifier,
#[serde(rename = "notificationID")]
pub notification_id: Identifier,
pub object_type: ObjectType,
pub operation: Operation,
#[serde(with = "crate::serde_rfc3339")]
pub notification_date_time: DateTime<Utc>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "objectType", content = "object", rename_all = "UPPERCASE")]
pub enum AnyObject {
Program(Program),
Report(Report),
Event(Event),
Subscription(Subscription),
Ven(Ven),
Resource(Resource),
ResourceGroup(ResourceGroup),
}
impl AnyObject {
pub fn id(&self) -> Identifier {
match self {
AnyObject::Program(program) => program.id.0.clone(),
AnyObject::Report(report) => report.id.0.clone(),
AnyObject::Event(event) => event.id.0.clone(),
AnyObject::Subscription(subscription) => subscription.id.0.clone(),
AnyObject::Ven(ven) => ven.id.0.clone(),
AnyObject::Resource(resource) => resource.id.0.clone(),
AnyObject::ResourceGroup(resource_group) => resource_group.id.0.clone(),
}
}
pub fn kind(&self) -> ObjectType {
match self {
AnyObject::Program(_) => ObjectType::Program,
AnyObject::Report(_) => ObjectType::Report,
AnyObject::Event(_) => ObjectType::Event,
AnyObject::Subscription(_) => ObjectType::Subscription,
AnyObject::Ven(_) => ObjectType::Ven,
AnyObject::Resource(_) => ObjectType::Resource,
AnyObject::ResourceGroup(_) => ObjectType::ResourceGroup,
}
}
}
#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
pub struct NotifiersResponse {
pub websocket: bool,
pub mqtt: Option<MqttNotifierBindingObject>,
pub push_mqtt: Option<MqttNotifierBindingObject>,
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct MqttNotifierBindingObject {
#[serde(rename = "URIS")]
pub uris: Vec<String>,
pub serialization: SerializationType,
pub authentication: MqttNotifierAuthentication,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE", rename_all_fields = "camelCase")]
#[serde(tag = "method")]
pub enum MqttNotifierAuthentication {
Anonymous,
Oauth2BearerToken {
username: String,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum SerializationType {
Json,
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotifierTopicsResponse {
pub topics: NotifierOperationsTopics,
}
#[skip_serializing_none]
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct NotifierOperationsTopics {
pub create: Option<String>,
pub update: String,
pub delete: String,
pub all: Option<String>,
}
#[cfg(test)]
mod tests {
use crate::program::ProgramRequest;
use super::*;
#[test]
fn parse_subscription_request() {
let example = r#"{
"clientName": "myClient",
"programID": "44",
"objectOperations": [
{
"callbackUrl": "https://myserver.com/event_callbacks",
"operations": [
"CREATE",
"UPDATE"
],
"objects": [
"EVENT"
]
},
{
"callbackUrl": "https://myserver.com/program_callbacks",
"operations": [
"CREATE",
"UPDATE"
],
"objects": [
"PROGRAM"
]
}
]
}"#;
assert_eq!(
serde_json::from_str::<SubscriptionRequest>(example).unwrap(),
SubscriptionRequest {
client_name: "myClient".to_owned(),
program_id: Some("44".parse().unwrap()),
object_operations: vec![
SubscriptionObjectOperation {
objects: vec![ObjectType::Event],
operations: vec![Operation::Create, Operation::Update],
mechanism: NotificationMechanism::Webhook,
callback_url: Some("https://myserver.com/event_callbacks".to_owned()),
bearer_token: None,
},
SubscriptionObjectOperation {
objects: vec![ObjectType::Program],
operations: vec![Operation::Create, Operation::Update],
mechanism: NotificationMechanism::Webhook,
callback_url: Some("https://myserver.com/program_callbacks".to_owned()),
bearer_token: None,
}
],
}
);
}
#[test]
fn parse_notification() {
let example = r#"{
"id": "100",
"objectType": "PROGRAM",
"operation": "UPDATE",
"object": {
"bindingEvents": false,
"createdDateTime": "2023-06-15T15:51:29.000Z",
"modificationDateTime": "2023-06-15T15:51:29.000Z",
"id": "0",
"localPrice": false,
"objectType": "PROGRAM",
"programName": "myProgram"
}
}"#;
assert_eq!(
serde_json::from_str::<Notification>(example).unwrap(),
Notification {
id: "100".parse().unwrap(),
operation: Operation::Update,
object: AnyObject::Program(Program {
id: "0".parse().unwrap(),
created_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
modification_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
content: ProgramRequest {
program_name: "myProgram".to_owned(),
interval_period: None,
program_descriptions: None,
payload_descriptors: None,
attributes: None,
targets: vec![],
}
}),
}
);
}
}