-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add "HumanBinaryData" as an alternative to "Base64UrlSafeData" (#354)
* Add "HumanBinaryData" as alternative to "Base64UrlSafeData" (#352) * Add bytes support to Base64UrlSafeData, and copy across the tests * Rework the docs * dedupe functionality into macros, make tests consistent * more tests and conversions * move Borrow impl into common * add some more vec features * fix clippy
- Loading branch information
Showing
5 changed files
with
560 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/// Macro to declare common functionality for [`Base64UrlSafeData`][0] and | ||
/// [`HumanBinaryData`][1] | ||
/// | ||
/// [0]: crate::Base64UrlSafeData | ||
/// [1]: crate::HumanBinaryData | ||
macro_rules! common_impls { | ||
($type:ty) => { | ||
impl $type { | ||
pub const fn new() -> Self { | ||
Self(Vec::new()) | ||
} | ||
|
||
pub fn with_capacity(capacity: usize) -> Self { | ||
Vec::with_capacity(capacity).into() | ||
} | ||
} | ||
|
||
impl std::ops::Deref for $type { | ||
type Target = Vec<u8>; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl std::ops::DerefMut for $type { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
&mut self.0 | ||
} | ||
} | ||
|
||
impl std::borrow::Borrow<[u8]> for $type { | ||
fn borrow(&self) -> &[u8] { | ||
self.0.as_slice() | ||
} | ||
} | ||
|
||
impl From<Vec<u8>> for $type { | ||
fn from(value: Vec<u8>) -> Self { | ||
Self(value) | ||
} | ||
} | ||
|
||
impl<const N: usize> From<[u8; N]> for $type { | ||
fn from(value: [u8; N]) -> Self { | ||
Self(value.to_vec()) | ||
} | ||
} | ||
|
||
impl From<&[u8]> for $type { | ||
fn from(value: &[u8]) -> Self { | ||
Self(value.to_vec()) | ||
} | ||
} | ||
|
||
impl<const N: usize> From<&[u8; N]> for $type { | ||
fn from(value: &[u8; N]) -> Self { | ||
Self(value.to_vec()) | ||
} | ||
} | ||
|
||
impl From<$type> for Vec<u8> { | ||
fn from(value: $type) -> Self { | ||
value.0 | ||
} | ||
} | ||
|
||
impl AsRef<[u8]> for $type { | ||
fn as_ref(&self) -> &[u8] { | ||
&self.0 | ||
} | ||
} | ||
|
||
macro_rules! partial_eq_impl { | ||
($other:ty) => { | ||
impl PartialEq<$other> for $type { | ||
fn eq(&self, other: &$other) -> bool { | ||
self.as_slice() == &other[..] | ||
} | ||
} | ||
|
||
impl PartialEq<$type> for $other { | ||
fn eq(&self, other: &$type) -> bool { | ||
self.eq(&other.0) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
partial_eq_impl!(Vec<u8>); | ||
partial_eq_impl!([u8]); | ||
partial_eq_impl!(&[u8]); | ||
|
||
impl<const N: usize> PartialEq<[u8; N]> for $type { | ||
fn eq(&self, other: &[u8; N]) -> bool { | ||
self.0.eq(other) | ||
} | ||
} | ||
|
||
impl<const N: usize> PartialEq<$type> for [u8; N] { | ||
fn eq(&self, other: &$type) -> bool { | ||
self.as_slice().eq(&other.0) | ||
} | ||
} | ||
|
||
paste! { | ||
#[doc(hidden)] | ||
struct [<$type Visitor>]; | ||
|
||
impl<'de> serde::de::Visitor<'de> for [<$type Visitor>] { | ||
type Value = $type; | ||
|
||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | ||
write!( | ||
formatter, | ||
"a url-safe base64-encoded string, bytes, or sequence of integers" | ||
) | ||
} | ||
|
||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> | ||
where | ||
E: serde::de::Error, | ||
{ | ||
// Forgive alt base64 decoding formats | ||
for config in crate::ALLOWED_DECODING_FORMATS { | ||
if let Ok(data) = config.decode(v) { | ||
return Ok(<$type>::from(data)); | ||
} | ||
} | ||
|
||
Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self)) | ||
} | ||
|
||
fn visit_seq<A>(self, mut v: A) -> Result<Self::Value, A::Error> | ||
where | ||
A: serde::de::SeqAccess<'de>, | ||
{ | ||
let mut data = if let Some(sz) = v.size_hint() { | ||
Vec::with_capacity(sz) | ||
} else { | ||
Vec::new() | ||
}; | ||
|
||
while let Some(i) = v.next_element()? { | ||
data.push(i) | ||
} | ||
Ok(<$type>::from(data)) | ||
} | ||
|
||
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E> | ||
where | ||
E: serde::de::Error, | ||
{ | ||
Ok(<$type>::from(v)) | ||
} | ||
|
||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> | ||
where | ||
E: serde::de::Error, | ||
{ | ||
Ok(<$type>::from(v)) | ||
} | ||
} | ||
|
||
impl<'de> serde::Deserialize<'de> for $type { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error> | ||
where | ||
D: serde::Deserializer<'de>, | ||
{ | ||
// Was previously _str | ||
deserializer.deserialize_any([<$type Visitor>]) | ||
} | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use std::fmt; | ||
|
||
use crate::{Base64UrlSafeData, URL_SAFE_NO_PAD}; | ||
use base64::Engine; | ||
use serde::{Serialize, Serializer}; | ||
|
||
/// Serde wrapper for `Vec<u8>` which emits URL-safe, non-padded Base64 for | ||
/// *only* human-readable formats, and accepts Base64 and binary formats. | ||
/// | ||
/// * Deserialisation is described in the [module documentation][crate]. | ||
/// | ||
/// * Serialisation to [a human-readable format][0] (such as JSON) emits | ||
/// URL-safe, non-padded Base64 (per [RFC 4648 §5][sec5]). | ||
/// | ||
/// * Serialisation to [a non-human-readable format][0] (such as CBOR) emits | ||
/// a native "bytes" type, and not encode the value. | ||
/// | ||
/// [0]: https://docs.rs/serde/latest/serde/trait.Serializer.html#method.is_human_readable | ||
/// [sec5]: https://datatracker.ietf.org/doc/html/rfc4648#section-5 | ||
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] | ||
pub struct HumanBinaryData(Vec<u8>); | ||
|
||
common_impls!(HumanBinaryData); | ||
|
||
impl From<Base64UrlSafeData> for HumanBinaryData { | ||
fn from(value: Base64UrlSafeData) -> Self { | ||
Self(value.into()) | ||
} | ||
} | ||
|
||
impl PartialEq<Base64UrlSafeData> for HumanBinaryData { | ||
fn eq(&self, other: &Base64UrlSafeData) -> bool { | ||
self.0.eq(other) | ||
} | ||
} | ||
|
||
impl Serialize for HumanBinaryData { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
if serializer.is_human_readable() { | ||
let encoded = URL_SAFE_NO_PAD.encode(self); | ||
serializer.serialize_str(&encoded) | ||
} else { | ||
serializer.serialize_bytes(self) | ||
} | ||
} | ||
} |
Oops, something went wrong.