From 51cf8a88442b5a945bde39807674db502da28695 Mon Sep 17 00:00:00 2001 From: gabrik Date: Thu, 14 Mar 2024 14:13:56 +0100 Subject: [PATCH 1/5] fix(828): ensuring valid JSON response from REST API Signed-off-by: gabrik --- plugins/zenoh-plugin-rest/src/lib.rs | 84 ++++++++++++++++++---------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/plugins/zenoh-plugin-rest/src/lib.rs b/plugins/zenoh-plugin-rest/src/lib.rs index 1a99d7b5a4..f9a10f8676 100644 --- a/plugins/zenoh-plugin-rest/src/lib.rs +++ b/plugins/zenoh-plugin-rest/src/lib.rs @@ -18,9 +18,10 @@ //! //! [Click here for Zenoh's documentation](../zenoh/index.html) use async_std::prelude::FutureExt; -use base64::{engine::general_purpose::STANDARD as b64_std_engine, Engine}; +use base64::Engine; use futures::StreamExt; use http_types::Method; +use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::convert::TryFrom; use std::str::FromStr; @@ -46,36 +47,57 @@ lazy_static::lazy_static! { } const RAW_KEY: &str = "_raw"; -fn payload_to_json(payload: Payload) -> String { - payload - .deserialize::() - .unwrap_or_else(|_| format!(r#""{}""#, b64_std_engine.encode(payload.contiguous()))) +#[derive(Serialize, Deserialize)] +struct JSONSample { + key: String, + value: serde_json::Value, + encoding: String, + time: Option, } -fn sample_to_json(sample: Sample) -> String { - format!( - r#"{{ "key": "{}", "value": {}, "encoding": "{}", "time": "{}" }}"#, - sample.key_expr.as_str(), - payload_to_json(sample.payload), - sample.encoding, - if let Some(ts) = sample.timestamp { - ts.to_string() - } else { - "None".to_string() +pub fn base64_encode(data: &[u8]) -> String { + use base64::engine::general_purpose; + general_purpose::STANDARD.encode(data) +} + +fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { + match payload.len() { + // If the value is empty return a JSON null + 0 => serde_json::Value::Null, + // if it is not check the encoding + _ => { + match encoding { + // If it is a JSON try to deserialize as json, if it fails fallback to base64 + &Encoding::APPLICATION_JSON | &Encoding::TEXT_JSON | &Encoding::TEXT_JSON5 => { + serde_json::from_slice::(&payload.contiguous()).unwrap_or( + serde_json::Value::String(base64_encode(&payload.contiguous())), + ) + } + // otherwise convert to base64 and JSON String + _ => serde_json::Value::String(base64_encode(&payload.contiguous())), + } } - ) + } } -fn result_to_json(sample: Result) -> String { +fn sample_to_json(sample: Sample) -> JSONSample { + JSONSample { + key: sample.key_expr.as_str().to_string(), + value: payload_to_json(sample.payload, &sample.encoding), + encoding: sample.encoding.to_string(), + time: sample.timestamp.map(|ts| ts.to_string()), + } +} + +fn result_to_json(sample: Result) -> JSONSample { match sample { Ok(sample) => sample_to_json(sample), - Err(err) => { - format!( - r#"{{ "key": "ERROR", "value": {}, "encoding": "{}"}}"#, - payload_to_json(err.payload), - err.encoding, - ) - } + Err(err) => JSONSample { + key: "ERROR".into(), + value: payload_to_json(err.payload, &err.encoding), + encoding: err.encoding.to_string(), + time: None, + }, } } @@ -83,10 +105,10 @@ async fn to_json(results: flume::Receiver) -> String { let values = results .stream() .filter_map(move |reply| async move { Some(result_to_json(reply.sample)) }) - .collect::>() - .await - .join(",\n"); - format!("[\n{values}\n]\n") + .collect::>() + .await; + + serde_json::to_string(&values).unwrap_or("[]".into()) } async fn to_json_response(results: flume::Receiver) -> Response { @@ -321,8 +343,12 @@ async fn query(mut req: Request<(Arc, String)>) -> tide::Result Date: Thu, 14 Mar 2024 14:44:31 +0100 Subject: [PATCH 2/5] fix(828): improved JSON format conversion Signed-off-by: gabrik --- plugins/zenoh-plugin-rest/src/lib.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/zenoh-plugin-rest/src/lib.rs b/plugins/zenoh-plugin-rest/src/lib.rs index f9a10f8676..153fb1dd76 100644 --- a/plugins/zenoh-plugin-rest/src/lib.rs +++ b/plugins/zenoh-plugin-rest/src/lib.rs @@ -60,6 +60,10 @@ pub fn base64_encode(data: &[u8]) -> String { general_purpose::STANDARD.encode(data) } +fn payload_to_string(payload: &Payload) -> String { + String::from_utf8(payload.contiguous().to_vec()).unwrap_or(base64_encode(&payload.contiguous())) +} + fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { match payload.len() { // If the value is empty return a JSON null @@ -69,12 +73,11 @@ fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { match encoding { // If it is a JSON try to deserialize as json, if it fails fallback to base64 &Encoding::APPLICATION_JSON | &Encoding::TEXT_JSON | &Encoding::TEXT_JSON5 => { - serde_json::from_slice::(&payload.contiguous()).unwrap_or( - serde_json::Value::String(base64_encode(&payload.contiguous())), - ) + serde_json::from_slice::(&payload.contiguous()) + .unwrap_or(serde_json::Value::String(payload_to_string(&payload))) } - // otherwise convert to base64 and JSON String - _ => serde_json::Value::String(base64_encode(&payload.contiguous())), + // otherwise convert to JSON string + _ => serde_json::Value::String(payload_to_string(&payload)), } } } @@ -343,7 +346,7 @@ async fn query(mut req: Request<(Arc, String)>) -> tide::Result Date: Thu, 14 Mar 2024 15:44:54 +0100 Subject: [PATCH 3/5] chore: addressing comments Signed-off-by: gabrik --- plugins/zenoh-plugin-rest/src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/zenoh-plugin-rest/src/lib.rs b/plugins/zenoh-plugin-rest/src/lib.rs index 153fb1dd76..86cf94ba13 100644 --- a/plugins/zenoh-plugin-rest/src/lib.rs +++ b/plugins/zenoh-plugin-rest/src/lib.rs @@ -29,6 +29,7 @@ use std::sync::Arc; use tide::http::Mime; use tide::sse::Sender; use tide::{Request, Response, Server, StatusCode}; +use zenoh::payload::StringOrBase64; use zenoh::plugins::{RunningPluginTrait, ZenohPlugin}; use zenoh::prelude::r#async::*; use zenoh::query::{QueryConsolidation, Reply}; @@ -60,9 +61,9 @@ pub fn base64_encode(data: &[u8]) -> String { general_purpose::STANDARD.encode(data) } -fn payload_to_string(payload: &Payload) -> String { - String::from_utf8(payload.contiguous().to_vec()).unwrap_or(base64_encode(&payload.contiguous())) -} +// fn payload_to_string(payload: &Payload) -> String { +// String::from_utf8(payload.contiguous().to_vec()).unwrap_or(base64_encode(&payload.contiguous())) +// } fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { match payload.len() { @@ -73,11 +74,12 @@ fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { match encoding { // If it is a JSON try to deserialize as json, if it fails fallback to base64 &Encoding::APPLICATION_JSON | &Encoding::TEXT_JSON | &Encoding::TEXT_JSON5 => { - serde_json::from_slice::(&payload.contiguous()) - .unwrap_or(serde_json::Value::String(payload_to_string(&payload))) + serde_json::from_slice::(&payload.contiguous()).unwrap_or( + serde_json::Value::String((*StringOrBase64::from(payload)).clone()), + ) } // otherwise convert to JSON string - _ => serde_json::Value::String(payload_to_string(&payload)), + _ => serde_json::Value::String((*StringOrBase64::from(payload)).clone()), } } } From 280e9b70dec2d6d9322655559f62e7f3135950a8 Mon Sep 17 00:00:00 2001 From: gabrik Date: Thu, 14 Mar 2024 15:56:24 +0100 Subject: [PATCH 4/5] fix(828): added 'into_string' for StringOrBase64 Signed-off-by: gabrik --- plugins/zenoh-plugin-rest/src/lib.rs | 8 ++------ zenoh/src/payload.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/zenoh-plugin-rest/src/lib.rs b/plugins/zenoh-plugin-rest/src/lib.rs index 86cf94ba13..e6cf21e5dd 100644 --- a/plugins/zenoh-plugin-rest/src/lib.rs +++ b/plugins/zenoh-plugin-rest/src/lib.rs @@ -61,10 +61,6 @@ pub fn base64_encode(data: &[u8]) -> String { general_purpose::STANDARD.encode(data) } -// fn payload_to_string(payload: &Payload) -> String { -// String::from_utf8(payload.contiguous().to_vec()).unwrap_or(base64_encode(&payload.contiguous())) -// } - fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { match payload.len() { // If the value is empty return a JSON null @@ -75,11 +71,11 @@ fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { // If it is a JSON try to deserialize as json, if it fails fallback to base64 &Encoding::APPLICATION_JSON | &Encoding::TEXT_JSON | &Encoding::TEXT_JSON5 => { serde_json::from_slice::(&payload.contiguous()).unwrap_or( - serde_json::Value::String((*StringOrBase64::from(payload)).clone()), + serde_json::Value::String(StringOrBase64::from(payload).into_string()), ) } // otherwise convert to JSON string - _ => serde_json::Value::String((*StringOrBase64::from(payload)).clone()), + _ => serde_json::Value::String(StringOrBase64::from(payload).into_string()), } } } diff --git a/zenoh/src/payload.rs b/zenoh/src/payload.rs index f499db50da..f6bf036f75 100644 --- a/zenoh/src/payload.rs +++ b/zenoh/src/payload.rs @@ -563,6 +563,14 @@ pub enum StringOrBase64 { Base64(String), } +impl StringOrBase64 { + pub fn into_string(self) -> String { + match self { + StringOrBase64::String(s) | StringOrBase64::Base64(s) => s, + } + } +} + impl Deref for StringOrBase64 { type Target = String; From 56ddcb411acb2010f4f854afe228d78d19c5440b Mon Sep 17 00:00:00 2001 From: gabrik Date: Thu, 14 Mar 2024 18:00:58 +0100 Subject: [PATCH 5/5] chore: address comments Signed-off-by: gabrik --- plugins/zenoh-plugin-rest/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/zenoh-plugin-rest/src/lib.rs b/plugins/zenoh-plugin-rest/src/lib.rs index e6cf21e5dd..39225b5d25 100644 --- a/plugins/zenoh-plugin-rest/src/lib.rs +++ b/plugins/zenoh-plugin-rest/src/lib.rs @@ -62,11 +62,11 @@ pub fn base64_encode(data: &[u8]) -> String { } fn payload_to_json(payload: Payload, encoding: &Encoding) -> serde_json::Value { - match payload.len() { + match payload.is_empty() { // If the value is empty return a JSON null - 0 => serde_json::Value::Null, + true => serde_json::Value::Null, // if it is not check the encoding - _ => { + false => { match encoding { // If it is a JSON try to deserialize as json, if it fails fallback to base64 &Encoding::APPLICATION_JSON | &Encoding::TEXT_JSON | &Encoding::TEXT_JSON5 => {