reifydb_type/value/blob/
base64.rs

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