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