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