diff --git a/crates/core/c8y_api/src/smartrest/alarm.rs b/crates/core/c8y_api/src/smartrest/alarm.rs index 093c177b0a..90505888e8 100644 --- a/crates/core/c8y_api/src/smartrest/alarm.rs +++ b/crates/core/c8y_api/src/smartrest/alarm.rs @@ -2,7 +2,7 @@ use crate::json_c8y::AlarmSeverity; use crate::json_c8y::C8yAlarm; use time::format_description::well_known::Rfc3339; -use super::message::SmartrestPayload; +use super::payload::SmartrestPayload; /// Serialize C8yAlarm to SmartREST message pub fn serialize_alarm(c8y_alarm: &C8yAlarm) -> Result { diff --git a/crates/core/c8y_api/src/smartrest/inventory.rs b/crates/core/c8y_api/src/smartrest/inventory.rs index 76092eacf0..e88ceadfb4 100644 --- a/crates/core/c8y_api/src/smartrest/inventory.rs +++ b/crates/core/c8y_api/src/smartrest/inventory.rs @@ -16,7 +16,7 @@ use mqtt_channel::MqttMessage; use std::time::Duration; use tedge_config::TopicPrefix; -use super::message::SmartrestPayload; +use super::payload::SmartrestPayload; /// Create a SmartREST message for creating a child device under the given ancestors. /// diff --git a/crates/core/c8y_api/src/smartrest/message.rs b/crates/core/c8y_api/src/smartrest/message.rs index 4b9b14c99a..b466789ed2 100644 --- a/crates/core/c8y_api/src/smartrest/message.rs +++ b/crates/core/c8y_api/src/smartrest/message.rs @@ -1,101 +1,9 @@ -use std::fmt::Display; - -use serde::Serialize; use tracing::error; // The actual limit defined by c8y is 16184 including header and body. // We need a buffer, therefore here sets smaller size than the actual limit. pub const MAX_PAYLOAD_LIMIT_IN_BYTES: usize = 16000; -/// A Cumulocity SmartREST message payload. -/// -/// A SmartREST message is either an HTTP request or an MQTT message that contains SmartREST topic and payload. The -/// payload is a CSV-like format that is backed by templates, either static or registered by the user. This struct -/// represents that payload, and should be used as such in SmartREST 1.0 and 2.0 message implementations. -/// -/// # Example -/// -/// ```text -/// 503,c8y_Command,"This is a ""Set operation to SUCCESSFUL (503)"" message payload; it has a template id (503), -/// operation fragment (c8y_Command), and optional parameters." -/// ``` -/// -/// # Reference -/// -/// - https://cumulocity.com/docs/smartrest/smartrest-introduction/ -#[derive(Debug, Clone, PartialEq, Eq)] -// TODO: pub(crate) for now so it can be constructed manually in serializer::succeed_operation, need to figure out a -// good API -pub struct SmartrestPayload(pub(crate) String); - -impl SmartrestPayload { - /// Creates a payload that consists of a single record. - /// - /// Doesn't trim any fields, so if the resulting payload is above size limit, returns an error. - pub fn from_fields( - fields: impl IntoIterator, - ) -> Result - where - T: AsRef + AsRef<[u8]>, - { - let payload = super::csv::fields_to_csv_string(fields); - - if payload.len() > super::message::MAX_PAYLOAD_LIMIT_IN_BYTES { - return Err(SmartrestPayloadError::TooLarge(payload.len())); - } - - Ok(Self(payload)) - } - - pub fn serialize(record: S) -> Result { - let mut wtr = csv::Writer::from_writer(vec![]); - wtr.serialize(record)?; - let mut vec = wtr.into_inner().unwrap(); - // remove newline character - vec.pop(); - let payload = String::from_utf8(vec) - .expect("TODO: should SmartrestPayload wrap a string or a byte array?"); - - if payload.len() > super::message::MAX_PAYLOAD_LIMIT_IN_BYTES { - return Err(SmartrestPayloadError::TooLarge(payload.len())); - } - - Ok(Self(payload)) - } - - /// Returns a string slice view of the payload. - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - /// Moves the underlying `String` out of the payload. - pub fn into_inner(self) -> String { - self.0 - } -} - -/// Errors that can occur when trying to create a SmartREST payload. -#[derive(Debug, thiserror::Error)] -pub enum SmartrestPayloadError { - #[error("Payload size ({0}) would be bigger than the limit ({MAX_PAYLOAD_LIMIT_IN_BYTES})")] - TooLarge(usize), - - #[error("Could not serialize the record")] - SerializeError(#[from] csv::Error), -} - -impl From for Vec { - fn from(value: SmartrestPayload) -> Self { - value.0.into_bytes() - } -} - -impl Display for SmartrestPayload { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - /// Extract the Device ID from the SmartREST payload. /// /// ``` diff --git a/crates/core/c8y_api/src/smartrest/mod.rs b/crates/core/c8y_api/src/smartrest/mod.rs index 0deece1677..d7278ae3bc 100644 --- a/crates/core/c8y_api/src/smartrest/mod.rs +++ b/crates/core/c8y_api/src/smartrest/mod.rs @@ -4,6 +4,7 @@ pub mod error; pub mod inventory; pub mod message; pub mod operations; +pub mod payload; pub mod smartrest_deserializer; pub mod smartrest_serializer; pub mod topic; diff --git a/crates/core/c8y_api/src/smartrest/operations.rs b/crates/core/c8y_api/src/smartrest/operations.rs index ff5ce9ac5e..ea9975d2b6 100644 --- a/crates/core/c8y_api/src/smartrest/operations.rs +++ b/crates/core/c8y_api/src/smartrest/operations.rs @@ -10,7 +10,7 @@ use std::path::PathBuf; use std::time::Duration; use tracing::warn; -use super::message::SmartrestPayload; +use super::payload::SmartrestPayload; const DEFAULT_GRACEFUL_TIMEOUT: Duration = Duration::from_secs(3600); const DEFAULT_FORCEFUL_TIMEOUT: Duration = Duration::from_secs(60); diff --git a/crates/core/c8y_api/src/smartrest/payload.rs b/crates/core/c8y_api/src/smartrest/payload.rs new file mode 100644 index 0000000000..196a4d2d8e --- /dev/null +++ b/crates/core/c8y_api/src/smartrest/payload.rs @@ -0,0 +1,95 @@ +use std::fmt::Display; + +use serde::Serialize; +use tracing::error; + +use super::message::MAX_PAYLOAD_LIMIT_IN_BYTES; + +/// A Cumulocity SmartREST message payload. +/// +/// A SmartREST message is either an HTTP request or an MQTT message that contains SmartREST topic and payload. The +/// payload is a CSV-like format that is backed by templates, either static or registered by the user. This struct +/// represents that payload, and should be used as such in SmartREST 1.0 and 2.0 message implementations. +/// +/// # Example +/// +/// ```text +/// 503,c8y_Command,"This is a ""Set operation to SUCCESSFUL (503)"" message payload; it has a template id (503), +/// operation fragment (c8y_Command), and optional parameters." +/// ``` +/// +/// # Reference +/// +/// - https://cumulocity.com/docs/smartrest/smartrest-introduction/ +#[derive(Debug, Clone, PartialEq, Eq)] +// TODO: pub(crate) for now so it can be constructed manually in serializer::succeed_operation, need to figure out a +// good API +pub struct SmartrestPayload(pub(crate) String); + +impl SmartrestPayload { + /// Creates a payload that consists of a single record. + /// + /// Doesn't trim any fields, so if the resulting payload is above size limit, returns an error. + pub fn from_fields( + fields: impl IntoIterator, + ) -> Result + where + T: AsRef + AsRef<[u8]>, + { + let payload = super::csv::fields_to_csv_string(fields); + + if payload.len() > MAX_PAYLOAD_LIMIT_IN_BYTES { + return Err(SmartrestPayloadError::TooLarge(payload.len())); + } + + Ok(Self(payload)) + } + + pub fn serialize(record: S) -> Result { + let mut wtr = csv::Writer::from_writer(vec![]); + wtr.serialize(record)?; + let mut vec = wtr.into_inner().unwrap(); + // remove newline character + vec.pop(); + let payload = String::from_utf8(vec) + .expect("TODO: should SmartrestPayload wrap a string or a byte array?"); + + if payload.len() > MAX_PAYLOAD_LIMIT_IN_BYTES { + return Err(SmartrestPayloadError::TooLarge(payload.len())); + } + + Ok(Self(payload)) + } + + /// Returns a string slice view of the payload. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Moves the underlying `String` out of the payload. + pub fn into_inner(self) -> String { + self.0 + } +} + +/// Errors that can occur when trying to create a SmartREST payload. +#[derive(Debug, thiserror::Error)] +pub enum SmartrestPayloadError { + #[error("Payload size ({0}) would be bigger than the limit ({MAX_PAYLOAD_LIMIT_IN_BYTES})")] + TooLarge(usize), + + #[error("Could not serialize the record")] + SerializeError(#[from] csv::Error), +} + +impl From for Vec { + fn from(value: SmartrestPayload) -> Self { + value.0.into_bytes() + } +} + +impl Display for SmartrestPayload { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} diff --git a/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs b/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs index 9562ae78bb..0e477cbf83 100644 --- a/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs +++ b/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs @@ -1,6 +1,7 @@ +use super::payload::SmartrestPayload; +use super::payload::SmartrestPayloadError; use crate::smartrest::csv::fields_to_csv_string; use crate::smartrest::error::SmartRestSerializerError; -use crate::smartrest::message::SmartrestPayloadError; use csv::StringRecord; use serde::ser::SerializeSeq; use serde::Serialize; @@ -9,8 +10,6 @@ use tedge_api::SoftwareListCommand; use tedge_api::SoftwareModule; use tracing::warn; -use super::message::SmartrestPayload; - pub type SmartRest = String; pub fn request_pending_operations() -> &'static str { diff --git a/crates/extensions/c8y_mapper_ext/src/operations/handlers/mod.rs b/crates/extensions/c8y_mapper_ext/src/operations/handlers/mod.rs index ca116a88ed..90d21ac49c 100644 --- a/crates/extensions/c8y_mapper_ext/src/operations/handlers/mod.rs +++ b/crates/extensions/c8y_mapper_ext/src/operations/handlers/mod.rs @@ -17,7 +17,7 @@ use crate::actor::IdUploadRequest; use crate::actor::IdUploadResult; use crate::Capabilities; use c8y_api::http_proxy::C8yEndPoint; -use c8y_api::smartrest::message::SmartrestPayload; +use c8y_api::smartrest::payload::SmartrestPayload; use c8y_api::smartrest::smartrest_serializer::fail_operation_with_id; use c8y_api::smartrest::smartrest_serializer::fail_operation_with_name; use c8y_api::smartrest::smartrest_serializer::set_operation_executing_with_id;