Skip to content

Commit

Permalink
feat(socketioxide): unify non-binary and binary data
Browse files Browse the repository at this point in the history
This allows users to create a payload struct (that implements
Serialize/Deserialize) that has any binary payload data embedded in the
struct directly, rather than needing to look in a "side" Vec of payload
data, and have to guess what order the binary payloads fit into their
data model.

To accomplish this, there are new Serializer and Deserializer
implementations that mostly wrap the Ser/Deser impls provided for
serde_json::Value.  Unfortunately serde_json doesn't expose everything
necessary to do this in a truly simple way; some duplication of
serde_json's functionality is needed.

Closes Totodore#276.
  • Loading branch information
kelnos committed Apr 15, 2024
1 parent c4ead64 commit c7fc53a
Show file tree
Hide file tree
Showing 6 changed files with 1,632 additions and 40 deletions.
8 changes: 4 additions & 4 deletions socketioxide/src/handler/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ where
fn from_message_parts(
_: &Arc<Socket<A>>,
v: &mut serde_json::Value,
_: &mut Vec<Bytes>,
b: &mut Vec<Bytes>,
_: &Option<i64>,
) -> Result<Self, Self::Error> {
upwrap_array(v);
serde_json::from_value(v.clone()).map(Data)
crate::from_value::<T>(v.clone(), b).map(Data)
}
}

Expand Down Expand Up @@ -178,11 +178,11 @@ where
fn from_message_parts(
_: &Arc<Socket<A>>,
v: &mut serde_json::Value,
_: &mut Vec<Bytes>,
b: &mut Vec<Bytes>,
_: &Option<i64>,
) -> Result<Self, Infallible> {
upwrap_array(v);
Ok(TryData(serde_json::from_value(v.clone())))
Ok(TryData(crate::from_value::<T>(v.clone(), b)))
}
}
/// An Extractor that returns a reference to a [`Socket`].
Expand Down
2 changes: 2 additions & 0 deletions socketioxide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,13 @@ pub use engineioxide::TransportType;
pub use errors::{AckError, AdapterError, BroadcastError, DisconnectError, SendError, SocketError};
pub use handler::extract;
pub use io::{SocketIo, SocketIoBuilder, SocketIoConfig};
pub use value::{de::from_value, ser::to_value};

mod client;
mod errors;
mod io;
mod ns;
mod value;

/// Socket.IO protocol version.
/// It is accessible with the [`Socket::protocol`](socket::Socket) method or as an extractor
Expand Down
95 changes: 59 additions & 36 deletions socketioxide/src/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::borrow::Cow;
use crate::ProtocolVersion;
use bytes::Bytes;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
use serde_json::{json, Map, Value};

use crate::errors::Error;
use engineioxide::sid::Sid;
Expand Down Expand Up @@ -243,27 +243,13 @@ impl<'a> PacketData<'a> {
}

impl BinaryPacket {
/// Create a binary packet from incoming data and remove all placeholders and get the payload count
pub fn incoming(mut data: Value) -> Self {
let payload_count = match &mut data {
Value::Array(ref mut v) => {
let count = v.len();
v.retain(|v| v.as_object().and_then(|o| o.get("_placeholder")).is_none());
count - v.len()
}
val => {
if val
.as_object()
.and_then(|o| o.get("_placeholder"))
.is_some()
{
data = Value::Array(vec![]);
1
} else {
0
}
}
/// Create a binary packet from incoming data and gets the payload count
pub fn incoming(data: Value) -> Self {
let data = match data {
v @ Value::Array(_) => v,
v => Value::Array(vec![v]),
};
let payload_count = count_binary_payloads(&data);

Self {
data,
Expand All @@ -273,18 +259,38 @@ impl BinaryPacket {
}

/// Create a binary packet from outgoing data and a payload
///
/// The outgoing data should include numbered placeholder objects for each binary, like so:
/// ```json
/// {
/// "_placeholder": true,
/// "num": 0
/// }
/// ```
///The value of the "num" field should correspond to its index in the `bin` argument.
pub fn outgoing(data: Value, bin: Vec<Bytes>) -> Self {
let mut data = match data {
Value::Array(v) => Value::Array(v),
d => Value::Array(vec![d]),
};
let payload_count = bin.len();
(0..payload_count).for_each(|i| {
data.as_array_mut().unwrap().push(json!({
"_placeholder": true,
"num": i
}))
});

let payload_count = count_binary_payloads(&data);
let bin_count = bin.len();

// TODO: if payload_count > bin_count, maybe should return an error here? data has more
// placeholders than bin has payloads. but maybe some payloads are reused and it's ok? at
// any rate, serialization will fail later if there are placeholders that reference
// payloads that don't exist.

if bin_count > payload_count {
(payload_count..bin_count).for_each(|i| {
data.as_array_mut().unwrap().push(json!({
"_placeholder": true,
"num": i
}))
});
}

Self {
data,
bin,
Expand All @@ -303,6 +309,23 @@ impl BinaryPacket {
}
}

fn is_placeholder(o: &Map<String, Value>) -> bool {
o.len() == 2
&& o.get("_placeholder")
.and_then(|v| v.as_bool())
.unwrap_or(false)
&& o.get("num").and_then(|v| v.as_u64()).is_some()
}

fn count_binary_payloads(data: &Value) -> usize {
match data {
Value::Array(a) => a.iter().map(count_binary_payloads).sum(),
Value::Object(o) if is_placeholder(o) => 1,
Value::Object(o) => o.values().map(count_binary_payloads).sum(),
_ => 0,
}
}

impl<'a> From<Packet<'a>> for String {
fn from(mut packet: Packet<'a>) -> String {
use PacketData::*;
Expand Down Expand Up @@ -715,7 +738,7 @@ mod test {
let packet: String = Packet::bin_event(
"/",
"event",
json!({ "data": "value™" }),
json!([{ "data": "value™" }, { "_placeholder": true, "num": 0 }]),
vec![Bytes::from_static(&[1])],
)
.try_into()
Expand All @@ -728,7 +751,7 @@ mod test {
let mut packet = Packet::bin_event(
"/",
"event",
json!({ "data": "value™" }),
json!([{ "data": "value™" }, { "_placeholder": true, "num": 0 }]),
vec![Bytes::from_static(&[1])],
);
packet.inner.set_ack_id(254);
Expand All @@ -741,7 +764,7 @@ mod test {
let packet: String = Packet::bin_event(
"/admin™",
"event",
json!([{"data": "value™"}]),
json!([{"data": "value™"}, { "_placeholder": true, "num": 0 }]),
vec![Bytes::from_static(&[1])],
)
.try_into()
Expand All @@ -754,7 +777,7 @@ mod test {
let mut packet = Packet::bin_event(
"/admin™",
"event",
json!([{"data": "value™"}]),
json!([{"data": "value™"}, { "_placeholder": true, "num": 0 }]),
vec![Bytes::from_static(&[1])],
);
packet.inner.set_ack_id(254);
Expand All @@ -770,7 +793,7 @@ mod test {
"event".into(),
BinaryPacket {
bin: vec![Bytes::from_static(&[1])],
data: json!([{"data": "value™"}]),
data: json!([{"data": "value™"}, {"_placeholder": true, "num": 0}]),
payload_count: 1,
},
ack,
Expand Down Expand Up @@ -825,7 +848,7 @@ mod test {
let payload = format!("61-54{}", json);
let packet: String = Packet::bin_ack(
"/",
json!({ "data": "value™" }),
json!([{ "data": "value™" }, { "_placeholder": true, "num": 0 }]),
vec![Bytes::from_static(&[1])],
54,
)
Expand All @@ -838,7 +861,7 @@ mod test {
let payload = format!("61-/admin™,54{}", json);
let packet: String = Packet::bin_ack(
"/admin™",
json!({ "data": "value™" }),
json!([{ "data": "value™" }, { "_placeholder": true, "num": 0 }]),
vec![Bytes::from_static(&[1])],
54,
)
Expand All @@ -855,7 +878,7 @@ mod test {
inner: PacketData::BinaryAck(
BinaryPacket {
bin: vec![Bytes::from_static(&[1])],
data: json!([{"data": "value™"}]),
data: json!([{"data": "value™"}, {"_placeholder": true, "num": 0}]),
payload_count: 1,
},
ack,
Expand Down
Loading

0 comments on commit c7fc53a

Please sign in to comment.