drawbridge_byte/
lib.rs

1// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
2// SPDX-License-Identifier: Apache-2.0
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, clippy::expect_used, clippy::panic)]
10#![deny(
11    clippy::all,
12    absolute_paths_not_starting_with_crate,
13    deprecated_in_future,
14    missing_copy_implementations,
15    missing_debug_implementations,
16    missing_docs,
17    noop_method_call,
18    rust_2018_compatibility,
19    rust_2018_idioms,
20    rust_2021_compatibility,
21    single_use_lifetimes,
22    trivial_bounds,
23    trivial_casts,
24    trivial_numeric_casts,
25    unreachable_code,
26    unreachable_patterns,
27    unreachable_pub,
28    unstable_features,
29    unused,
30    unused_crate_dependencies,
31    unused_import_braces,
32    unused_lifetimes,
33    unused_qualifications,
34    unused_results,
35    variant_size_differences
36)]
37
38extern crate alloc;
39
40use alloc::vec::Vec;
41
42use base64::Engine;
43use core::fmt::{Debug, Display, Formatter};
44use core::marker::PhantomData;
45use core::ops::{Deref, DerefMut};
46use core::str::FromStr;
47
48use base64::engine::general_purpose::GeneralPurpose;
49use base64::engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
50
51mod sealed {
52    pub trait Config {
53        const CONFIG: base64::engine::general_purpose::GeneralPurpose;
54    }
55}
56
57use sealed::Config;
58
59/// Standard Base64 encoding with padding
60#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct Standard(());
62
63impl Config for Standard {
64    const CONFIG: GeneralPurpose = STANDARD;
65}
66
67/// Standard Base64 encoding without padding
68#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct StandardNoPad(());
70
71impl Config for StandardNoPad {
72    const CONFIG: GeneralPurpose = STANDARD_NO_PAD;
73}
74
75/// URL-safe Base64 encoding with padding
76#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub struct UrlSafe(());
78
79impl Config for UrlSafe {
80    const CONFIG: GeneralPurpose = URL_SAFE;
81}
82
83/// URL-safe Base64 encoding without padding
84#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
85pub struct UrlSafeNoPad(());
86
87impl Config for UrlSafeNoPad {
88    const CONFIG: GeneralPurpose = URL_SAFE_NO_PAD;
89}
90
91/// A wrapper for bytes which provides base64 encoding in string contexts
92#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93pub struct Bytes<T, C = Standard>(T, PhantomData<C>);
94
95impl<T: Debug, C> Debug for Bytes<T, C> {
96    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
97        f.debug_tuple("Bytes").field(&self.0).finish()
98    }
99}
100
101impl<T: Default, C> Default for Bytes<T, C> {
102    fn default() -> Self {
103        Self(Default::default(), PhantomData)
104    }
105}
106
107impl<T, C> Bytes<T, C> {
108    /// Consumes the outer type, returning the inner type
109    pub fn into_inner(self) -> T {
110        self.0
111    }
112}
113
114impl<T, C> From<T> for Bytes<T, C> {
115    fn from(value: T) -> Self {
116        Self(value, PhantomData)
117    }
118}
119
120impl<T: AsRef<U>, U: ?Sized, C> AsRef<U> for Bytes<T, C> {
121    fn as_ref(&self) -> &U {
122        self.0.as_ref()
123    }
124}
125
126impl<T: AsMut<U>, U: ?Sized, C> AsMut<U> for Bytes<T, C> {
127    fn as_mut(&mut self) -> &mut U {
128        self.0.as_mut()
129    }
130}
131
132impl<T, C> Deref for Bytes<T, C> {
133    type Target = T;
134
135    fn deref(&self) -> &Self::Target {
136        &self.0
137    }
138}
139
140impl<T, C> DerefMut for Bytes<T, C> {
141    fn deref_mut(&mut self) -> &mut Self::Target {
142        &mut self.0
143    }
144}
145
146impl<T: AsRef<[u8]>, C: Config> Display for Bytes<T, C> {
147    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
148        f.write_str(&C::CONFIG.encode(self.0.as_ref()))
149    }
150}
151
152impl<T: From<Vec<u8>>, C: Config> FromStr for Bytes<T, C> {
153    type Err = base64::DecodeError;
154
155    fn from_str(s: &str) -> Result<Self, Self::Err> {
156        C::CONFIG.decode(s).map(|x| Self(x.into(), PhantomData))
157    }
158}
159
160#[cfg(feature = "serde")]
161impl<T: AsRef<[u8]>, C: Config> serde::Serialize for Bytes<T, C> {
162    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
163        if serializer.is_human_readable() {
164            C::CONFIG.encode(self.0.as_ref()).serialize(serializer)
165        } else {
166            serializer.serialize_bytes(self.0.as_ref())
167        }
168    }
169}
170
171#[cfg(feature = "serde")]
172impl<'de, T: From<Vec<u8>>, C: Config> serde::Deserialize<'de> for Bytes<T, C> {
173    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
174        use serde::de::Error;
175
176        if deserializer.is_human_readable() {
177            let b64 = alloc::borrow::Cow::<'de, str>::deserialize(deserializer)?;
178            let buf = C::CONFIG
179                .decode(b64.as_ref())
180                .map_err(|_| Error::custom("invalid base64"))?;
181            Ok(Self(buf.into(), PhantomData))
182        } else {
183            Ok(Self(Vec::deserialize(deserializer)?.into(), PhantomData))
184        }
185    }
186}