#![no_std]
#![forbid(unsafe_code, clippy::expect_used, clippy::panic)]
#![deny(
clippy::all,
absolute_paths_not_starting_with_crate,
deprecated_in_future,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
noop_method_call,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
single_use_lifetimes,
trivial_bounds,
trivial_casts,
trivial_numeric_casts,
unreachable_code,
unreachable_patterns,
unreachable_pub,
unstable_features,
unused,
unused_crate_dependencies,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_results,
variant_size_differences
)]
extern crate alloc;
use alloc::vec::Vec;
use base64::Engine;
use core::fmt::{Debug, Display, Formatter};
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
use core::str::FromStr;
use base64::engine::general_purpose::GeneralPurpose;
use base64::engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
mod sealed {
pub trait Config {
const CONFIG: base64::engine::general_purpose::GeneralPurpose;
}
}
use sealed::Config;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Standard(());
impl Config for Standard {
const CONFIG: GeneralPurpose = STANDARD;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StandardNoPad(());
impl Config for StandardNoPad {
const CONFIG: GeneralPurpose = STANDARD_NO_PAD;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UrlSafe(());
impl Config for UrlSafe {
const CONFIG: GeneralPurpose = URL_SAFE;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UrlSafeNoPad(());
impl Config for UrlSafeNoPad {
const CONFIG: GeneralPurpose = URL_SAFE_NO_PAD;
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Bytes<T, C = Standard>(T, PhantomData<C>);
impl<T: Debug, C> Debug for Bytes<T, C> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Bytes").field(&self.0).finish()
}
}
impl<T: Default, C> Default for Bytes<T, C> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}
impl<T, C> Bytes<T, C> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T, C> From<T> for Bytes<T, C> {
fn from(value: T) -> Self {
Self(value, PhantomData)
}
}
impl<T: AsRef<U>, U: ?Sized, C> AsRef<U> for Bytes<T, C> {
fn as_ref(&self) -> &U {
self.0.as_ref()
}
}
impl<T: AsMut<U>, U: ?Sized, C> AsMut<U> for Bytes<T, C> {
fn as_mut(&mut self) -> &mut U {
self.0.as_mut()
}
}
impl<T, C> Deref for Bytes<T, C> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, C> DerefMut for Bytes<T, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: AsRef<[u8]>, C: Config> Display for Bytes<T, C> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(&C::CONFIG.encode(self.0.as_ref()))
}
}
impl<T: From<Vec<u8>>, C: Config> FromStr for Bytes<T, C> {
type Err = base64::DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
C::CONFIG.decode(s).map(|x| Self(x.into(), PhantomData))
}
}
#[cfg(feature = "serde")]
impl<T: AsRef<[u8]>, C: Config> serde::Serialize for Bytes<T, C> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
C::CONFIG.encode(self.0.as_ref()).serialize(serializer)
} else {
serializer.serialize_bytes(self.0.as_ref())
}
}
}
#[cfg(feature = "serde")]
impl<'de, T: From<Vec<u8>>, C: Config> serde::Deserialize<'de> for Bytes<T, C> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
if deserializer.is_human_readable() {
let b64 = alloc::borrow::Cow::<'de, str>::deserialize(deserializer)?;
let buf = C::CONFIG
.decode(b64.as_ref())
.map_err(|_| Error::custom("invalid base64"))?;
Ok(Self(buf.into(), PhantomData))
} else {
Ok(Self(Vec::deserialize(deserializer)?.into(), PhantomData))
}
}
}