From 0a43fa25f1e46524be39d9b8d9b0434786b27c05 Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Tue, 11 Jun 2024 11:52:41 +0200 Subject: [PATCH] Updates to client --- src/cli/main.rs | 19 +- src/client.rs | 530 +++++++++++++++--- src/error.rs | 10 +- src/generated/models/mod.rs | 3 - src/generated/models/notification.rs | 1 + src/generated/models/resource.rs | 1 + src/generated/models/subscription.rs | 1 + .../subscription_object_operations_inner.rs | 1 + src/generated/models/ven.rs | 1 + src/lib.rs | 4 +- src/wire/event.rs | 46 ++ src/wire/program.rs | 8 +- src/wire/report.rs | 34 +- 13 files changed, 575 insertions(+), 84 deletions(-) diff --git a/src/cli/main.rs b/src/cli/main.rs index 8ce3379..c8cb916 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,14 +1,19 @@ -use openadr::wire::program::ProgramContent; +use openadr::Target; #[tokio::main] async fn main() -> Result<(), Box> { let client = openadr::Client::new("http://localhost:3000/".try_into()?); - let created_program = client.create_program(ProgramContent::new("name")).await?; - let created_program_1 = client.create_program(ProgramContent::new("name1")).await?; - dbg!(created_program); - dbg!(created_program_1); - let _program = client.get_program_by_name("name").await?; - // let events = program.get_all_events().await?; + // let created_program = client.create_program(ProgramContent::new("name")).await?; + // let created_program_1 = client.create_program(ProgramContent::new("name1")).await?; + let program = client.get_program(Target::Program("name")).await?; + // let created_event = program + // .create_event(program.new_event().with_event_name("prices3").with_priority(0)) + // .await?; + let events = program.get_all_events().await?; + let reports = events[0].get_all_reports().await?; + // let event = program.get_event(Target::Event("prices3")).await?; + dbg!(events); + dbg!(reports); // let programs: Vec = client.get_all_programs()?; // let programs = client.get_programs(TargetLabel::ProgramName, &["name"])?; diff --git a/src/client.rs b/src/client.rs index c8d793f..ccc1b12 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,10 +2,76 @@ use std::sync::Arc; use url::Url; -use crate::wire::program::{ProgramContent, ProgramId}; +pub use crate::wire::{ + event::EventContent, + program::{ProgramContent, ProgramId}, +}; +use crate::wire::{ + event::EventObjectType, + report::{ReportContent, ReportObjectType}, + Event, Report, +}; use crate::wire::{target::TargetLabel, Program}; use crate::Result; +#[derive(Copy, Clone, Debug)] +pub enum Target<'a> { + Program(&'a str), + Programs(&'a [&'a str]), + Event(&'a str), + Events(&'a [&'a str]), + VEN(&'a str), + VENs(&'a [&'a str]), + Group(&'a str), + Groups(&'a [&'a str]), + Resource(&'a str), + Resources(&'a [&'a str]), + ServiceArea(&'a str), + ServiceAreas(&'a [&'a str]), + PowerServiceLocation(&'a str), + PowerServiceLocations(&'a [&'a str]), + Other(&'a str, &'a str), + Others(&'a str, &'a [&'a str]), +} + +impl<'a> Target<'a> { + pub fn target_label(&self) -> TargetLabel { + match self { + Target::Program(_) | Target::Programs(_) => TargetLabel::ProgramName, + Target::Event(_) | Target::Events(_) => TargetLabel::EventName, + Target::VEN(_) | Target::VENs(_) => TargetLabel::VENName, + Target::Group(_) | Target::Groups(_) => TargetLabel::Group, + Target::Resource(_) | Target::Resources(_) => TargetLabel::ResourceName, + Target::ServiceArea(_) | Target::ServiceAreas(_) => TargetLabel::ServiceArea, + Target::PowerServiceLocation(_) | Target::PowerServiceLocations(_) => { + TargetLabel::PowerServiceLocation + } + Target::Other(p, _) | Target::Others(p, _) => TargetLabel::Private(p.to_string()), + } + } + + pub fn target_values(&self) -> &[&str] { + match self { + Target::Program(v) => std::slice::from_ref(v), + Target::Programs(v) => v, + Target::Event(v) => std::slice::from_ref(v), + Target::Events(v) => v, + Target::VEN(v) => std::slice::from_ref(v), + Target::VENs(v) => v, + Target::Group(v) => std::slice::from_ref(v), + Target::Groups(v) => v, + Target::Resource(v) => std::slice::from_ref(v), + Target::Resources(v) => v, + Target::ServiceArea(v) => std::slice::from_ref(v), + Target::ServiceAreas(v) => v, + Target::PowerServiceLocation(v) => std::slice::from_ref(v), + Target::PowerServiceLocations(v) => v, + Target::Other(_, v) => std::slice::from_ref(v), + Target::Others(_, v) => v, + } + } +} + /// Client used for interaction with a VTN. /// /// Can be used to implement both, the VEN and the business logic @@ -13,24 +79,29 @@ pub struct Client { client_ref: Arc, } -#[derive(Debug)] pub struct ClientRef { client: reqwest::Client, base_url: url::Url, + default_page_size: usize, +} + +impl std::fmt::Debug for ClientRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ClientRef") + .field(&self.base_url.to_string()) + .finish() + } } impl ClientRef { - pub async fn get<'a, T: serde::de::DeserializeOwned>( - &self, - path: &str, - query: Option, + async fn request( + mut request: reqwest::RequestBuilder, + query: &[(&str, &str)], ) -> Result { - let url = self.base_url.join(path)?; - let mut request = self.client.get(url); - if let Some(query) = query { + request = request.header("Accept", "application/json"); + if !query.is_empty() { request = request.query(&query); } - request = request.header("Accept", "application/json"); let res = request.send().await?; // handle any errors returned by the server @@ -42,51 +113,42 @@ impl ClientRef { Ok(res.json().await?) } - pub async fn post<'a, S: serde::ser::Serialize, T: serde::de::DeserializeOwned>( + pub async fn get( &self, path: &str, - body: &S, + query: &[(&str, &str)], ) -> Result { let url = self.base_url.join(path)?; - let res = self.client.post(url).json(body).send().await?; - - // handle any errors returned by the server - if !res.status().is_success() { - let problem = res.json::().await?; - return Err(crate::Error::from(problem)); - } - - Ok(res.json().await?) + let request = self.client.get(url); + ClientRef::request(request, query).await } - pub async fn put<'a, S: serde::ser::Serialize, T: serde::de::DeserializeOwned>( + pub async fn post( &self, path: &str, body: &S, + query: &[(&str, &str)], ) -> Result { let url = self.base_url.join(path)?; - let res = self.client.put(url).json(body).send().await?; - - // handle any errors returned by the server - if !res.status().is_success() { - let problem = res.json::().await?; - return Err(crate::Error::from(problem)); - } - - Ok(res.json().await?) + let request = self.client.post(url).json(body); + ClientRef::request(request, query).await } - pub async fn delete(&self, path: &str) -> Result<()> { + pub async fn put( + &self, + path: &str, + body: &S, + query: &[(&str, &str)], + ) -> Result { let url = self.base_url.join(path)?; - let res = self.client.delete(url).send().await?; - - // handle any errors returned by the server - if !res.status().is_success() { - let problem = res.json::().await?; - return Err(crate::Error::from(problem)); - } + let request = self.client.put(url).json(body); + ClientRef::request(request, query).await + } - Ok(()) + pub async fn delete(&self, path: &str, query: &[(&str, &str)]) -> Result<()> { + let url = self.base_url.join(path)?; + let request = self.client.delete(url); + ClientRef::request(request, query).await } } @@ -98,13 +160,17 @@ impl Client { pub fn with_reqwest(base_url: Url, client: reqwest::Client) -> Client { Client { - client_ref: Arc::new(ClientRef { client, base_url }), + client_ref: Arc::new(ClientRef { + client, + base_url, + default_page_size: 50, + }), } } /// Create a new program on the VTN pub async fn create_program(&self, program_data: ProgramContent) -> Result { - let program = self.client_ref.post("programs", &program_data).await?; + let program = self.client_ref.post("programs", &program_data, &[]).await?; Ok(ProgramClient::from_program( self.client_ref.clone(), program, @@ -112,7 +178,7 @@ impl Client { } /// Get a list of programs from the VTN with the given query parameters - pub async fn get_programs( + async fn get_programs_req( &self, target_type: Option, targets: &[&str], @@ -136,24 +202,67 @@ impl Client { query.push(("limit", &limit_str)); // send request and return response - let programs: Vec = self.client_ref.get("programs", Some(&query)).await?; + let programs: Vec = self.client_ref.get("programs", &query).await?; Ok(programs .into_iter() .map(|program| ProgramClient::from_program(self.client_ref.clone(), program)) .collect()) } + /// Get a single program from the VTN that matches the given target + pub async fn get_program(&self, target: Target<'_>) -> Result { + let mut programs = self + .get_programs_req(Some(target.target_label()), target.target_values(), 0, 2) + .await?; + if programs.is_empty() { + Err(crate::Error::ObjectNotFound) + } else if programs.len() > 1 { + Err(crate::Error::DuplicateObject) + } else { + Ok(programs.remove(0)) + } + } + + /// Get a list of programs from the VTN with the given query parameters + pub async fn get_program_list(&self, target: Target<'_>) -> Result> { + let page_size = self.client_ref.default_page_size; + let mut programs = vec![]; + let mut page = 0; + loop { + let received = self + .get_programs_req( + Some(target.target_label()), + target.target_values(), + page * page_size, + page_size, + ) + .await?; + let received_all = received.len() < page_size; + for program in received { + programs.push(program); + } + + if received_all { + break; + } else { + page += 1; + } + } + + Ok(programs) + } + /// Get all programs from the VTN, trying to paginate whenever possible pub async fn get_all_programs(&self) -> Result> { - const PAGE_SIZE: usize = 50; + let page_size = self.client_ref.default_page_size; let mut programs = vec![]; let mut page = 0; loop { // TODO: this pagination should really depend on that the server indicated there are more results let received = self - .get_programs(None, &[], page * PAGE_SIZE, PAGE_SIZE) + .get_programs_req(None, &[], page * page_size, page_size) .await?; - let received_all = received.len() < PAGE_SIZE; + let received_all = received.len() < page_size; for program in received { programs.push(program); } @@ -170,23 +279,14 @@ impl Client { /// Get a program by name pub async fn get_program_by_name(&self, name: &str) -> Result { - let mut programs = self - .get_programs(Some(TargetLabel::ProgramName), &[name], 0, 2) - .await?; - if programs.is_empty() { - Err(crate::Error::ProgramNotFound) - } else if programs.len() > 1 { - Err(crate::Error::DuplicateProgram) - } else { - Ok(programs.remove(0)) - } + self.get_program(Target::Program(name)).await } /// Get a program by id pub async fn get_program_by_id(&self, id: &ProgramId) -> Result { let program = self .client_ref - .get(&format!("programs/{}", id.0), None::<()>) + .get(&format!("programs/{}", id.0), &[]) .await?; Ok(ProgramClient::from_program( @@ -234,7 +334,7 @@ impl ProgramClient { pub async fn update(&mut self) -> Result<()> { let res = self .client - .put(&format!("programs/{}", self.id()), &self.data.content) + .put(&format!("programs/{}", self.id()), &self.data.content, &[]) .await?; self.data = res; Ok(()) @@ -242,23 +342,47 @@ impl ProgramClient { /// Delete the program from the VTN pub async fn delete(self) -> Result<()> { - self.client.delete(&format!("programs/{}", self.id())).await + self.client + .delete(&format!("programs/{}", self.id()), &[]) + .await + } + + pub async fn create_event(&self, event_data: EventContent) -> Result { + if &event_data.program_id != self.id() { + return Err(crate::Error::InvalidParentObject); + } + let event = self.client.post("events", &event_data, &[]).await?; + Ok(EventClient::from_event(self.client.clone(), event)) } - pub async fn get_events( + pub fn new_event(&self) -> EventContent { + EventContent { + object_type: Some(EventObjectType::Event), + program_id: self.id().clone(), + event_name: None, + priority: None, + targets: None, + report_descriptors: None, + payload_descriptors: None, + interval_period: None, + intervals: vec![], + } + } + + async fn get_events_req( &self, target_type: Option, targets: &[&str], skip: usize, limit: usize, - ) -> Result> { + ) -> Result> { // convert query params let target_type_str = target_type.map(|t| t.to_string()); let skip_str = skip.to_string(); let limit_str = limit.to_string(); // insert into query params - let mut query = vec![]; + let mut query = vec![("programID", self.id().as_str())]; if let Some(target_type_ref) = &target_type_str { for target in targets { query.push(("targetValues", *target)); @@ -269,6 +393,284 @@ impl ProgramClient { query.push(("limit", &limit_str)); // send request and return response - self.client.get("programs", Some(&query)).await + let events: Vec = self.client.get("events", &query).await?; + Ok(events + .into_iter() + .map(|event| EventClient::from_event(self.client.clone(), event)) + .collect()) + } + + /// Get a single event from the VTN that matches the given target + pub async fn get_event(&self, target: Target<'_>) -> Result { + let mut events = self + .get_events_req(Some(target.target_label()), target.target_values(), 0, 2) + .await?; + if events.is_empty() { + Err(crate::Error::ObjectNotFound) + } else if events.len() > 1 { + Err(crate::Error::DuplicateObject) + } else { + Ok(events.remove(0)) + } + } + + /// Get a list of events from the VTN with the given query parameters + pub async fn get_event_list(&self, target: Target<'_>) -> Result> { + let page_size = self.client.default_page_size; + let mut events = vec![]; + let mut page = 0; + loop { + let received = self + .get_events_req( + Some(target.target_label()), + target.target_values(), + page * page_size, + page_size, + ) + .await?; + let received_all = received.len() < page_size; + for event in received { + events.push(event); + } + + if received_all { + break; + } else { + page += 1; + } + } + + Ok(events) + } + + /// Get all events from the VTN, trying to paginate whenever possible + pub async fn get_all_events(&self) -> Result> { + let page_size = self.client.default_page_size; + let mut events = vec![]; + let mut page = 0; + loop { + // TODO: this pagination should really depend on that the server indicated there are more results + let received = self + .get_events_req(None, &[], page * page_size, page_size) + .await?; + let received_all = received.len() < page_size; + for event in received { + events.push(event); + } + + if received_all { + break; + } else { + page += 1; + } + } + + Ok(events) + } +} + +#[derive(Debug)] +pub struct EventClient { + client: Arc, + data: Event, +} + +impl EventClient { + fn from_event(client: Arc, event: Event) -> EventClient { + EventClient { + client, + data: event, + } + } + + pub fn id(&self) -> &crate::wire::event::EventId { + &self.data.id + } + + pub fn created_date_time(&self) -> &chrono::DateTime { + &self.data.created_date_time + } + + pub fn modification_date_time(&self) -> &chrono::DateTime { + &self.data.modification_date_time + } + + pub fn data(&self) -> &EventContent { + &self.data.content + } + + pub fn data_mut(&mut self) -> &mut EventContent { + &mut self.data.content + } + + /// Save any modifications of the event to the VTN + pub async fn update(&mut self) -> Result<()> { + let res = self + .client + .put(&format!("events/{}", self.id()), &self.data.content, &[]) + .await?; + self.data = res; + Ok(()) + } + + /// Delete the event from the VTN + pub async fn delete(self) -> Result<()> { + self.client + .delete(&format!("events/{}", self.id()), &[]) + .await + } + + /// Create a new report object + pub fn new_report(&self) -> ReportContent { + ReportContent { + object_type: Some(ReportObjectType::Report), + program_id: self.data().program_id.clone(), + event_id: self.id().clone(), + client_name: "".to_string(), + report_name: None, + payload_descriptors: None, + resources: vec![], + } + } + + /// Create a new report for the event + pub async fn create_report(&self, report_data: ReportContent) -> Result { + if report_data.program_id != self.data().program_id { + return Err(crate::Error::InvalidParentObject); + } + + if &report_data.event_id != self.id() { + return Err(crate::Error::InvalidParentObject); + } + + let report = self.client.post("events", &report_data, &[]).await?; + Ok(ReportClient::from_report(self.client.clone(), report)) + } + + async fn get_reports_req( + &self, + client_name: Option<&str>, + skip: usize, + limit: usize, + ) -> Result> { + let skip_str = skip.to_string(); + let limit_str = limit.to_string(); + + let mut query = vec![ + ("programID", self.data().program_id.as_str()), + ("eventID", self.id().as_str()), + ("skip", &skip_str), + ("limit", &limit_str), + ]; + + if let Some(client_name) = client_name { + query.push(("clientName", client_name)); + } + + let reports: Vec = self.client.get("reports", &query).await?; + Ok(reports + .into_iter() + .map(|report| ReportClient::from_report(self.client.clone(), report)) + .collect()) + } + + /// Get all reports from the VTN for a specific client, trying to paginate whenever possible + pub async fn get_client_reports(&self, client_name: &str) -> Result> { + let page_size = self.client.default_page_size; + let mut reports = vec![]; + let mut page = 0; + loop { + let received = self + .get_reports_req(Some(client_name), page * page_size, page_size) + .await?; + let received_all = received.len() < page_size; + for report in received { + reports.push(report); + } + + if received_all { + break; + } else { + page += 1; + } + } + + Ok(reports) + } + + /// Get all reports from the VTN, trying to paginate whenever possible + pub async fn get_all_reports(&self) -> Result> { + let page_size = self.client.default_page_size; + let mut reports = vec![]; + let mut page = 0; + loop { + let received = self + .get_reports_req(None, page * page_size, page_size) + .await?; + let received_all = received.len() < page_size; + for report in received { + reports.push(report); + } + + if received_all { + break; + } else { + page += 1; + } + } + + Ok(reports) + } +} + +#[derive(Debug)] +pub struct ReportClient { + client: Arc, + data: Report, +} + +impl ReportClient { + fn from_report(client: Arc, report: Report) -> ReportClient { + ReportClient { + client, + data: report, + } + } + + pub fn id(&self) -> &crate::wire::report::ReportId { + &self.data.id + } + + pub fn created_date_time(&self) -> &chrono::DateTime { + &self.data.created_date_time + } + + pub fn modification_date_time(&self) -> &chrono::DateTime { + &self.data.modification_date_time + } + + pub fn data(&self) -> &ReportContent { + &self.data.content + } + + pub fn data_mut(&mut self) -> &mut ReportContent { + &mut self.data.content + } + + /// Save any modifications of the report to the VTN + pub async fn update(&mut self) -> Result<()> { + let res = self + .client + .put(&format!("reports/{}", self.id()), &self.data.content, &[]) + .await?; + self.data = res; + Ok(()) + } + + /// Delete the report from the VTN + pub async fn delete(self) -> Result<()> { + self.client + .delete(&format!("reports/{}", self.id()), &[]) + .await } } diff --git a/src/error.rs b/src/error.rs index ad3de0b..320c956 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,8 +5,9 @@ pub enum Error { Serde(serde_json::Error), UrlParseError(url::ParseError), Problem(crate::wire::Problem), - ProgramNotFound, - DuplicateProgram, + ObjectNotFound, + DuplicateObject, + InvalidParentObject, } impl From for Error { @@ -40,8 +41,9 @@ impl std::fmt::Display for Error { Error::Serde(err) => write!(f, "Serde error: {}", err), Error::UrlParseError(err) => write!(f, "URL parse error: {}", err), Error::Problem(err) => write!(f, "OpenADR Problem: {:?}", err), - Error::ProgramNotFound => write!(f, "Program not found"), - Error::DuplicateProgram => write!(f, "Found more than one program with the same name"), + Error::ObjectNotFound => write!(f, "Object not found"), + Error::DuplicateObject => write!(f, "Found more than one object matching the filter"), + Error::InvalidParentObject => write!(f, "Invalid parent object"), } } } diff --git a/src/generated/models/mod.rs b/src/generated/models/mod.rs index 7a39195..d602cac 100644 --- a/src/generated/models/mod.rs +++ b/src/generated/models/mod.rs @@ -1,10 +1,7 @@ -pub use self::notification::Notification; pub use self::notification_object::NotificationObject; pub use self::object_types::ObjectTypes; pub use self::resource::Resource; -pub use self::subscription::Subscription; pub use self::subscription_object_operations_inner::SubscriptionObjectOperationsInner; -pub use self::ven::Ven; mod notification; mod notification_object; diff --git a/src/generated/models/notification.rs b/src/generated/models/notification.rs index 2cfc10f..119f2eb 100644 --- a/src/generated/models/notification.rs +++ b/src/generated/models/notification.rs @@ -32,6 +32,7 @@ pub struct Notification { impl Notification { /// VTN generated object included in request to subscription callbackUrl. + #[allow(dead_code)] pub fn new( object_type: ObjectTypes, operation: Operation, diff --git a/src/generated/models/resource.rs b/src/generated/models/resource.rs index 2e4aa2d..77b8964 100644 --- a/src/generated/models/resource.rs +++ b/src/generated/models/resource.rs @@ -47,6 +47,7 @@ pub struct Resource { impl Resource { /// A resource is an energy device or system subject to control by a VEN. + #[allow(dead_code)] pub fn new(resource_name: String) -> Resource { Resource { id: None, diff --git a/src/generated/models/subscription.rs b/src/generated/models/subscription.rs index 95eaf51..5f13899 100644 --- a/src/generated/models/subscription.rs +++ b/src/generated/models/subscription.rs @@ -48,6 +48,7 @@ pub struct Subscription { impl Subscription { /// An object created by a client to receive notification of operations on objects. Clients may subscribe to be notified when a type of object is created, updated, or deleted. + #[allow(dead_code)] pub fn new( client_name: String, program_id: String, diff --git a/src/generated/models/subscription_object_operations_inner.rs b/src/generated/models/subscription_object_operations_inner.rs index bc6be41..42dbedf 100644 --- a/src/generated/models/subscription_object_operations_inner.rs +++ b/src/generated/models/subscription_object_operations_inner.rs @@ -31,6 +31,7 @@ pub struct SubscriptionObjectOperationsInner { impl SubscriptionObjectOperationsInner { /// object type, operations, and callbackUrl. + #[allow(dead_code)] pub fn new( objects: Vec, operations: Vec, diff --git a/src/generated/models/ven.rs b/src/generated/models/ven.rs index 05c1ded..6ea964e 100644 --- a/src/generated/models/ven.rs +++ b/src/generated/models/ven.rs @@ -49,6 +49,7 @@ pub struct Ven { impl Ven { /// Ven represents a client with the ven role. + #[allow(dead_code)] pub fn new(ven_name: String) -> Ven { Ven { id: None, diff --git a/src/lib.rs b/src/lib.rs index b1afe9b..cc6a5b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,8 @@ mod client; mod error; -pub mod generated; +mod generated; pub mod wire; -pub use client::Client; +pub use client::*; pub use error::*; diff --git a/src/wire/event.rs b/src/wire/event.rs index 8205129..b56e894 100644 --- a/src/wire/event.rs +++ b/src/wire/event.rs @@ -56,6 +56,46 @@ pub struct EventContent { pub intervals: Vec, } +impl EventContent { + 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(mut self, priority: u32) -> Self { + self.priority = Some(priority); + self + } + + pub fn with_targets(mut self, targets: TargetMap) -> Self { + self.targets = Some(targets); + self + } + + pub fn with_report_descriptors(mut self, report_descriptors: Vec) -> Self { + self.report_descriptors = Some(report_descriptors); + self + } + + pub fn with_payload_descriptors( + mut self, + payload_descriptors: Vec, + ) -> 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) -> Self { + self.intervals = intervals; + self + } +} + impl Event { pub fn new(content: EventContent) -> Self { Self { @@ -84,6 +124,12 @@ impl Display for EventId { } } +impl EventId { + pub fn as_str(&self) -> &str { + &self.0 + } +} + /// Used as discriminator, e.g. notification.object #[derive( Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, diff --git a/src/wire/program.rs b/src/wire/program.rs index f34ed8c..bb82f22 100644 --- a/src/wire/program.rs +++ b/src/wire/program.rs @@ -95,7 +95,7 @@ pub struct ProgramContent { impl ProgramContent { pub fn new(name: impl ToString) -> ProgramContent { ProgramContent { - object_type: Default::default(), + object_type: Some(ProgramObjectType::Program), program_name: name.to_string(), program_long_name: Default::default(), retailer_name: Default::default(), @@ -131,6 +131,12 @@ impl Display for ProgramId { } } +impl ProgramId { + pub fn as_str(&self) -> &str { + &self.0 + } +} + /// Used as discriminator, e.g. notification.object #[derive( Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, diff --git a/src/wire/report.rs b/src/wire/report.rs index 172d6c7..508099e 100644 --- a/src/wire/report.rs +++ b/src/wire/report.rs @@ -34,7 +34,7 @@ pub struct Report { #[serde(rename_all = "camelCase")] pub struct ReportContent { /// Used as discriminator, e.g. notification.object - pub object_type: Option, + pub object_type: Option, // FIXME Must likely bei EITHER a programID OR an eventID /// ID attribute of the program object this report is associated with. #[serde(rename = "programID")] @@ -55,6 +55,28 @@ pub struct ReportContent { pub resources: Vec, } +impl ReportContent { + pub fn with_client_name(mut self, client_name: &str) -> Self { + self.client_name = client_name.to_string(); + self + } + + pub fn with_name(mut self, name: &str) -> Self { + self.report_name = Some(name.to_string()); + self + } + + pub fn with_payload_descriptors(mut self, descriptors: Vec) -> Self { + self.payload_descriptors = Some(descriptors); + self + } + + pub fn with_resources(mut self, resources: Vec) -> Self { + self.resources = resources; + self + } +} + impl Report { pub fn new(content: ReportContent) -> Self { Self { @@ -77,6 +99,12 @@ impl Report { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, Hash, Eq)] pub struct ReportId(pub String); +impl ReportId { + pub fn as_str(&self) -> &str { + &self.0 + } +} + impl Display for ReportId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) @@ -88,7 +116,7 @@ impl Display for ReportId { Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum ObjectType { +pub enum ReportObjectType { #[default] Report, } @@ -411,7 +439,7 @@ mod tests { created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(), modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(), content: ReportContent { - object_type: Some(ObjectType::Report), + object_type: Some(ReportObjectType::Report), program_id: ProgramId("object-999".into()), event_id: EventId("object-999".into()), client_name: "VEN-999".into(),