Skip to content

Commit 4d9a1ca

Browse files
authored
feat: add bytes type (#3178)
2 parents b0dc70d + 61904c8 commit 4d9a1ca

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

lib/unionlabs/src/bytes.rs

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
use alloc::borrow::Cow;
2+
use core::{cmp::Ordering, fmt, marker::PhantomData, ops::Deref, str::FromStr};
3+
4+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
5+
6+
use crate::hash::hash_v2::{Encoding, HexPrefixed};
7+
8+
pub struct Bytes<E: Encoding = HexPrefixed> {
9+
bytes: Cow<'static, [u8]>,
10+
__marker: PhantomData<fn() -> E>,
11+
}
12+
13+
impl<E: Encoding> AsRef<[u8]> for Bytes<E> {
14+
fn as_ref(&self) -> &[u8] {
15+
self
16+
}
17+
}
18+
19+
impl<E: Encoding> Clone for Bytes<E> {
20+
fn clone(&self) -> Self {
21+
Self::new(self.bytes.clone())
22+
}
23+
}
24+
25+
impl<E: Encoding> core::hash::Hash for Bytes<E> {
26+
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
27+
core::hash::Hash::hash(&**self, state);
28+
}
29+
}
30+
31+
impl<E: Encoding> Bytes<E> {
32+
#[must_use = "constructing a Bytes has no effect"]
33+
pub fn new(bytes: impl Into<Cow<'static, [u8]>>) -> Self {
34+
Self {
35+
bytes: bytes.into(),
36+
__marker: PhantomData,
37+
}
38+
}
39+
40+
#[must_use = "constructing a Bytes has no effect"]
41+
pub const fn new_static(bytes: &'static [u8]) -> Self {
42+
Self {
43+
bytes: Cow::Borrowed(bytes),
44+
__marker: PhantomData,
45+
}
46+
}
47+
48+
pub fn iter(&self) -> core::slice::Iter<'_, u8> {
49+
<&Self as IntoIterator>::into_iter(self)
50+
}
51+
52+
#[must_use = "converting a hash to a hash with a different encoding has no effect"]
53+
#[inline]
54+
pub fn into_encoding<E2: Encoding>(self) -> Bytes<E2> {
55+
Bytes::new(self.bytes)
56+
}
57+
58+
#[must_use = "converting to a vec has no effect"]
59+
pub fn into_vec(self) -> Vec<u8> {
60+
self.bytes.into()
61+
}
62+
}
63+
64+
impl<E: Encoding> Deref for Bytes<E> {
65+
type Target = [u8];
66+
67+
fn deref(&self) -> &Self::Target {
68+
&self.bytes
69+
}
70+
}
71+
72+
impl<E: Encoding> fmt::Debug for Bytes<E> {
73+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74+
f.write_fmt(format_args!("Bytes({self})"))
75+
}
76+
}
77+
78+
impl<E: Encoding> fmt::Display for Bytes<E> {
79+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80+
E::fmt(self, f)
81+
}
82+
}
83+
84+
impl<E: Encoding, RhsE: Encoding> PartialEq<Bytes<RhsE>> for Bytes<E> {
85+
fn eq(&self, other: &Bytes<RhsE>) -> bool {
86+
(**self).eq(&**other)
87+
}
88+
}
89+
90+
impl<E: Encoding> Eq for Bytes<E> {}
91+
92+
impl<E: Encoding, RhsE: Encoding> PartialOrd<Bytes<RhsE>> for Bytes<E> {
93+
fn partial_cmp(&self, other: &Bytes<RhsE>) -> Option<Ordering> {
94+
(**self).partial_cmp(&**other)
95+
}
96+
}
97+
98+
impl<E: Encoding> Ord for Bytes<E> {
99+
fn cmp(&self, other: &Self) -> Ordering {
100+
(**self).cmp(&**other)
101+
}
102+
}
103+
104+
impl<E: Encoding> Serialize for Bytes<E> {
105+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106+
where
107+
S: Serializer,
108+
{
109+
if serializer.is_human_readable() {
110+
serializer.collect_str(self)
111+
} else {
112+
<serde_bytes::Bytes>::new(self).serialize(serializer)
113+
}
114+
}
115+
}
116+
117+
impl<'de, E: Encoding> Deserialize<'de> for Bytes<E> {
118+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
119+
where
120+
D: Deserializer<'de>,
121+
{
122+
if deserializer.is_human_readable() {
123+
String::deserialize(deserializer)
124+
.and_then(|s| s.parse().map_err(::serde::de::Error::custom))
125+
} else {
126+
<&serde_bytes::Bytes>::deserialize(deserializer).map(|b| Bytes::new(b.to_vec()))
127+
}
128+
}
129+
}
130+
131+
impl<E: Encoding> FromStr for Bytes<E> {
132+
type Err = E::Error;
133+
134+
fn from_str(s: &str) -> Result<Self, Self::Err> {
135+
E::decode(s).map(Self::new)
136+
}
137+
}
138+
139+
impl<E: Encoding> Default for Bytes<E> {
140+
fn default() -> Self {
141+
Self::new_static(&[])
142+
}
143+
}
144+
145+
impl<'a, E: Encoding> IntoIterator for &'a Bytes<E> {
146+
type Item = &'a u8;
147+
type IntoIter = core::slice::Iter<'a, u8>;
148+
149+
fn into_iter(self) -> core::slice::Iter<'a, u8> {
150+
(**self).iter()
151+
}
152+
}
153+
154+
impl<E: Encoding> IntoIterator for Bytes<E> {
155+
type Item = u8;
156+
type IntoIter = alloc::vec::IntoIter<u8>;
157+
158+
#[allow(clippy::unnecessary_to_owned)]
159+
fn into_iter(self) -> Self::IntoIter {
160+
self.bytes.to_vec().into_iter()
161+
}
162+
}
163+
164+
impl<E: Encoding> From<Vec<u8>> for Bytes<E> {
165+
fn from(value: Vec<u8>) -> Self {
166+
Self::new(value)
167+
}
168+
}
169+
170+
impl<E: Encoding> From<&Vec<u8>> for Bytes<E> {
171+
fn from(value: &Vec<u8>) -> Self {
172+
Self::new(value.to_owned())
173+
}
174+
}
175+
176+
impl<E: Encoding> From<&[u8]> for Bytes<E> {
177+
fn from(value: &[u8]) -> Self {
178+
Self::new(value.to_owned())
179+
}
180+
}
181+
182+
impl<E: Encoding> From<Bytes<E>> for Vec<u8> {
183+
fn from(value: Bytes<E>) -> Self {
184+
value.deref().into()
185+
}
186+
}
187+
188+
// TODO: Feature gate rlp across the crate
189+
// #[cfg(feature = "rlp")]
190+
impl<E: Encoding> rlp::Decodable for Bytes<E> {
191+
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
192+
rlp.decoder()
193+
.decode_value(|bytes| Ok(Self::new(bytes.to_owned())))
194+
}
195+
}
196+
197+
// TODO: Feature gate rlp across the crate
198+
// #[cfg(feature = "rlp")]
199+
impl<E: Encoding> rlp::Encodable for Bytes<E> {
200+
fn rlp_append(&self, s: &mut ::rlp::RlpStream) {
201+
s.encoder().encode_value(self.as_ref());
202+
}
203+
}
204+
205+
#[cfg(feature = "ethabi")]
206+
impl<E: Encoding> From<alloy::core::primitives::Bytes> for Bytes<E> {
207+
fn from(value: alloy::core::primitives::Bytes) -> Self {
208+
value.0.to_vec().into()
209+
}
210+
}
211+
212+
#[cfg(feature = "ethabi")]
213+
impl<E: Encoding> From<Bytes<E>> for alloy::core::primitives::Bytes {
214+
fn from(value: Bytes<E>) -> Self {
215+
value.deref().to_owned().into()
216+
}
217+
}
218+
219+
#[cfg(test)]
220+
mod tests {
221+
use super::*;
222+
use crate::hash::hash_v2::{Base64, HexUnprefixed};
223+
224+
const BASE64_STR: &str = "YWJjZA==";
225+
const HEX_PREFIXED_STR: &str = "0x61626364";
226+
const HEX_UNPREFIXED_STR: &str = "61626364";
227+
228+
const RAW_VALUE: &[u8; 4] = b"abcd";
229+
230+
#[test]
231+
fn hex_prefixed() {
232+
let decoded = <Bytes<HexPrefixed>>::from_str(HEX_PREFIXED_STR).unwrap();
233+
234+
assert_eq!(HEX_PREFIXED_STR, decoded.to_string());
235+
236+
assert_eq!(&*decoded, b"abcd");
237+
}
238+
239+
#[test]
240+
fn hex_unprefixed() {
241+
let decoded = <Bytes<HexUnprefixed>>::from_str(HEX_UNPREFIXED_STR).unwrap();
242+
243+
assert_eq!(HEX_UNPREFIXED_STR, decoded.to_string());
244+
245+
assert_eq!(&*decoded, b"abcd");
246+
}
247+
248+
#[test]
249+
fn base64() {
250+
let decoded = <Bytes<Base64>>::from_str(BASE64_STR).unwrap();
251+
252+
assert_eq!(BASE64_STR, decoded.to_string());
253+
254+
assert_eq!(&*decoded, RAW_VALUE);
255+
}
256+
}

lib/unionlabs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub mod ics24;
7878

7979
pub mod validated;
8080

81+
pub mod bytes;
8182
pub mod hash;
8283

8384
pub mod encoding;

0 commit comments

Comments
 (0)