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}