Skip to main content

flaron_sdk/
request.rs

1//! Read the inbound HTTP request being handled by the flare.
2//!
3//! All four accessors are cheap - the host stores the request bytes once
4//! when the flare is invoked, and these helpers copy slices out of the
5//! per-invocation bump arena.
6
7use crate::{ffi, mem};
8
9/// HTTP method of the inbound request, e.g. `"GET"` or `"POST"`.
10///
11/// Returns an empty string only if the host invoked the flare without an
12/// active request context, which should never happen in production.
13pub fn method() -> String {
14    // SAFETY: host writes a valid UTF-8 method string into the bump arena.
15    unsafe { mem::read_packed_string(ffi::req_method()) }.unwrap_or_default()
16}
17
18/// Full request URL, including path and query string.
19///
20/// Returns an empty string only if there is no active request context.
21pub fn url() -> String {
22    // SAFETY: host writes a valid UTF-8 URL string into the bump arena.
23    unsafe { mem::read_packed_string(ffi::req_url()) }.unwrap_or_default()
24}
25
26/// Look up a request header by (case-insensitive) name.
27///
28/// Returns `None` if the header is absent. Note that the flaron host strips
29/// `Authorization`, `Cookie`, and any `X-Flaron-*` headers unless the flare
30/// is configured with `requires_raw_auth = true`.
31pub fn header(name: &str) -> Option<String> {
32    let (name_ptr, name_len) = mem::host_arg_str(name);
33    // SAFETY: name is a valid borrowed slice in guest memory.
34    let result = unsafe { ffi::req_header_get(name_ptr, name_len) };
35    // SAFETY: host writes the header value (UTF-8) into the bump arena.
36    unsafe { mem::read_packed_string(result) }
37}
38
39/// Read the request body as raw bytes.
40///
41/// Returns an empty `Vec<u8>` when:
42/// * the flare is not configured with `requires_body = true`,
43/// * the request had no body, or
44/// * the body exceeds the host-side 10 MiB cap (the host truncates).
45pub fn body() -> Vec<u8> {
46    // SAFETY: host writes the body bytes into the bump arena.
47    unsafe { mem::read_packed_bytes(ffi::req_body()) }.unwrap_or_default()
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::ffi::test_host;
54
55    #[test]
56    fn method_returns_host_value() {
57        test_host::reset();
58        test_host::with_mock(|m| m.req_method = Some("POST".into()));
59        assert_eq!(method(), "POST");
60    }
61
62    #[test]
63    fn method_empty_when_unset() {
64        test_host::reset();
65        assert_eq!(method(), "");
66    }
67
68    #[test]
69    fn url_returns_host_value() {
70        test_host::reset();
71        test_host::with_mock(|m| m.req_url = Some("https://flaron.dev/path?q=1".into()));
72        assert_eq!(url(), "https://flaron.dev/path?q=1");
73    }
74
75    #[test]
76    fn header_lookup_hits_store() {
77        test_host::reset();
78        test_host::with_mock(|m| {
79            m.req_headers.insert("user-agent".into(), "curl/8.0".into());
80        });
81        assert_eq!(header("user-agent").as_deref(), Some("curl/8.0"));
82        assert!(header("missing").is_none());
83    }
84
85    #[test]
86    fn body_reads_bytes() {
87        test_host::reset();
88        test_host::with_mock(|m| m.req_body = Some(vec![1, 2, 3, 4]));
89        assert_eq!(body(), vec![1, 2, 3, 4]);
90    }
91
92    #[test]
93    fn body_empty_when_unset() {
94        test_host::reset();
95        assert!(body().is_empty());
96    }
97}