rust-auth-utils 1.0.0

A rust port of @better-auth/utils.
Documentation
// based on https://github.com/better-auth/utils/blob/main/src/base64.ts

use std::collections::HashMap;

/**
 * Returns the Base64 alphabet based on the encoding type.
 */
fn get_alphabet(url_safe: bool) -> &'static str {
    if url_safe {
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
    } else {
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    }
}

/**
 * Creates a decode map for the given alphabet.
 */
fn create_decode_map(alphabet: &str) -> HashMap<char, u8> {
    alphabet
        .chars()
        .enumerate()
        .map(|(i, c)| (c, i as u8))
        .collect()
}

/**
 * Encodes a slice of bytes into a Base64 string.
 */
fn base64_encode(data: &[u8], alphabet: &str, padding: bool) -> String {
    let mut result = String::new();
    let mut buffer = 0u32;
    let mut shift = 0;

    for &byte in data {
        buffer = (buffer << 8) | (byte as u32);
        shift += 8;
        while shift >= 6 {
            shift -= 6;
            result.push(
                alphabet
                    .chars()
                    .nth(((buffer >> shift) & 0x3f) as usize)
                    .unwrap(),
            );
        }
    }

    if shift > 0 {
        result.push(
            alphabet
                .chars()
                .nth(((buffer << (6 - shift)) & 0x3f) as usize)
                .unwrap(),
        );
    }

    if padding {
        let pad_count = (4 - (result.len() % 4)) % 4;
        result.extend(std::iter::repeat('=').take(pad_count));
    }

    result
}

/**
 * Decodes a Base64 string into a Vec<u8>.
 */
fn base64_decode(data: &str, alphabet: &str) -> Result<Vec<u8>, String> {
    let decode_map = create_decode_map(alphabet);
    let mut result = Vec::new();
    let mut buffer = 0u32;
    let mut bits_collected = 0;

    for c in data.chars().take_while(|&c| c != '=') {
        let value = decode_map
            .get(&c)
            .ok_or_else(|| format!("Invalid Base64 character: {}", c))?;

        buffer = (buffer << 6) | (*value as u32);
        bits_collected += 6;

        if bits_collected >= 8 {
            bits_collected -= 8;
            result.push(((buffer >> bits_collected) & 0xff) as u8);
        }
    }

    Ok(result)
}

#[derive(Default)]
pub struct Base64;

impl Base64 {
    /**
     * Encodes data into a Base64 string.
     */
    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
        let alphabet = get_alphabet(false);
        base64_encode(data, alphabet, padding.unwrap_or(true))
    }

    /**
     * Decodes a Base64 string into a Vec<u8>.
     */
    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
        let url_safe = data.contains('-') || data.contains('_');
        let alphabet = get_alphabet(url_safe);
        base64_decode(data, alphabet)
    }

    /**
     * Encodes a string into a Base64 string.
     */
    pub fn encode_string(data: &str, padding: Option<bool>) -> String {
        Self::encode(data.as_bytes(), padding)
    }

    /**
     * Decodes a Base64 string into a UTF-8 string.
     */
    pub fn decode_string(data: &str) -> Result<String, String> {
        let bytes = Self::decode(data)?;
        String::from_utf8(bytes).map_err(|e| format!("Invalid UTF-8 sequence: {}", e))
    }
}

#[derive(Default)]
pub struct Base64Url;

impl Base64Url {
    /**
     * Encodes data into a Base64URL string.
     */
    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
        let alphabet = get_alphabet(true);
        base64_encode(data, alphabet, padding.unwrap_or(true))
    }

    /**
     * Decodes a Base64URL string into a Vec<u8>.
     */
    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
        let alphabet = get_alphabet(true);
        base64_decode(data, alphabet)
    }

    /**
     * Encodes a string into a Base64URL string.
     */
    pub fn encode_string(data: &str, padding: Option<bool>) -> String {
        Self::encode(data.as_bytes(), padding)
    }

    /**
     * Decodes a Base64URL string into a UTF-8 string.
     */
    pub fn decode_string(data: &str) -> Result<String, String> {
        let bytes = Self::decode(data)?;
        String::from_utf8(bytes).map_err(|e| format!("Invalid UTF-8 sequence: {}", e))
    }
}