Skip to content

Commit 5e0bea6

Browse files
RUST-1225 Add base64 string constructor to Binary (#365)
Co-authored-by: Isabel Atkinson <[email protected]>
1 parent adc440e commit 5e0bea6

File tree

8 files changed

+145
-64
lines changed

8 files changed

+145
-64
lines changed

src/binary.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use crate::{spec::BinarySubtype, Document, RawBinaryRef};
2+
use std::{
3+
convert::TryFrom,
4+
error,
5+
fmt::{self, Display},
6+
};
7+
8+
/// Represents a BSON binary value.
9+
#[derive(Debug, Clone, PartialEq)]
10+
pub struct Binary {
11+
/// The subtype of the bytes.
12+
pub subtype: BinarySubtype,
13+
14+
/// The binary bytes.
15+
pub bytes: Vec<u8>,
16+
}
17+
18+
impl Display for Binary {
19+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
20+
write!(
21+
fmt,
22+
"Binary({:#x}, {})",
23+
u8::from(self.subtype),
24+
base64::encode(&self.bytes)
25+
)
26+
}
27+
}
28+
29+
impl Binary {
30+
/// Creates a [`Binary`] from a base64 string and optional [`BinarySubtype`]. If the
31+
/// `subtype` argument is `None`, the [`Binary`] constructed will default to
32+
/// [`BinarySubtype::Generic`].
33+
///
34+
/// ```rust
35+
/// # use bson::{Binary, binary::Result};
36+
/// # fn example() -> Result<()> {
37+
/// let input = base64::encode("hello");
38+
/// let binary = Binary::from_base64(input, None)?;
39+
/// println!("{:?}", binary);
40+
/// // binary: Binary { subtype: Generic, bytes: [104, 101, 108, 108, 111] }
41+
/// # Ok(())
42+
/// # }
43+
/// ```
44+
pub fn from_base64(
45+
input: impl AsRef<str>,
46+
subtype: impl Into<Option<BinarySubtype>>,
47+
) -> Result<Self> {
48+
let bytes = base64::decode(input.as_ref()).map_err(|e| Error::DecodingError {
49+
message: e.to_string(),
50+
})?;
51+
let subtype = match subtype.into() {
52+
Some(s) => s,
53+
None => BinarySubtype::Generic,
54+
};
55+
Ok(Binary { subtype, bytes })
56+
}
57+
58+
pub(crate) fn from_extended_doc(doc: &Document) -> Option<Self> {
59+
let binary_doc = doc.get_document("$binary").ok()?;
60+
61+
if let Ok(bytes) = binary_doc.get_str("base64") {
62+
let bytes = base64::decode(bytes).ok()?;
63+
let subtype = binary_doc.get_str("subType").ok()?;
64+
let subtype = hex::decode(subtype).ok()?;
65+
if subtype.len() == 1 {
66+
Some(Self {
67+
bytes,
68+
subtype: subtype[0].into(),
69+
})
70+
} else {
71+
None
72+
}
73+
} else {
74+
// in non-human-readable mode, RawBinary will serialize as
75+
// { "$binary": { "bytes": <bytes>, "subType": <i32> } };
76+
let binary = binary_doc.get_binary_generic("bytes").ok()?;
77+
let subtype = binary_doc.get_i32("subType").ok()?;
78+
79+
Some(Self {
80+
bytes: binary.clone(),
81+
subtype: u8::try_from(subtype).ok()?.into(),
82+
})
83+
}
84+
}
85+
86+
/// Borrow the contents as a `RawBinaryRef`.
87+
pub fn as_raw_binary(&self) -> RawBinaryRef<'_> {
88+
RawBinaryRef {
89+
bytes: self.bytes.as_slice(),
90+
subtype: self.subtype,
91+
}
92+
}
93+
}
94+
95+
/// Possible errors that can arise during [`Binary`] construction.
96+
#[derive(Clone, Debug)]
97+
#[non_exhaustive]
98+
pub enum Error {
99+
/// While trying to decode from base64, an error was returned.
100+
DecodingError { message: String },
101+
}
102+
103+
impl error::Error for Error {}
104+
105+
impl std::fmt::Display for Error {
106+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
107+
match self {
108+
Error::DecodingError { message: m } => fmt.write_str(m),
109+
}
110+
}
111+
}
112+
113+
pub type Result<T> = std::result::Result<T, Error>;

src/bson.rs

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ pub use crate::document::Document;
3232
use crate::{
3333
oid::{self, ObjectId},
3434
spec::{BinarySubtype, ElementType},
35+
Binary,
3536
Decimal128,
36-
RawBinaryRef,
3737
};
3838

3939
/// Possible BSON value types.
@@ -1080,65 +1080,6 @@ impl Display for JavaScriptCodeWithScope {
10801080
}
10811081
}
10821082

1083-
/// Represents a BSON binary value.
1084-
#[derive(Debug, Clone, PartialEq)]
1085-
pub struct Binary {
1086-
/// The subtype of the bytes.
1087-
pub subtype: BinarySubtype,
1088-
1089-
/// The binary bytes.
1090-
pub bytes: Vec<u8>,
1091-
}
1092-
1093-
impl Display for Binary {
1094-
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1095-
write!(
1096-
fmt,
1097-
"Binary({:#x}, {})",
1098-
u8::from(self.subtype),
1099-
base64::encode(&self.bytes)
1100-
)
1101-
}
1102-
}
1103-
1104-
impl Binary {
1105-
pub(crate) fn from_extended_doc(doc: &Document) -> Option<Self> {
1106-
let binary_doc = doc.get_document("$binary").ok()?;
1107-
1108-
if let Ok(bytes) = binary_doc.get_str("base64") {
1109-
let bytes = base64::decode(bytes).ok()?;
1110-
let subtype = binary_doc.get_str("subType").ok()?;
1111-
let subtype = hex::decode(subtype).ok()?;
1112-
if subtype.len() == 1 {
1113-
Some(Self {
1114-
bytes,
1115-
subtype: subtype[0].into(),
1116-
})
1117-
} else {
1118-
None
1119-
}
1120-
} else {
1121-
// in non-human-readable mode, RawBinary will serialize as
1122-
// { "$binary": { "bytes": <bytes>, "subType": <i32> } };
1123-
let binary = binary_doc.get_binary_generic("bytes").ok()?;
1124-
let subtype = binary_doc.get_i32("subType").ok()?;
1125-
1126-
Some(Self {
1127-
bytes: binary.clone(),
1128-
subtype: u8::try_from(subtype).ok()?.into(),
1129-
})
1130-
}
1131-
}
1132-
1133-
/// Borrow the contents as a `RawBinaryRef`.
1134-
pub fn as_raw_binary(&self) -> RawBinaryRef<'_> {
1135-
RawBinaryRef {
1136-
bytes: self.bytes.as_slice(),
1137-
subtype: self.subtype,
1138-
}
1139-
}
1140-
}
1141-
11421083
/// Represents a DBPointer. (Deprecated)
11431084
#[derive(Debug, Clone, PartialEq)]
11441085
pub struct DbPointer {

src/de/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ pub use self::{
3333
use std::io::Read;
3434

3535
use crate::{
36-
bson::{Array, Binary, Bson, DbPointer, Document, JavaScriptCodeWithScope, Regex, Timestamp},
36+
bson::{Array, Bson, DbPointer, Document, JavaScriptCodeWithScope, Regex, Timestamp},
3737
oid::{self, ObjectId},
3838
raw::RawBinaryRef,
3939
ser::write_i32,
4040
spec::{self, BinarySubtype},
41+
Binary,
4142
Decimal128,
4243
};
4344

src/de/serde.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ use serde::de::{
2121
use serde_bytes::ByteBuf;
2222

2323
use crate::{
24-
bson::{Binary, Bson, DbPointer, JavaScriptCodeWithScope, Regex, Timestamp},
24+
bson::{Bson, DbPointer, JavaScriptCodeWithScope, Regex, Timestamp},
2525
datetime::DateTime,
2626
document::{Document, IntoIter},
2727
oid::ObjectId,
2828
raw::{RawBsonRef, RAW_ARRAY_NEWTYPE, RAW_BSON_NEWTYPE, RAW_DOCUMENT_NEWTYPE},
2929
spec::BinarySubtype,
3030
uuid::UUID_NEWTYPE_NAME,
31+
Binary,
3132
Decimal128,
3233
};
3334

src/document.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ use indexmap::IndexMap;
1313
use serde::de::Error;
1414

1515
use crate::{
16-
bson::{Array, Binary, Bson, Timestamp},
16+
bson::{Array, Bson, Timestamp},
1717
de::{deserialize_bson_kvp, ensure_read_exactly, read_i32, MIN_BSON_DOCUMENT_SIZE},
1818
oid::ObjectId,
1919
ser::{serialize_bson, write_i32},
2020
spec::BinarySubtype,
21+
Binary,
2122
Decimal128,
2223
};
2324

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@
269269

270270
#[doc(inline)]
271271
pub use self::{
272-
bson::{Array, Binary, Bson, DbPointer, Document, JavaScriptCodeWithScope, Regex, Timestamp},
272+
bson::{Array, Bson, DbPointer, Document, JavaScriptCodeWithScope, Regex, Timestamp},
273273
datetime::DateTime,
274+
binary::Binary,
274275
de::{
275276
from_bson, from_bson_with_options, from_document, from_document_with_options, from_reader,
276277
from_reader_utf8_lossy, from_slice, from_slice_utf8_lossy, Deserializer,
@@ -291,6 +292,7 @@ pub use self::{
291292
#[macro_use]
292293
mod macros;
293294
mod bson;
295+
pub mod binary;
294296
pub mod datetime;
295297
pub mod de;
296298
pub mod decimal128;

src/tests/modules/binary.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use crate::{spec::BinarySubtype, tests::LOCK, Binary};
2+
3+
#[test]
4+
fn binary_from_base64() {
5+
let _guard = LOCK.run_concurrently();
6+
7+
let input = base64::encode("hello");
8+
let produced = Binary::from_base64(input, None).unwrap();
9+
let expected = Binary {
10+
bytes: "hello".as_bytes().to_vec(),
11+
subtype: BinarySubtype::Generic,
12+
};
13+
assert_eq!(produced, expected);
14+
15+
let produced = Binary::from_base64("", BinarySubtype::Uuid).unwrap();
16+
let expected = Binary {
17+
bytes: "".as_bytes().to_vec(),
18+
subtype: BinarySubtype::Uuid,
19+
};
20+
assert_eq!(produced, expected);
21+
}

src/tests/modules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod binary;
12
mod bson;
23
mod document;
34
mod lock;

0 commit comments

Comments
 (0)