flaron-sdk 0.99.0

Official Rust SDK for writing Flaron edge flares - WebAssembly modules that run on the Flaron CDN edge runtime.
Documentation
//! Read the inbound HTTP request being handled by the flare.
//!
//! All four accessors are cheap - the host stores the request bytes once
//! when the flare is invoked, and these helpers copy slices out of the
//! per-invocation bump arena.

use crate::{ffi, mem};

/// HTTP method of the inbound request, e.g. `"GET"` or `"POST"`.
///
/// Returns an empty string only if the host invoked the flare without an
/// active request context, which should never happen in production.
pub fn method() -> String {
    // SAFETY: host writes a valid UTF-8 method string into the bump arena.
    unsafe { mem::read_packed_string(ffi::req_method()) }.unwrap_or_default()
}

/// Full request URL, including path and query string.
///
/// Returns an empty string only if there is no active request context.
pub fn url() -> String {
    // SAFETY: host writes a valid UTF-8 URL string into the bump arena.
    unsafe { mem::read_packed_string(ffi::req_url()) }.unwrap_or_default()
}

/// Look up a request header by (case-insensitive) name.
///
/// Returns `None` if the header is absent. Note that the flaron host strips
/// `Authorization`, `Cookie`, and any `X-Flaron-*` headers unless the flare
/// is configured with `requires_raw_auth = true`.
pub fn header(name: &str) -> Option<String> {
    let (name_ptr, name_len) = mem::host_arg_str(name);
    // SAFETY: name is a valid borrowed slice in guest memory.
    let result = unsafe { ffi::req_header_get(name_ptr, name_len) };
    // SAFETY: host writes the header value (UTF-8) into the bump arena.
    unsafe { mem::read_packed_string(result) }
}

/// Read the request body as raw bytes.
///
/// Returns an empty `Vec<u8>` when:
/// * the flare is not configured with `requires_body = true`,
/// * the request had no body, or
/// * the body exceeds the host-side 10 MiB cap (the host truncates).
pub fn body() -> Vec<u8> {
    // SAFETY: host writes the body bytes into the bump arena.
    unsafe { mem::read_packed_bytes(ffi::req_body()) }.unwrap_or_default()
}

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

    #[test]
    fn method_returns_host_value() {
        test_host::reset();
        test_host::with_mock(|m| m.req_method = Some("POST".into()));
        assert_eq!(method(), "POST");
    }

    #[test]
    fn method_empty_when_unset() {
        test_host::reset();
        assert_eq!(method(), "");
    }

    #[test]
    fn url_returns_host_value() {
        test_host::reset();
        test_host::with_mock(|m| m.req_url = Some("https://flaron.dev/path?q=1".into()));
        assert_eq!(url(), "https://flaron.dev/path?q=1");
    }

    #[test]
    fn header_lookup_hits_store() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.req_headers.insert("user-agent".into(), "curl/8.0".into());
        });
        assert_eq!(header("user-agent").as_deref(), Some("curl/8.0"));
        assert!(header("missing").is_none());
    }

    #[test]
    fn body_reads_bytes() {
        test_host::reset();
        test_host::with_mock(|m| m.req_body = Some(vec![1, 2, 3, 4]));
        assert_eq!(body(), vec![1, 2, 3, 4]);
    }

    #[test]
    fn body_empty_when_unset() {
        test_host::reset();
        assert!(body().is_empty());
    }
}