use serde::{
Deserialize,
de::{self},
ser::{self, Serialize},
};
#[derive(Debug, Clone)]
#[must_use]
pub struct Json<T>(pub T);
impl<T> Json<T> {
pub fn into_inner(self) -> T {
self.0
}
}
const TRANSIENT_INPUT: &str = "`Json<T>` needs data borrowed from the connection buffer, and this \
deserializer supplied owned data; use `serde_json` directly";
impl<'de, T> Deserialize<'de> for Json<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::{fmt, marker::PhantomData};
struct Visitor<T> {
phantom: PhantomData<T>,
}
impl<'de, T> de::Visitor<'de> for Visitor<T>
where
T: Deserialize<'de>,
{
type Value = Json<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a JSON-encoded bulk string")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(
"the reply is nil: a key that may be missing must be read as \
`Option<Json<T>>`, not `Json<T>`",
))
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_none()
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(format!(
"expected a JSON-encoded bulk string, got the integer reply {v}"
)))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(format!(
"expected a JSON-encoded bulk string, got the integer reply {v}"
)))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(format!(
"expected a JSON-encoded bulk string, got the double reply {v}"
)))
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(format!(
"expected a JSON-encoded bulk string, got the boolean reply {v}"
)))
}
fn visit_str<E>(self, _v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(TRANSIENT_INPUT))
}
fn visit_string<E>(self, _v: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(TRANSIENT_INPUT))
}
fn visit_bytes<E>(self, _v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(TRANSIENT_INPUT))
}
fn visit_byte_buf<E>(self, _v: Vec<u8>) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(de::Error::custom(TRANSIENT_INPUT))
}
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let value: T = serde_json::from_slice(v).map_err(|e| {
de::Error::custom(format!(
"Cannot deserialize from json (borrowed bytes): {}",
e
))
})?;
Ok(Json(value))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: de::Error,
{
let value: T = serde_json::from_str(v).map_err(|e| {
de::Error::custom(format!(
"Cannot deserialize from json (borrowed str): {}",
e
))
})?;
Ok(Json(value))
}
}
deserializer.deserialize_any(Visitor {
phantom: PhantomData,
})
}
}
impl<T> Serialize for Json<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let bytes = serde_json::to_vec(&self.0)
.map_err(|e| ser::Error::custom(format!("Cannot serialize to json: {e}")))?;
serializer.serialize_bytes(&bytes)
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable,
clippy::indexing_slicing,
reason = "test code: a panic is how a test reports failure"
)]
use super::Json;
use crate::{
ClientError, Error,
resp::{Command, FastPathCommandBuilder, RespBuf, cmd},
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
struct Person {
id: u32,
name: String,
}
fn person() -> Person {
Person {
id: 12,
name: "Foo".to_string(),
}
}
struct FailingSerialize;
impl Serialize for FailingSerialize {
fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
Err(serde::ser::Error::custom("boom"))
}
}
fn serialization_error_of(mut command: Command) -> Option<Error> {
command.take_serialization_error()
}
#[test]
fn a_failing_serialize_fails_the_command() {
let mut command: Command = FastPathCommandBuilder::set("key", Json(&FailingSerialize));
let error = command.take_serialization_error();
assert!(
matches!(&error, Some(Error::Client(ClientError::SerdeSerialize(m))) if m.contains("Cannot serialize to json")),
"unexpected error: {error:?}"
);
}
#[test]
fn a_serde_json_error_reaches_the_caller() {
let map: BTreeMap<(u8, u8), u8> = BTreeMap::from([((1, 2), 3)]);
let command: Command = FastPathCommandBuilder::set("key", Json(&map));
assert!(matches!(
serialization_error_of(command),
Some(Error::Client(ClientError::SerdeSerialize(_)))
));
}
#[test]
fn an_unserializable_value_is_never_written_as_an_empty_argument() {
let mut command: Command = FastPathCommandBuilder::set("key", Json(&FailingSerialize));
assert!(command.take_serialization_error().is_some());
assert_eq!(1, command.num_args());
assert_eq!(Some(&b"key"[..]), command.get_arg(0).as_deref());
}
#[test]
fn the_generic_builder_defers_the_same_error() {
let command: Command = cmd("SET").key("key").arg(Json(&FailingSerialize)).into();
assert!(matches!(
serialization_error_of(command),
Some(Error::Client(ClientError::SerdeSerialize(_)))
));
}
#[test]
fn a_serializable_value_becomes_one_json_argument() {
let mut command: Command = FastPathCommandBuilder::set("key", Json(&person()));
assert!(command.take_serialization_error().is_none());
assert_eq!(2, command.num_args());
assert_eq!(
Some(&br#"{"id":12,"name":"Foo"}"#[..]),
command.get_arg(1).as_deref()
);
}
#[test]
fn a_borrowed_value_serializes_like_an_owned_one() {
let person = person();
let borrowed: Command = FastPathCommandBuilder::set("key", Json(&person));
let owned: Command = FastPathCommandBuilder::set("key", Json(person.clone()));
assert_eq!(borrowed.get_arg(1), owned.get_arg(1));
}
#[test]
fn a_bulk_string_reply_deserializes() {
let resp = RespBuf::from_slice(b"$22\r\n{\"id\":12,\"name\":\"Foo\"}\r\n");
let Json(deserialized): Json<Person> = resp.to().unwrap();
assert_eq!(person(), deserialized);
}
#[test]
fn a_simple_string_reply_deserializes() {
let resp = RespBuf::from_slice(b"+{\"id\":12,\"name\":\"Foo\"}\r\n");
let Json(deserialized): Json<Person> = resp.to().unwrap();
assert_eq!(person(), deserialized);
}
#[test]
fn a_nil_reply_points_at_option_json() {
let resp = RespBuf::from_slice(b"_\r\n");
let error = resp.to::<Json<Person>>().unwrap_err();
assert!(
error.to_string().contains("Option<Json<T>>"),
"unexpected error: {error}"
);
assert!(resp.to::<Option<Json<Person>>>().unwrap().is_none());
}
#[test]
fn an_integer_reply_is_named_in_the_error() {
let resp = RespBuf::from_slice(b":12\r\n");
let error = resp.to::<Json<Person>>().unwrap_err();
assert!(
error.to_string().contains("integer"),
"unexpected error: {error}"
);
}
#[test]
fn malformed_json_reports_the_serde_json_message() {
let resp = RespBuf::from_slice(b"$3\r\nnot\r\n");
let error = resp.to::<Json<Person>>().unwrap_err();
assert!(
error.to_string().contains("Cannot deserialize from json"),
"unexpected error: {error}"
);
}
#[test]
fn a_json_value_becomes_one_json_argument() {
let value = serde_json::json!({ "id": 12, "name": "Foo" });
let mut command: Command = FastPathCommandBuilder::set("key", Json(&value));
assert!(command.take_serialization_error().is_none());
assert_eq!(2, command.num_args());
assert_eq!(
Some(&br#"{"id":12,"name":"Foo"}"#[..]),
command.get_arg(1).as_deref()
);
}
#[test]
fn a_bulk_string_reply_deserializes_into_a_json_value() {
let resp = RespBuf::from_slice(b"$22\r\n{\"id\":12,\"name\":\"Foo\"}\r\n");
let Json(value): Json<serde_json::Value> = resp.to().unwrap();
assert_eq!(serde_json::json!({ "id": 12, "name": "Foo" }), value);
}
#[test]
fn into_inner_returns_the_wrapped_value() {
assert_eq!(person(), Json(person()).into_inner());
}
}