Skip to content

Commit

Permalink
Merge pull request #5 from matteopolak/feat/borrow-from-input
Browse files Browse the repository at this point in the history
feat: support borrowing from input
  • Loading branch information
matteopolak authored Dec 4, 2024
2 parents 7823c55 + be4a73b commit c4a4753
Show file tree
Hide file tree
Showing 9 changed files with 590 additions and 340 deletions.
775 changes: 467 additions & 308 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ serde_json = { version = "1", optional = true }
serde_yaml = { version = "0.9", optional = true }
thiserror = "1"
toml = { version = "0.8", optional = true }
validator = { version = "0.18", optional = true }
validator = { version = "0.19", optional = true }

[dev-dependencies]
axum = "0.7"
Expand All @@ -42,7 +42,7 @@ bitcode = "0.6"
default = ["json", "macros", "pretty-errors"]

# Enables all codecs
full-codecs = ["bincode", "bitcode", "cbor", "json", "msgpack", "toml", "yaml"]
full-codecs = ["bincode", "bitcode", "json", "msgpack", "toml", "yaml"]
macros = ["schemars?/derive", "bincode?/derive", "bitcode?/derive", "serde?/derive", "validator?/derive", "axum-codec-macros/debug"]

# Enables support for {get,put,..}_with and relevant chaning methods
Expand All @@ -58,7 +58,7 @@ pretty-errors = ["macros"]

bincode = ["dep:bincode", "axum-codec-macros/bincode"]
bitcode = ["dep:bitcode", "axum-codec-macros/bitcode"]
cbor = ["dep:ciborium", "serde"]
# cbor = ["dep:ciborium", "serde"]
json = ["dep:serde_json", "serde"]
msgpack = ["dep:rmp-serde", "serde"]
toml = ["dep:toml", "serde"]
Expand All @@ -67,3 +67,6 @@ yaml = ["dep:serde_yaml", "serde"]
# Should not be manually enabled, but will not cause any issues if it is.
serde = ["dep:serde", "axum-codec-macros/serde"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("cbor"))'] }

2 changes: 0 additions & 2 deletions examples/basic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,4 @@ axum = "0.7"
serde = { version = "1", features = ["derive", "rc"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
axum-codec = { path = "../..", features = ["full-codecs", "macros", "validator"] }
bincode = "2.0.0-rc.3"
validator = { version = "0.18.1", features = ["derive"] }

3 changes: 0 additions & 3 deletions examples/todo-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@ axum = "0.7"
serde = { version = "1", features = ["derive", "rc"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
axum-codec = { path = "../..", features = ["full-codecs", "macros", "aide", "validator", "pretty-errors"] }
bitcode = "0.6"
bincode = "2.0.0-rc.3"
schemars = { version = "0.8", features = ["derive"] }
validator = "0.18"

7 changes: 5 additions & 2 deletions macros/src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ pub fn apply(

if args.decode {
tokens.extend(quote! {
#[derive(#crate_name::__private::bincode::Decode)]
#[derive(#crate_name::__private::bincode::BorrowDecode)]
});
}

Expand Down Expand Up @@ -199,8 +199,11 @@ pub fn apply(
// For now, use the real crate name so the error is nicer.
#[cfg(feature = "validator")]
if args.decode {
let crate_ = format!("{}::__private::validator", crate_name.to_token_stream());

tokens.extend(quote! {
#[derive(validator::Validate)]
#[derive(#crate_name::__private::validator::Validate)]
#[validate(crate = #crate_)]
});
}

Expand Down
10 changes: 5 additions & 5 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,11 @@ mod __private {

codec_trait.extend(quote! {
#input
pub trait CodecDecode
pub trait CodecDecode<'de>
});

codec_impl.extend(quote! {
impl<T> CodecDecode for T
impl<'de, T> CodecDecode<'de> for T
});

#[cfg(any(
Expand Down Expand Up @@ -180,7 +180,7 @@ mod __private {
}

constraints.extend(quote! {
serde::de::DeserializeOwned
serde::de::Deserialize<'de>
});
}

Expand All @@ -191,7 +191,7 @@ mod __private {
}

constraints.extend(quote! {
bincode::Decode
bincode::BorrowDecode<'de>
});
}

Expand All @@ -202,7 +202,7 @@ mod __private {
}

constraints.extend(quote! {
bitcode::DecodeOwned
bitcode::Decode<'de>
});
}

Expand Down
32 changes: 16 additions & 16 deletions src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ crate::macros::__private_decode_trait! {
}

#[cfg(feature = "serde")]
impl<T> Codec<T>
impl<'b, T> Codec<T>
where
T: serde::de::DeserializeOwned,
T: serde::de::Deserialize<'b>,
{
/// Attempts to deserialize the given bytes as [JSON](https://www.json.org).
///
Expand All @@ -20,7 +20,7 @@ where
/// See [`serde_json::from_slice`].
#[cfg(feature = "json")]
#[inline]
pub fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
pub fn from_json(bytes: &'b [u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(bytes).map(Self)
}

Expand All @@ -33,7 +33,7 @@ where
/// See [`rmp_serde::from_slice`].
#[cfg(feature = "msgpack")]
#[inline]
pub fn from_msgpack(bytes: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
pub fn from_msgpack(bytes: &'b [u8]) -> Result<Self, rmp_serde::decode::Error> {
let mut deserializer = rmp_serde::Deserializer::new(bytes).with_human_readable();

serde::Deserialize::deserialize(&mut deserializer).map(Self)
Expand All @@ -48,7 +48,7 @@ where
/// See [`ciborium::from_slice`].
#[cfg(feature = "cbor")]
#[inline]
pub fn from_cbor(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>> {
pub fn from_cbor(bytes: &'b [u8]) -> Result<Self, ciborium::de::Error<std::io::Error>> {
ciborium::from_reader(bytes).map(Self)
}

Expand All @@ -61,7 +61,7 @@ where
/// See [`serde_yaml::from_slice`].
#[cfg(feature = "yaml")]
#[inline]
pub fn from_yaml(text: &str) -> Result<Self, serde_yaml::Error> {
pub fn from_yaml(text: &'b str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(text).map(Self)
}

Expand All @@ -74,12 +74,12 @@ where
/// See [`toml::from_str`].
#[cfg(feature = "toml")]
#[inline]
pub fn from_toml(text: &str) -> Result<Self, toml::de::Error> {
toml::from_str(text).map(Self)
pub fn from_toml(text: &'b str) -> Result<Self, toml::de::Error> {
T::deserialize(toml::Deserializer::new(text)).map(Self)
}
}

impl<T> Codec<T> {
impl<'b, T> Codec<T> {
/// Attempts to deserialize the given bytes as [Bincode](https://github.com/bincode-org/bincode).
/// Does not perform any validation if the `validator` feature is enabled. For
/// validation, use [`Self::from_bytes`].
Expand All @@ -89,11 +89,11 @@ impl<T> Codec<T> {
/// See [`bincode::decode_from_slice`].
#[cfg(feature = "bincode")]
#[inline]
pub fn from_bincode(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError>
pub fn from_bincode(bytes: &'b [u8]) -> Result<Self, bincode::error::DecodeError>
where
T: bincode::Decode,
T: bincode::BorrowDecode<'b>,
{
bincode::decode_from_slice(bytes, bincode::config::standard()).map(|t| Self(t.0))
bincode::borrow_decode_from_slice(bytes, bincode::config::standard()).map(|t| Self(t.0))
}

/// Attempts to deserialize the given bytes as [Bitcode](https://github.com/SoftbearStudios/bitcode).
Expand All @@ -105,9 +105,9 @@ impl<T> Codec<T> {
/// See [`bitcode::decode`].
#[cfg(feature = "bitcode")]
#[inline]
pub fn from_bitcode(bytes: &[u8]) -> Result<Self, bitcode::Error>
pub fn from_bitcode(bytes: &'b [u8]) -> Result<Self, bitcode::Error>
where
T: bitcode::DecodeOwned,
T: bitcode::Decode<'b>,
{
bitcode::decode(bytes).map(Self)
}
Expand All @@ -117,9 +117,9 @@ impl<T> Codec<T> {
/// # Errors
///
/// See [`CodecRejection`].
pub fn from_bytes(bytes: &[u8], content_type: ContentType) -> Result<Self, CodecRejection>
pub fn from_bytes(bytes: &'b [u8], content_type: ContentType) -> Result<Self, CodecRejection>
where
T: CodecDecode,
T: CodecDecode<'b>,
{
let codec = match content_type {
#[cfg(feature = "json")]
Expand Down
3 changes: 2 additions & 1 deletion src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use crate::{Accept, CodecDecode, CodecEncode, CodecRejection, ContentType, IntoC
/// assert_eq!(data.hello, "world");
/// # }
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Codec<T>(pub T);

impl<T> Codec<T>
Expand Down Expand Up @@ -93,7 +94,7 @@ impl<T: fmt::Display> fmt::Display for Codec<T> {
#[axum::async_trait]
impl<T, S> FromRequest<S> for Codec<T>
where
T: CodecDecode,
T: for<'de> CodecDecode<'de>,
S: Send + Sync + 'static,
{
type Rejection = Response;
Expand Down
89 changes: 89 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ mod test {
boolean: bool,
}

#[apply(decode, encode)]
#[derive(Debug, PartialEq)]
struct BorrowedData<'a> {
string: &'a str,
integer: i32,
boolean: bool,
}

fn data() -> Data {
Data {
string: "hello".into(),
Expand All @@ -76,6 +84,14 @@ mod test {
}
}

fn borrowed_data<'a>() -> BorrowedData<'a> {
BorrowedData {
string: "hello",
integer: 42,
boolean: true,
}
}

#[test]
fn test_msgpack_roundtrip() {
let data = data();
Expand All @@ -86,6 +102,17 @@ mod test {
assert_eq!(decoded, data);
}

#[test]
#[should_panic(expected = "invalid type: string \\\"hello\\\", expected a borrowed string")]
fn test_borrowed_msgpack_roundtrip() {
let data = borrowed_data();
let encoded = Codec(&data).to_msgpack().unwrap();

let Codec(decoded) = Codec::<BorrowedData>::from_msgpack(&encoded).unwrap();

assert_eq!(decoded, data);
}

#[test]
fn test_json_roundtrip() {
let data = data();
Expand All @@ -97,6 +124,17 @@ mod test {
}

#[test]
fn test_borrowed_json_roundtrip() {
let data = borrowed_data();
let encoded = Codec(&data).to_json().unwrap();

let Codec(decoded) = Codec::<BorrowedData>::from_json(&encoded).unwrap();

assert_eq!(decoded, data);
}

#[test]
#[cfg(feature = "cbor")]
fn test_cbor_roundtrip() {
let data = data();
let encoded = Codec(&data).to_cbor().unwrap();
Expand All @@ -106,6 +144,17 @@ mod test {
assert_eq!(decoded, data);
}

#[test]
#[cfg(feature = "cbor")]
fn test_borrowed_cbor_roundtrip() {
let data = borrowed_data();
let encoded = Codec(&data).to_cbor().unwrap();

let Codec(decoded) = Codec::<BorrowedData>::from_cbor(&encoded).unwrap();

assert_eq!(decoded, data);
}

#[test]
fn test_yaml_roundtrip() {
let data = data();
Expand All @@ -116,6 +165,16 @@ mod test {
assert_eq!(decoded, data);
}

#[test]
fn test_borrowed_yaml_roundtrip() {
let data = borrowed_data();
let encoded = Codec(&data).to_yaml().unwrap();

let Codec(decoded) = Codec::<BorrowedData>::from_yaml(&encoded).unwrap();

assert_eq!(decoded, data);
}

#[test]
fn test_toml_roundtrip() {
let data = data();
Expand All @@ -126,6 +185,17 @@ mod test {
assert_eq!(decoded, data);
}

#[test]
#[should_panic(expected = "invalid type: string \\\"hello\\\", expected a borrowed string")]
fn test_borrowed_toml_roundtrip() {
let data = borrowed_data();
let encoded = Codec(&data).to_toml().unwrap();

let Codec(decoded) = Codec::<BorrowedData>::from_toml(&encoded).unwrap();

assert_eq!(decoded, data);
}

#[test]
fn test_bincode_roundtrip() {
let data = data();
Expand All @@ -136,6 +206,16 @@ mod test {
assert_eq!(decoded, data);
}

#[test]
fn test_borrowed_bincode_roundtrip() {
let data = borrowed_data();
let encoded = Codec(&data).to_bincode().unwrap();

let Codec(decoded) = Codec::<BorrowedData>::from_bincode(&encoded).unwrap();

assert_eq!(decoded, data);
}

#[test]
fn test_bitcode_roundtrip() {
let encoded = Codec(data()).to_bitcode();
Expand All @@ -144,4 +224,13 @@ mod test {

assert_eq!(decoded, data());
}

#[test]
fn test_borrowed_bitcode_roundtrip() {
let encoded = Codec(borrowed_data()).to_bitcode();

let Codec(decoded) = Codec::<BorrowedData>::from_bitcode(&encoded).unwrap();

assert_eq!(decoded, borrowed_data());
}
}

0 comments on commit c4a4753

Please sign in to comment.