geam-stdlib 0.2.0

Official Gleam standard-library providers for Geam
Documentation
use crate::BitArrayValue;
use base64::Engine;
use base64::alphabet;
use base64::engine::general_purpose::{
    GeneralPurpose, GeneralPurposeConfig, STANDARD, STANDARD_NO_PAD,
};
use ecow::EcoString;
use geam_core::provider_support::bit_array_pad_to_bytes;

const ERLANG_BASE64: GeneralPurpose = GeneralPurpose::new(
    &alphabet::STANDARD,
    GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true),
);

pub(in crate::bit_array) fn base64_encode(value: BitArrayValue, padding: bool) -> EcoString {
    let value = bit_array_pad_to_bytes(&value);
    if padding {
        STANDARD.encode(value.bytes()).into()
    } else {
        STANDARD_NO_PAD.encode(value.bytes()).into()
    }
}

pub(in crate::bit_array) fn decode64(value: EcoString) -> Result<BitArrayValue, ()> {
    decode_base64(&value)
        .map(BitArrayValue::from_bytes)
        .map_err(|_| ())
}

pub(in crate::bit_array) fn base16_encode(value: BitArrayValue) -> EcoString {
    hex::encode_upper(bit_array_pad_to_bytes(&value).bytes()).into()
}

pub(in crate::bit_array) fn base16_decode(value: EcoString) -> Result<BitArrayValue, ()> {
    hex::decode(value.as_bytes())
        .map(BitArrayValue::from_bytes)
        .map_err(|_| ())
}

fn decode_base64(value: &str) -> Result<Vec<u8>, base64::DecodeError> {
    let encoded = value
        .bytes()
        .filter(|byte| !matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
        .collect::<Vec<_>>();
    ERLANG_BASE64.decode(encoded)
}

#[cfg(test)]
mod tests {
    use super::{base16_encode, base64_encode, decode_base64};
    use crate::BitArrayValue;

    #[test]
    fn matches_erlang_base64_whitespace_and_trailing_bit_acceptance() {
        assert_eq!(decode_base64("aG  \t\nVsbG8="), Ok(b"hello".to_vec()));
        assert_eq!(decode_base64("AB=="), Ok(vec![0]));
        for invalid in [
            "=", "A===", "AAAA====", "AA=A", "AA==junk", "A", "AA", "AAA",
        ] {
            assert!(decode_base64(invalid).is_err(), "{invalid:?}");
        }
        assert!(decode_base64("aG\u{000c}VsbG8=").is_err());
        assert!(decode_base64("aG\u{000b}VsbG8=").is_err());
    }

    #[test]
    fn zero_pads_unaligned_values_for_encoding() {
        let value = BitArrayValue::try_from_parts(vec![0b1010_0000], 3)
            .expect("three supplied bits should be valid");

        assert_eq!(base64_encode(value.clone(), true), "oA==");
        assert_eq!(base64_encode(value.clone(), false), "oA");
        assert_eq!(base16_encode(value), "A0");
    }
}