use base64ct::{Base64, Base64Url, Base64UrlUnpadded, Encoding};
use uuid::Uuid;
use super::extra_uuid::{new_v4, now_v7};
use super::{Error, support};
pub fn new_v4_b64() -> String {
let uuid = new_v4();
Base64::encode_string(uuid.as_bytes())
}
pub fn new_v4_b64url() -> String {
let uuid = new_v4();
Base64Url::encode_string(uuid.as_bytes())
}
pub fn new_v4_b64url_nopad() -> String {
let uuid = new_v4();
Base64UrlUnpadded::encode_string(uuid.as_bytes())
}
pub fn new_v7_b64() -> String {
let uuid = now_v7();
Base64::encode_string(uuid.as_bytes())
}
pub fn new_v7_b64url() -> String {
let uuid = now_v7();
Base64Url::encode_string(uuid.as_bytes())
}
pub fn new_v7_b64url_nopad() -> String {
let uuid = now_v7();
Base64UrlUnpadded::encode_string(uuid.as_bytes())
}
pub fn from_b64(s: &str) -> Result<Uuid, Error> {
let decoded_bytes = Base64::decode_vec(s).map_err(Error::custom_from_err)?;
support::from_vec_u8(decoded_bytes, "base64")
}
pub fn from_b64url(s: &str) -> Result<Uuid, Error> {
let decoded_bytes = Base64Url::decode_vec(s).map_err(Error::custom_from_err)?;
support::from_vec_u8(decoded_bytes, "base64url")
}
pub fn from_b64url_nopad(s: &str) -> Result<Uuid, Error> {
let decoded_bytes = Base64UrlUnpadded::decode_vec(s).map_err(Error::custom_from_err)?;
support::from_vec_u8(decoded_bytes, "base64url-nopad")
}
#[cfg(test)]
mod tests {
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
use super::*;
use uuid::{Uuid, Version};
#[test]
fn test_extra_base64_new_v4_b64_simple() -> Result<()> {
let b64_uuid = new_v4_b64();
assert_eq!(b64_uuid.len(), 24, "Standard Base64 of UUID should be 24 chars with padding");
assert!(b64_uuid.ends_with("=="), "Standard Base64 should be padded with '==' for 16 bytes");
assert!(
!b64_uuid.contains('-') && !b64_uuid.contains('_'),
"Standard Base64 should not contain URL-safe characters '-' or '_'"
);
let decoded_bytes = Base64::decode_vec(&b64_uuid)?;
let uuid = Uuid::from_bytes(decoded_bytes.try_into().map_err(|_| "Failed to convert vec to array")?);
assert_eq!(uuid.get_version(), Some(Version::Random));
Ok(())
}
#[test]
fn test_extra_base64_new_v4_b64url_simple() -> Result<()> {
let b64url_uuid = new_v4_b64url();
assert_eq!(b64url_uuid.len(), 24, "URL-safe Base64 of UUID should be 24 chars with padding");
assert!(b64url_uuid.ends_with("=="), "URL-safe Base64 with padding should be padded with '==' for 16 bytes");
assert!(!b64url_uuid.contains('+') && !b64url_uuid.contains('/'), "URL-safe Base64 should not contain '+' or '/'");
let decoded_bytes = Base64Url::decode_vec(&b64url_uuid)?;
let uuid = Uuid::from_bytes(decoded_bytes.try_into().map_err(|_| "Failed to convert vec to array")?);
assert_eq!(uuid.get_version(), Some(Version::Random));
Ok(())
}
#[test]
fn test_extra_base64_new_v4_b64url_nopad_simple() -> Result<()> {
let b64url_nopad_uuid = new_v4_b64url_nopad();
assert_eq!(b64url_nopad_uuid.len(), 22, "URL-safe Base64 (no pad) of UUID should be 22 chars");
assert!(!b64url_nopad_uuid.ends_with('='), "URL-safe Base64 (no pad) should not have padding");
assert!(
!b64url_nopad_uuid.contains('+') && !b64url_nopad_uuid.contains('/'),
"URL-safe Base64 (no pad) should not contain '+' or '/'"
);
let decoded_bytes = Base64UrlUnpadded::decode_vec(&b64url_nopad_uuid)?;
let uuid = Uuid::from_bytes(decoded_bytes.try_into().map_err(|_| "Failed to convert vec to array")?);
assert_eq!(uuid.get_version(), Some(Version::Random));
Ok(())
}
#[test]
fn test_extra_base64_new_v7_b64_simple() -> Result<()> {
let b64_uuid = new_v7_b64();
assert_eq!(b64_uuid.len(), 24, "Standard Base64 of V7 UUID should be 24 chars with padding");
assert!(b64_uuid.ends_with("=="), "Standard Base64 should be padded with '==' for 16 bytes");
assert!(
!b64_uuid.contains('-') && !b64_uuid.contains('_'),
"Standard Base64 should not contain URL-safe characters '-' or '_'"
);
let decoded_bytes = Base64::decode_vec(&b64_uuid)?;
let uuid = Uuid::from_bytes(decoded_bytes.try_into().map_err(|_| "Failed to convert vec to array")?);
assert_eq!(uuid.get_version(), Some(Version::SortRand));
Ok(())
}
#[test]
fn test_extra_base64_new_v7_b64url_simple() -> Result<()> {
let b64url_uuid = new_v7_b64url();
assert_eq!(b64url_uuid.len(), 24, "URL-safe Base64 of V7 UUID should be 24 chars with padding");
assert!(b64url_uuid.ends_with("=="), "URL-safe Base64 with padding should be padded with '==' for 16 bytes");
assert!(!b64url_uuid.contains('+') && !b64url_uuid.contains('/'), "URL-safe Base64 should not contain '+' or '/'");
let decoded_bytes = Base64Url::decode_vec(&b64url_uuid)?;
let uuid = Uuid::from_bytes(decoded_bytes.try_into().map_err(|_| "Failed to convert vec to array")?);
assert_eq!(uuid.get_version(), Some(Version::SortRand));
Ok(())
}
#[test]
fn test_extra_base64_new_v7_b64url_nopad_simple() -> Result<()> {
let b64url_nopad_uuid = new_v7_b64url_nopad();
assert_eq!(b64url_nopad_uuid.len(), 22, "URL-safe Base64 (no pad) of V7 UUID should be 22 chars");
assert!(!b64url_nopad_uuid.ends_with('='), "URL-safe Base64 (no pad) should not have padding");
assert!(
!b64url_nopad_uuid.contains('+') && !b64url_nopad_uuid.contains('/'),
"URL-safe Base64 (no pad) should not contain '+' or '/'"
);
let decoded_bytes = Base64UrlUnpadded::decode_vec(&b64url_nopad_uuid)?;
let uuid = Uuid::from_bytes(decoded_bytes.try_into().map_err(|_| "Failed to convert vec to array")?);
assert_eq!(uuid.get_version(), Some(Version::SortRand));
Ok(())
}
#[test]
fn test_extra_base64_from_b64_ok() -> Result<()> {
let original_uuid = Uuid::now_v7();
let b64_string = Base64::encode_string(original_uuid.as_bytes());
let decoded_uuid_res = from_b64(&b64_string);
assert!(decoded_uuid_res.is_ok(), "Decoding should succeed");
assert_eq!(decoded_uuid_res.unwrap(), original_uuid, "Decoded UUID should match original");
Ok(())
}
#[test]
fn test_extra_base64_from_b64_err_invalid_char() -> Result<()> {
let invalid_b64_string = "ThisIsNotValidBase64!=";
let decoded_uuid_res = from_b64(invalid_b64_string);
assert!(decoded_uuid_res.is_err(), "Decoding should fail for invalid characters");
let err_msg = decoded_uuid_res.err().unwrap().to_string();
assert!(
err_msg.contains("invalid") || err_msg.contains("Invalid") || err_msg.contains("symbol"),
"Error message should indicate invalid character, but got: {}",
err_msg
);
Ok(())
}
#[test]
fn test_extra_base64_from_b64_err_wrong_len() -> Result<()> {
let short_b64_string = Base64::encode_string("short".as_bytes());
let decoded_uuid_res = from_b64(&short_b64_string);
assert!(decoded_uuid_res.is_err(), "Decoding should fail for wrong length");
let err_msg = decoded_uuid_res.err().unwrap().to_string();
println!("->> {err_msg}");
assert!(
err_msg.contains("Fail to decode 16U8") && err_msg.contains("base64") && err_msg.contains("actual length: 5"),
"Error message should indicate wrong length, but got: {}",
err_msg
);
Ok(())
}
#[test]
fn test_extra_base64_from_b64url_ok() -> Result<()> {
let original_uuid = Uuid::now_v7();
let b64url_string = Base64Url::encode_string(original_uuid.as_bytes());
let decoded_uuid_res = from_b64url(&b64url_string);
assert!(decoded_uuid_res.is_ok(), "Decoding should succeed");
assert_eq!(decoded_uuid_res.unwrap(), original_uuid, "Decoded UUID should match original");
Ok(())
}
#[test]
fn test_extra_base64_from_b64url_err_invalid_char() -> Result<()> {
let invalid_b64url_string = "ThisIsNotValidBase64Url+";
let decoded_uuid_res = from_b64url(invalid_b64url_string);
assert!(decoded_uuid_res.is_err(), "Decoding should fail for invalid characters");
let err_msg = decoded_uuid_res.err().unwrap().to_string();
assert!(!err_msg.is_empty(), "Error message should not be empty");
Ok(())
}
#[test]
fn test_extra_base64_from_b64url_err_wrong_len() -> Result<()> {
let short_b64url_string = Base64Url::encode_string("short".as_bytes());
let decoded_uuid_res = from_b64url(&short_b64url_string);
assert!(decoded_uuid_res.is_err(), "Decoding should fail for wrong length");
let err_msg = decoded_uuid_res.err().unwrap().to_string();
assert!(
err_msg.contains("Fail to decode 16U8") && err_msg.contains("base64url") && err_msg.contains("actual length: 5"),
"Error message should indicate FailToDecode16U8 for base64url, but got: {}",
err_msg
);
Ok(())
}
#[test]
fn test_extra_base64_from_b64url_nopad_ok() -> Result<()> {
let original_uuid = Uuid::now_v7();
let b64url_nopad_string = Base64UrlUnpadded::encode_string(original_uuid.as_bytes());
let decoded_uuid_res = from_b64url_nopad(&b64url_nopad_string);
assert!(decoded_uuid_res.is_ok(), "Decoding should succeed");
assert_eq!(decoded_uuid_res.unwrap(), original_uuid, "Decoded UUID should match original");
Ok(())
}
#[test]
fn test_extra_base64_from_b64url_nopad_err_invalid_char() -> Result<()> {
let invalid_b64url_nopad_string = "ThisIsNotValidBase64UrlNoPad=";
let decoded_uuid_res = from_b64url_nopad(invalid_b64url_nopad_string);
assert!(decoded_uuid_res.is_err(), "Decoding should fail for invalid characters");
let err_msg = decoded_uuid_res.err().unwrap().to_string();
assert!(!err_msg.is_empty(), "Error message should not be empty");
Ok(())
}
#[test]
fn test_extra_base64_from_b64url_nopad_err_wrong_len() -> Result<()> {
let short_b64url_nopad_string = Base64UrlUnpadded::encode_string("short".as_bytes());
let decoded_uuid_res = from_b64url_nopad(&short_b64url_nopad_string);
assert!(decoded_uuid_res.is_err(), "Decoding should fail for wrong length");
let err_msg = decoded_uuid_res.err().unwrap().to_string();
assert!(
err_msg.contains("Fail to decode 16U8")
&& err_msg.contains("base64url-nopad")
&& err_msg.contains("actual length: 5"),
"Error message should indicate FailToDecode16U8 for base64url-nopad, but got: {}",
err_msg
);
Ok(())
}
}