tiedcrossing_byte/
lib.rs

1// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
2// SPDX-License-Identifier: AGPL-3.0-only
3
4//! This crate provides a [`Bytes`] type which wraps most types that represent
5//! a contiguous array of bytes. It provides implementations for easy
6//! conversions to and from Base64 representations in string contexts.
7
8#![no_std]
9#![forbid(unsafe_code)]
10#![warn(
11    clippy::all,
12    rust_2018_idioms,
13    unused_lifetimes,
14    unused_qualifications,
15    missing_docs
16)]
17
18extern crate alloc;
19
20use alloc::vec::Vec;
21
22use core::fmt::{Debug, Display, Formatter};
23use core::marker::PhantomData;
24use core::ops::{Deref, DerefMut};
25use core::str::FromStr;
26
27mod sealed {
28    pub trait Config {
29        const CONFIG: base64::Config;
30    }
31}
32
33use sealed::Config;
34
35/// Standard Base64 encoding with padding
36#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct Standard(());
38
39impl Config for Standard {
40    const CONFIG: base64::Config = base64::STANDARD;
41}
42
43/// Standard Base64 encoding without padding
44#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct StandardNoPad(());
46
47impl Config for StandardNoPad {
48    const CONFIG: base64::Config = base64::STANDARD_NO_PAD;
49}
50
51/// URL-safe Base64 encoding with padding
52#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
53pub struct UrlSafe(());
54
55impl Config for UrlSafe {
56    const CONFIG: base64::Config = base64::URL_SAFE;
57}
58
59/// URL-safe Base64 encoding without padding
60#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct UrlSafeNoPad(());
62
63impl Config for UrlSafeNoPad {
64    const CONFIG: base64::Config = base64::URL_SAFE_NO_PAD;
65}
66
67/// A wrapper for bytes which provides base64 encoding in string contexts
68#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct Bytes<T, C = Standard>(T, PhantomData<C>);
70
71impl<T: Debug, C> Debug for Bytes<T, C> {
72    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
73        f.debug_tuple("Bytes").field(&self.0).finish()
74    }
75}
76
77impl<T: Default, C> Default for Bytes<T, C> {
78    fn default() -> Self {
79        Self(Default::default(), PhantomData)
80    }
81}
82
83impl<T, C> Bytes<T, C> {
84    /// Consumes the outer type, returning the inner type
85    pub fn into_inner(self) -> T {
86        self.0
87    }
88}
89
90impl<T, C> From<T> for Bytes<T, C> {
91    fn from(value: T) -> Self {
92        Self(value, PhantomData)
93    }
94}
95
96impl<T: AsRef<U>, U: ?Sized, C> AsRef<U> for Bytes<T, C> {
97    fn as_ref(&self) -> &U {
98        self.0.as_ref()
99    }
100}
101
102impl<T: AsMut<U>, U: ?Sized, C> AsMut<U> for Bytes<T, C> {
103    fn as_mut(&mut self) -> &mut U {
104        self.0.as_mut()
105    }
106}
107
108impl<T, C> Deref for Bytes<T, C> {
109    type Target = T;
110
111    fn deref(&self) -> &Self::Target {
112        &self.0
113    }
114}
115
116impl<T, C> DerefMut for Bytes<T, C> {
117    fn deref_mut(&mut self) -> &mut Self::Target {
118        &mut self.0
119    }
120}
121
122impl<T: AsRef<[u8]>, C: Config> Display for Bytes<T, C> {
123    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
124        f.write_str(&base64::encode_config(self.0.as_ref(), C::CONFIG))
125    }
126}
127
128impl<T: From<Vec<u8>>, C: Config> FromStr for Bytes<T, C> {
129    type Err = base64::DecodeError;
130
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        base64::decode_config(s, C::CONFIG).map(|x| Self(x.into(), PhantomData))
133    }
134}
135
136#[cfg(feature = "serde")]
137impl<T: AsRef<[u8]>, C: Config> serde::Serialize for Bytes<T, C> {
138    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
139        if serializer.is_human_readable() {
140            base64::encode_config(self.0.as_ref(), C::CONFIG).serialize(serializer)
141        } else {
142            serializer.serialize_bytes(self.0.as_ref())
143        }
144    }
145}
146
147#[cfg(feature = "serde")]
148impl<'de, T: From<Vec<u8>>, C: Config> serde::Deserialize<'de> for Bytes<T, C> {
149    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
150        use serde::de::Error;
151
152        if deserializer.is_human_readable() {
153            let b64 = alloc::borrow::Cow::<'de, str>::deserialize(deserializer)?;
154            let buf = base64::decode_config(b64.as_ref(), C::CONFIG)
155                .map_err(|_| D::Error::custom("invalid base64"))?;
156            Ok(Self(buf.into(), PhantomData))
157        } else {
158            Ok(Self(Vec::deserialize(deserializer)?.into(), PhantomData))
159        }
160    }
161}