Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "HumanBinaryData" as an alternative to "Base64UrlSafeData" #354

Merged
merged 8 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions base64urlsafedata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ readme = "README.md"
[dependencies]
serde.workspace = true
base64.workspace = true
paste = "1.0.14"

[dev-dependencies]
serde_cbor_2.workspace = true
serde_json.workspace = true
176 changes: 176 additions & 0 deletions base64urlsafedata/src/common.rs
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>])
}
}
}
};
}
49 changes: 49 additions & 0 deletions base64urlsafedata/src/human.rs
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)
}
}
}
Loading
Loading