Skip to main content

reifydb_type/value/blob/
base64.rs

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