flaron-sdk 0.99.0

Official Rust SDK for writing Flaron edge flares - WebAssembly modules that run on the Flaron CDN edge runtime.
Documentation
//! Encoding helpers (base64, hex, URL) backed by the host runtime.
//!
//! These are zero-dep wrappers - calling the host avoids pulling a Rust
//! base64/hex crate into your Wasm binary, which keeps `.wasm` size down.

use crate::{ffi, mem};

/// Base64-encode a byte slice using the standard alphabet (with padding).
pub fn base64_encode(data: &[u8]) -> String {
    let (data_ptr, data_len) = mem::host_arg_bytes(data);
    let result = unsafe { ffi::encoding_base64_encode(data_ptr, data_len) };
    // SAFETY: host writes a UTF-8 base64 string into the bump arena.
    unsafe { mem::read_packed_string(result) }.unwrap_or_default()
}

/// Decode a base64 string into raw bytes.
///
/// Returns `None` if the input is not valid base64.
pub fn base64_decode(input: &str) -> Option<Vec<u8>> {
    let (data_ptr, data_len) = mem::host_arg_str(input);
    let result = unsafe { ffi::encoding_base64_decode(data_ptr, data_len) };
    // SAFETY: host writes the decoded bytes into the bump arena.
    unsafe { mem::read_packed_bytes(result) }
}

/// Hex-encode a byte slice (lowercase, no separators).
pub fn hex_encode(data: &[u8]) -> String {
    let (data_ptr, data_len) = mem::host_arg_bytes(data);
    let result = unsafe { ffi::encoding_hex_encode(data_ptr, data_len) };
    // SAFETY: host writes a UTF-8 hex string into the bump arena.
    unsafe { mem::read_packed_string(result) }.unwrap_or_default()
}

/// Decode a hex string into raw bytes. Accepts upper or lower case.
///
/// Returns `None` if the input is not valid hex.
pub fn hex_decode(input: &str) -> Option<Vec<u8>> {
    let (data_ptr, data_len) = mem::host_arg_str(input);
    let result = unsafe { ffi::encoding_hex_decode(data_ptr, data_len) };
    // SAFETY: host writes the decoded bytes into the bump arena.
    unsafe { mem::read_packed_bytes(result) }
}

/// URL-encode a string using percent-encoding (`application/x-www-form-urlencoded`
/// rules).
pub fn url_encode(input: &str) -> String {
    let (data_ptr, data_len) = mem::host_arg_str(input);
    let result = unsafe { ffi::encoding_url_encode(data_ptr, data_len) };
    // SAFETY: host writes the encoded UTF-8 string into the bump arena.
    unsafe { mem::read_packed_string(result) }.unwrap_or_default()
}

/// URL-decode a percent-encoded string.
///
/// Returns `None` if the input contains invalid percent escapes.
pub fn url_decode(input: &str) -> Option<String> {
    let (data_ptr, data_len) = mem::host_arg_str(input);
    let result = unsafe { ffi::encoding_url_decode(data_ptr, data_len) };
    // SAFETY: host writes the decoded UTF-8 string into the bump arena.
    unsafe { mem::read_packed_string(result) }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ffi::test_host;

    #[test]
    fn base64_encode_passes_input_and_returns_response() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.encoding_base64_encode_response = Some("aGVsbG8=".into());
        });
        assert_eq!(base64_encode(b"hello"), "aGVsbG8=");
        assert_eq!(
            test_host::read_mock(|m| m.last_encoding_base64_encode_input.clone()),
            Some(b"hello".to_vec())
        );
    }

    #[test]
    fn base64_encode_empty_when_host_silent() {
        test_host::reset();
        assert_eq!(base64_encode(b"x"), "");
    }

    #[test]
    fn base64_decode_returns_bytes() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.encoding_base64_decode_response = Some(b"hello".to_vec());
        });
        assert_eq!(base64_decode("aGVsbG8="), Some(b"hello".to_vec()));
    }

    #[test]
    fn base64_decode_none_on_failure() {
        test_host::reset();
        assert!(base64_decode("not base64!").is_none());
    }

    #[test]
    fn hex_encode_round_trip_via_mock() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.encoding_hex_encode_response = Some("deadbeef".into());
            m.encoding_hex_decode_response = Some(vec![0xde, 0xad, 0xbe, 0xef]);
        });
        let encoded = hex_encode(&[0xde, 0xad, 0xbe, 0xef]);
        assert_eq!(encoded, "deadbeef");
        let decoded = hex_decode(&encoded).unwrap();
        assert_eq!(decoded, vec![0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn hex_decode_none_on_failure() {
        test_host::reset();
        assert!(hex_decode("zz").is_none());
    }

    #[test]
    fn url_encode_passes_input() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.encoding_url_encode_response = Some("hello%20world".into());
        });
        assert_eq!(url_encode("hello world"), "hello%20world");
        assert_eq!(
            test_host::read_mock(|m| m.last_encoding_url_encode_input.clone()),
            Some("hello world".into())
        );
    }

    #[test]
    fn url_decode_returns_some() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.encoding_url_decode_response = Some("hello world".into());
        });
        assert_eq!(url_decode("hello%20world").as_deref(), Some("hello world"));
    }

    #[test]
    fn url_decode_none_on_failure() {
        test_host::reset();
        assert!(url_decode("%ZZ").is_none());
    }
}