Skip to content

Commit 5ee1057

Browse files
corvusrabusVeykril
authored andcommitted
Add support for borsh
1 parent de2af0d commit 5ee1057

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ all-features = true
1313

1414
[dependencies]
1515
serde = { version = "1.0", optional = true, default-features = false }
16+
borsh = { version = "1.4.0", optional = true, default-features = false }
1617
arbitrary = { version = "1.3", optional = true }
1718

1819
[dev-dependencies]
@@ -22,4 +23,4 @@ serde = { version = "1.0", features = ["derive"] }
2223

2324
[features]
2425
default = ["std"]
25-
std = ["serde?/std"]
26+
std = ["serde?/std", "borsh?/std"]

src/borsh.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::{Repr, SmolStr, INLINE_CAP};
2+
use alloc::string::{String, ToString};
3+
use borsh::io::{Error, ErrorKind, Read, Write};
4+
use borsh::{BorshDeserialize, BorshSerialize};
5+
use core::intrinsics::transmute;
6+
7+
impl BorshSerialize for SmolStr {
8+
fn serialize<W: Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
9+
self.as_str().serialize(writer)
10+
}
11+
}
12+
13+
impl BorshDeserialize for SmolStr {
14+
#[inline]
15+
fn deserialize_reader<R: Read>(reader: &mut R) -> borsh::io::Result<Self> {
16+
let len = u32::deserialize_reader(reader)?;
17+
if (len as usize) < INLINE_CAP {
18+
let mut buf = [0u8; INLINE_CAP];
19+
reader.read_exact(&mut buf[..len as usize])?;
20+
_ = core::str::from_utf8(&buf[..len as usize]).map_err(|err| {
21+
let msg = err.to_string();
22+
Error::new(ErrorKind::InvalidData, msg)
23+
})?;
24+
Ok(SmolStr(Repr::Inline {
25+
len: unsafe { transmute(len as u8) },
26+
buf,
27+
}))
28+
} else {
29+
// u8::vec_from_reader always returns Some on success in current implementation
30+
let vec = u8::vec_from_reader(len, reader)?.ok_or_else(|| {
31+
Error::new(
32+
ErrorKind::Other,
33+
"u8::vec_from_reader unexpectedly returned None".to_string(),
34+
)
35+
})?;
36+
Ok(SmolStr::from(String::from_utf8(vec).map_err(|err| {
37+
let msg = err.to_string();
38+
Error::new(ErrorKind::InvalidData, msg)
39+
})?))
40+
}
41+
}
42+
}
43+
44+
#[cfg(feature = "borsh/unstable__schema")]
45+
mod schema {
46+
use alloc::collections::BTreeMap;
47+
use borsh::schema::{Declaration, Definition};
48+
use borsh::BorshSchema;
49+
impl BorshSchema for SmolStr {
50+
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
51+
str::add_definitions_recursively(definitions)
52+
}
53+
54+
fn declaration() -> Declaration {
55+
str::declaration()
56+
}
57+
}
58+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,5 +795,7 @@ impl<'a> arbitrary::Arbitrary<'a> for SmolStr {
795795
}
796796
}
797797

798+
#[cfg(feature = "borsh")]
799+
mod borsh;
798800
#[cfg(feature = "serde")]
799801
mod serde;

tests/test.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,55 @@ mod test_str_ext {
348348
assert!(!result.is_heap_allocated());
349349
}
350350
}
351+
#[cfg(feature = "borsh")]
352+
353+
mod borsh_tests {
354+
use borsh::BorshDeserialize;
355+
use smol_str::{SmolStr, ToSmolStr};
356+
use std::io::Cursor;
357+
358+
#[test]
359+
fn borsh_serialize_stack() {
360+
let smolstr_on_stack = "aßΔCaßδc".to_smolstr();
361+
let mut buffer = Vec::new();
362+
borsh::BorshSerialize::serialize(&smolstr_on_stack, &mut buffer).unwrap();
363+
let mut cursor = Cursor::new(buffer);
364+
let decoded: SmolStr = borsh::BorshDeserialize::deserialize_reader(&mut cursor).unwrap();
365+
assert_eq!(smolstr_on_stack, decoded);
366+
}
367+
#[test]
368+
fn borsh_serialize_heap() {
369+
let smolstr_on_heap = "aßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδc".to_smolstr();
370+
let mut buffer = Vec::new();
371+
borsh::BorshSerialize::serialize(&smolstr_on_heap, &mut buffer).unwrap();
372+
let mut cursor = Cursor::new(buffer);
373+
let decoded: SmolStr = borsh::BorshDeserialize::deserialize_reader(&mut cursor).unwrap();
374+
assert_eq!(smolstr_on_heap, decoded);
375+
}
376+
#[test]
377+
fn borsh_non_utf8_stack() {
378+
let invalid_utf8: Vec<u8> = vec![0xF0, 0x9F, 0x8F]; // Incomplete UTF-8 sequence
379+
380+
let wrong_utf8 = SmolStr::from(unsafe { String::from_utf8_unchecked(invalid_utf8) });
381+
let mut buffer = Vec::new();
382+
borsh::BorshSerialize::serialize(&wrong_utf8, &mut buffer).unwrap();
383+
let mut cursor = Cursor::new(buffer);
384+
let result = SmolStr::deserialize_reader(&mut cursor);
385+
assert!(result.is_err());
386+
}
387+
388+
#[test]
389+
fn borsh_non_utf8_heap() {
390+
let invalid_utf8: Vec<u8> = vec![
391+
0xC1, 0x8A, 0x5F, 0xE2, 0x3A, 0x9E, 0x3B, 0xAA, 0x01, 0x08, 0x6F, 0x2F, 0xC0, 0x32,
392+
0xAB, 0xE1, 0x9A, 0x2F, 0x4A, 0x3F, 0x25, 0x0D, 0x8A, 0x2A, 0x19, 0x11, 0xF0, 0x7F,
393+
0x0E, 0x80,
394+
];
395+
let wrong_utf8 = SmolStr::from(unsafe { String::from_utf8_unchecked(invalid_utf8) });
396+
let mut buffer = Vec::new();
397+
borsh::BorshSerialize::serialize(&wrong_utf8, &mut buffer).unwrap();
398+
let mut cursor = Cursor::new(buffer);
399+
let result = SmolStr::deserialize_reader(&mut cursor);
400+
assert!(result.is_err());
401+
}
402+
}

0 commit comments

Comments
 (0)