Skip to main content

reifydb_type/value/blob/
base64.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use super::Blob;
5use crate::{
6	error::{Error, diagnostic::blob},
7	fragment::Fragment,
8	util::base64::engine::general_purpose,
9};
10
11impl Blob {
12	pub fn from_b64(fragment: Fragment) -> Result<Self, Error> {
13		let fragment = fragment;
14		let b64_str = fragment.text();
15		// Try transaction base64 first, then without padding if it fails
16		match general_purpose::STANDARD.decode(b64_str) {
17			Ok(bytes) => Ok(Blob::new(bytes)),
18			Err(_) => {
19				// Try without padding
20				match general_purpose::STANDARD_NO_PAD.decode(b64_str) {
21					Ok(bytes) => Ok(Blob::new(bytes)),
22					Err(_) => Err(Error(blob::invalid_base64_string(fragment))),
23				}
24			}
25		}
26	}
27
28	pub fn from_b64url(fragment: Fragment) -> Result<Self, Error> {
29		let fragment = fragment;
30		let b64url_str = fragment.text();
31		match general_purpose::URL_SAFE_NO_PAD.decode(b64url_str) {
32			Ok(bytes) => Ok(Blob::new(bytes)),
33			Err(_) => Err(Error(blob::invalid_base64url_string(fragment))),
34		}
35	}
36
37	pub fn to_b64(&self) -> String {
38		general_purpose::STANDARD.encode(self.as_bytes())
39	}
40
41	pub fn to_b64url(&self) -> String {
42		general_purpose::URL_SAFE_NO_PAD.encode(self.as_bytes())
43	}
44}
45
46#[cfg(test)]
47pub mod tests {
48	use super::*;
49	use crate::fragment::Fragment;
50
51	#[test]
52	fn test_from_b64() {
53		let blob = Blob::from_b64(Fragment::testing("SGVsbG8=")).unwrap();
54		assert_eq!(blob.as_bytes(), b"Hello");
55	}
56
57	#[test]
58	fn test_from_b64_no_padding() {
59		let blob = Blob::from_b64(Fragment::testing("SGVsbG8")).unwrap();
60		assert_eq!(blob.as_bytes(), b"Hello");
61	}
62
63	#[test]
64	fn test_from_b64_empty() {
65		let blob = Blob::from_b64(Fragment::testing("")).unwrap();
66		assert_eq!(blob.as_bytes(), b"");
67	}
68
69	#[test]
70	fn test_from_b64_invalid() {
71		let result = Blob::from_b64(Fragment::testing("!!!invalid!!!"));
72		assert!(result.is_err());
73	}
74
75	#[test]
76	fn test_from_b64url() {
77		let blob = Blob::from_b64url(Fragment::testing("SGVsbG8")).unwrap();
78		assert_eq!(blob.as_bytes(), b"Hello");
79	}
80
81	#[test]
82	fn test_from_b64url_invalid() {
83		let result = Blob::from_b64url(Fragment::testing("SGVsbG8=")); // padding not allowed in URL-safe
84		assert!(result.is_err());
85	}
86
87	#[test]
88	fn test_to_b64() {
89		let blob = Blob::new(b"Hello".to_vec());
90		assert_eq!(blob.to_b64(), "SGVsbG8=");
91	}
92
93	#[test]
94	fn test_to_b64url() {
95		let blob = Blob::new(b"Hello".to_vec());
96		assert_eq!(blob.to_b64url(), "SGVsbG8");
97	}
98
99	#[test]
100	fn test_b64_roundtrip() {
101		let original = b"Hello, World! \x00\x01\x02\xFF";
102		let blob = Blob::new(original.to_vec());
103		let b64_str = blob.to_b64();
104		let decoded = Blob::from_b64(Fragment::testing(&b64_str)).unwrap();
105		assert_eq!(decoded.as_bytes(), original);
106	}
107
108	#[test]
109	fn test_b64url_roundtrip() {
110		let original = b"Hello, World! \x00\x01\x02\xFF";
111		let blob = Blob::new(original.to_vec());
112		let b64url_str = blob.to_b64url();
113		let decoded = Blob::from_b64url(Fragment::testing(&b64url_str)).unwrap();
114		assert_eq!(decoded.as_bytes(), original);
115	}
116}