Skip to content

Commit

Permalink
Extract SmartrestPayload to separate module
Browse files Browse the repository at this point in the history
Signed-off-by: Marcel Guzik <[email protected]>
  • Loading branch information
Bravo555 committed Oct 21, 2024
1 parent 0ea61af commit ba1ac4b
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 99 deletions.
2 changes: 1 addition & 1 deletion crates/core/c8y_api/src/smartrest/alarm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SmartrestPayload, time::error::Format> {
Expand Down
2 changes: 1 addition & 1 deletion crates/core/c8y_api/src/smartrest/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
92 changes: 0 additions & 92 deletions crates/core/c8y_api/src/smartrest/message.rs
Original file line number Diff line number Diff line change
@@ -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<T>(
fields: impl IntoIterator<Item = T>,
) -> Result<Self, SmartrestPayloadError>
where
T: AsRef<str> + 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<S: Serialize>(record: S) -> Result<Self, SmartrestPayloadError> {
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<SmartrestPayload> for Vec<u8> {
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.
///
/// ```
Expand Down
1 change: 1 addition & 0 deletions crates/core/c8y_api/src/smartrest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion crates/core/c8y_api/src/smartrest/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
95 changes: 95 additions & 0 deletions crates/core/c8y_api/src/smartrest/payload.rs
Original file line number Diff line number Diff line change
@@ -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<T>(
fields: impl IntoIterator<Item = T>,
) -> Result<Self, SmartrestPayloadError>
where
T: AsRef<str> + 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<S: Serialize>(record: S) -> Result<Self, SmartrestPayloadError> {
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<SmartrestPayload> for Vec<u8> {
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)
}
}
5 changes: 2 additions & 3 deletions crates/core/c8y_api/src/smartrest/smartrest_serializer.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit ba1ac4b

Please sign in to comment.