Skip to main content

varnish_sys/vcl/
ctx.rs

1//! Expose the Varnish context [`vrt_ctx`] as a Rust object
2//!
3use std::ffi::{c_int, c_uint, c_void, CStr};
4use std::io::{self, Write};
5use std::net::SocketAddr;
6
7use crate::ffi;
8use crate::ffi::{vrt_ctx, VRT_call, VRT_check_call, VRT_fail, VRT_handled, VRT_CTX_MAGIC};
9use crate::vcl::{
10    sc_to_ptr, subroutine::Subroutine, Acl, HttpHeaders, LogTag, StreamClose, TestWS, VclError,
11    VclResult, Workspace,
12};
13
14/// VCL context
15///
16/// A mutable reference to this structure is always passed to vmod functions and provides access to
17/// the available HTTP objects, as well as the workspace.
18///
19/// This struct is a pure Rust structure, mirroring some of the C fields, so you should always use
20/// the provided methods to interact with them. If they are not enough, the `raw` field is actually
21/// the C original pointer that can be used to directly, and unsafely, act on the structure.
22///
23/// Which `http_*` are present will depend on which VCL sub routine the function is called from.
24///
25/// ``` rust
26/// # mod varnish { pub use varnish_sys::vcl; }
27/// use varnish::vcl::Ctx;
28///
29/// fn foo(ctx: &Ctx) {
30///     if let Some(ref req) = ctx.http_req {
31///         for (name, value) in req {
32///             println!("header {name} has value {value:?}");
33///         }
34///     }
35/// }
36/// ```
37#[derive(Debug)]
38pub struct Ctx<'a> {
39    pub raw: &'a mut vrt_ctx,
40    pub http_req: Option<HttpHeaders<'a>>,
41    pub http_req_top: Option<HttpHeaders<'a>>,
42    pub http_resp: Option<HttpHeaders<'a>>,
43    pub http_bereq: Option<HttpHeaders<'a>>,
44    pub http_beresp: Option<HttpHeaders<'a>>,
45    pub ws: Workspace<'a>,
46
47    req: Option<Req<'a>>,
48}
49
50/// The state of a request or response body, mirroring Varnish's `body_status_t`
51/// (see `tbl/body_status.h`).
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum BodyState {
54    /// No body.
55    None,
56    /// An error occurred while producing/reading the body.
57    Error,
58    /// Chunked transfer encoding; the length isn't known upfront.
59    Chunked,
60    /// A known length (e.g. `Content-Length`).
61    Length,
62    /// The body ends when the connection closes; the length isn't known upfront.
63    Eof,
64    /// The body has already been consumed by someone else.
65    Taken,
66    /// The body has been fully read and is cached as an object.
67    Cached,
68}
69
70impl BodyState {
71    fn from_raw(ptr: ffi::body_status_t) -> Self {
72        unsafe {
73            if ptr == ffi::BS_NONE.as_ptr() {
74                Self::None
75            } else if ptr == ffi::BS_ERROR.as_ptr() {
76                Self::Error
77            } else if ptr == ffi::BS_CHUNKED.as_ptr() {
78                Self::Chunked
79            } else if ptr == ffi::BS_LENGTH.as_ptr() {
80                Self::Length
81            } else if ptr == ffi::BS_EOF.as_ptr() {
82                Self::Eof
83            } else if ptr == ffi::BS_TAKEN.as_ptr() {
84                Self::Taken
85            } else if ptr == ffi::BS_CACHED.as_ptr() {
86                Self::Cached
87            } else {
88                unreachable!("unknown body_status_t")
89            }
90        }
91    }
92}
93
94/// State threaded through Varnish's body-iterate C callback via its `priv_` pointer.
95///
96/// Used by [`Ctx::req_body`].
97struct BodyWriterState<'w, W: Write> {
98    writer: &'w mut W,
99    error: Option<io::Error>,
100}
101
102/// Bridges Varnish's body-iterate callback (`objiterate_f`) to `W::write_all`.
103///
104/// Monomorphized per `W`. `ObjIterate`/`VRB_Iterate` call this synchronously
105/// and sequentially, so at most one `&mut` derived from `priv_` is ever live,
106/// and it never outlives that call. Used by [`Ctx::req_body`].
107///
108/// The `ptr.is_null() || len <= 0` guard below means `W::write_all` (and thus
109/// a caller's own `Write` impl) is never invoked with an empty buffer through
110/// this path — callers of [`Ctx::req_body`] can rely on that when implementing
111/// `W`.
112unsafe extern "C" fn write_body_iterate<W: Write>(
113    priv_: *mut c_void,
114    _flush: c_uint,
115    ptr: *const c_void,
116    len: isize,
117) -> c_int {
118    if ptr.is_null() || len <= 0 {
119        return 0;
120    }
121    let writer_state = priv_
122        .cast::<BodyWriterState<W>>()
123        .as_mut()
124        .expect("body-iterate callback priv pointer must not be null");
125    let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
126    match writer_state.writer.write_all(buf) {
127        Ok(()) => 0,
128        Err(e) => {
129            writer_state.error = Some(e);
130            1
131        }
132    }
133}
134
135/// Resolves a completed `VRB_Iterate` call into [`Ctx::req_body`]'s return value: a
136/// captured writer error always takes priority over the raw C return code (`rv < 0`
137/// signals a stream-side failure instead).
138fn resolve_iterate_result<W: Write>(
139    rv: isize,
140    writer_state: &mut BodyWriterState<'_, W>,
141    stream_err: &'static str,
142) -> VclResult<bool> {
143    if let Some(e) = writer_state.error.take() {
144        return Err(e.to_string().into());
145    }
146    if rv < 0 {
147        return Err(stream_err.into());
148    }
149    Ok(true)
150}
151
152impl<'a> Ctx<'a> {
153    /// Wrap a raw pointer into an object we can use.
154    ///
155    /// The pointer must be non-null, and the magic must match
156    pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
157        Self::from_ref(
158            ptr.cast_mut()
159                .as_mut()
160                .expect("vrt_ctx pointer must not be null"),
161        )
162    }
163
164    /// Instantiate from a mutable reference to a [`vrt_ctx`].
165    pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
166        assert_eq!(raw.magic, VRT_CTX_MAGIC);
167        Self {
168            http_req: HttpHeaders::from_ptr(raw.http_req),
169            http_req_top: HttpHeaders::from_ptr(raw.http_req_top),
170            http_resp: HttpHeaders::from_ptr(raw.http_resp),
171            http_bereq: HttpHeaders::from_ptr(raw.http_bereq),
172            http_beresp: HttpHeaders::from_ptr(raw.http_beresp),
173            ws: Workspace::from_ptr(raw.ws),
174            req: unsafe { Req::from_ptr(raw.req) },
175            raw,
176        }
177    }
178
179    /// Log an error message and fail the current VSL task.
180    ///
181    /// Once the control goes back to Varnish, it will see that the transaction was marked as fail
182    /// and will return a synthetic error to the client.
183    pub fn fail(&mut self, msg: impl Into<VclError>) {
184        let msg = msg.into();
185        let msg = msg.as_str();
186        unsafe {
187            VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
188        }
189    }
190
191    /// Log a message, attached to the current context
192    pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
193        unsafe {
194            let vsl = self.raw.vsl;
195            if vsl.is_null() {
196                log(tag, msg);
197            } else {
198                let msg = ffi::txt::from_str(msg.as_ref());
199                ffi::VSLbt(vsl, tag, msg);
200            }
201        }
202    }
203
204    /// Match an ACL against a provided address.
205    pub fn acl_match(&self, acl: &Acl, addr: SocketAddr) -> bool {
206        assert!(!acl.raw.0.is_null());
207
208        unsafe {
209            let mut sa_buf = vec![0u8; ffi::vsa_suckaddr_len];
210            crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
211            ffi::VRT_acl_match(self.raw, acl.raw, ffi::VCL_IP(sa_buf.as_ptr().cast())) == 1
212        }
213    }
214
215    /// Return the name of the listener socket that received the current request.
216    ///
217    /// This corresponds to the VCL variable `local.socket` and returns the `-a` socket
218    /// name (e.g., `"a0"`, `"http-80"`). Returns an `Err` in backend context where the
219    /// session isn't available, or if the name is non-UTF-8.
220    pub fn local_socket(&self) -> Result<&'a str, VclError> {
221        // we're breaking abstraction here, but the other ways are to just reimplement the
222        // whole logic in rust (which is admittedly short), or to let the user crash
223        if self.raw.req.is_null() && self.raw.bo.is_null() {
224            return Err("local.socket isn't available in this context".into());
225        }
226        let raw = unsafe { ffi::VRT_r_local_socket(self.raw) };
227        let cstr = <&CStr>::from(raw);
228        Ok(cstr.to_str()?)
229    }
230
231    /// Return the address of the local endpoint that received the current request.
232    ///
233    /// This corresponds to the VCL variable `local.endpoint` and returns the address
234    /// string (e.g., `"127.0.0.1:8080"`, `"/var/run/varnish.sock"`). Returns an `Err` in
235    /// backend context where the session isn't available, or if the value is non-UTF-8.
236    // same notes as for local_socket
237    pub fn local_endpoint(&self) -> Result<&'a str, VclError> {
238        if self.raw.req.is_null() && self.raw.bo.is_null() {
239            return Err("local.endpoint isn't available in this context".into());
240        }
241        let raw = unsafe { ffi::VRT_r_local_endpoint(self.raw) };
242        let cstr = <&CStr>::from(raw);
243        Ok(cstr.to_str()?)
244    }
245
246    /// Call a VCL subroutine.
247    ///
248    /// Returns `Ok(true)` if the request was handled after the call, `Ok(false)` otherwise.
249    /// Returns `Err` if the subroutine cannot be called in the current context (e.g. wrong VCL
250    /// state or incompatible subroutine type).
251    /// If `Ok(true)` was returned, no other subroutine can be called, and doing so will result
252    /// in a VCL error.
253    pub fn call_sub(&mut self, sub: Subroutine) -> Result<bool, VclError> {
254        self.check_call_sub(sub)?;
255        unsafe { VRT_call(self.raw, sub.vcl_ptr()) };
256        Ok(self.is_handled())
257    }
258
259    /// Check whether a VCL subroutine can be called in the current context.
260    ///
261    /// Returns `Ok(())` if the call is valid, or `Err` with the reason otherwise.
262    pub fn check_call_sub(&self, sub: Subroutine) -> Result<(), VclError> {
263        let result = unsafe { VRT_check_call(self.raw, sub.vcl_ptr()) };
264        if result.0.is_null() {
265            Ok(())
266        } else {
267            let msg = unsafe { CStr::from_ptr(result.0) }
268                .to_string_lossy()
269                .into_owned();
270            Err(VclError::new(msg))
271        }
272    }
273
274    /// Returns `true` if the current request has already been handled.
275    /// If `true`, no other subroutine can be called, and doing so will result
276    /// in a VCL error.
277    pub fn is_handled(&self) -> bool {
278        unsafe { VRT_handled(self.raw) != 0 }
279    }
280
281    /// Retrieve the cached request body as a list of byte slices.
282    ///
283    /// Returns slices pointing into the workspace; each slice is a contiguous chunk of the body.
284    /// Fails if the body has not been cached (i.e. `std.cache_req_body()` was not called in VCL
285    /// before this subroutine ran).
286    pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
287        unsafe extern "C" fn chunk_collector(
288            priv_: *mut c_void,
289            _flush: c_uint,
290            ptr: *const c_void,
291            len: isize,
292        ) -> c_int {
293            let v = priv_
294                .cast::<Vec<&[u8]>>()
295                .as_mut()
296                .expect("cached_req_body callback priv pointer must not be null");
297            let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
298            v.push(buf);
299            0
300        }
301
302        let req = &mut *self.req.as_mut().ok_or("req object isn't available")?.raw;
303        unsafe {
304            if req.req_body_status != ffi::BS_CACHED.as_ptr() {
305                return Err("request body hasn't been previously cached".into());
306            }
307        }
308        let mut v: Box<Vec<&'a [u8]>> = Box::default();
309        let p: *mut Vec<&'a [u8]> = &raw mut *v;
310        match unsafe {
311            ffi::VRB_Iterate(
312                req.wrk,
313                req.vsl.as_mut_ptr(),
314                req,
315                Some(chunk_collector),
316                p.cast::<c_void>(),
317            )
318        } {
319            0 => Ok(*v),
320            _ => Err("req.body iteration failed".into()),
321        }
322    }
323
324    /// Return the current state of the request body — `bereq`'s body from a
325    /// backend context, or the client `req`'s body directly if called earlier
326    /// (`vcl_recv` and later, before any backend is involved).
327    ///
328    /// From backend context (busyobj set - typically
329    /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response)): if
330    /// the body has already been cached as an object (e.g. after
331    /// `std.cache_req_body()`, or on a fetch retry), returns
332    /// [`BodyState::Cached`]; otherwise reflects the live client body's state,
333    /// or [`BodyState::None`] if there is no client request to read from.
334    ///
335    /// From client context (no busyobj yet, e.g. `vcl_recv`/`vcl_hash`): reflects
336    /// `req`'s own state directly - [`BodyState::Cached`] after
337    /// `std.cache_req_body()`, otherwise whatever the live, not-yet-consumed
338    /// client body's state is.
339    pub fn req_body_state(&self) -> VclResult<BodyState> {
340        if let Some(bo) = unsafe { self.raw.bo.as_ref() } {
341            // mirrors V1F_SendReq's `AZ(bo->req)` in its `bo->bereq_body != NULL`
342            // branch: a live `bo.req` and an already-cached `bo.bereq_body` are
343            // normally mutually exclusive by the time a backend runs, but prefer
344            // the cached body rather than assert the invariant, so an unexpected
345            // combination degrades gracefully instead of panicking the worker.
346            if !bo.bereq_body.is_null() {
347                return Ok(BodyState::Cached);
348            }
349            if !bo.req.is_null() {
350                return Ok(BodyState::from_raw(unsafe { (*bo.req).req_body_status }));
351            }
352            return Ok(BodyState::None);
353        }
354        if let Some(req) = self.req.as_ref() {
355            return Ok(BodyState::from_raw(req.raw.req_body_status));
356        }
357        Err("req.body/bereq.body isn't available in this context".into())
358    }
359
360    /// Copy the request body into `writer` — `bereq`'s body from a backend
361    /// context, or the client `req`'s body directly if called earlier
362    /// (`vcl_recv` and later, before any backend is involved).
363    ///
364    /// From backend context (busyobj set - typically
365    /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response)):
366    /// transparently handles both a body already cached as an object (e.g.
367    /// after `std.cache_req_body()`, or on a fetch retry) and a body streamed
368    /// live from the client, hiding the underlying `ObjIterate`/`VRB_Iterate`
369    /// choice and bookkeeping.
370    ///
371    /// From client context (no busyobj yet, e.g. `vcl_recv`/`vcl_hash`): reads
372    /// `req`'s body directly, same `ObjIterate`/`VRB_Iterate` machinery, minus
373    /// the busyobj-specific `no_retry`/`doclose` bookkeeping (there's no fetch
374    /// yet to retry or close).
375    ///
376    /// **Read-once tradeoff**: per Varnish's own rule, an uncached body can be
377    /// read exactly once - either by you here, or later by whatever backend
378    /// ends up handling this request (a custom
379    /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response),
380    /// or a plain upstream backend forwarding it). Read it once in `vcl_recv`
381    /// without caching first, and that *later* read fails
382    /// (`BodyState::Taken`/an error), not this one. Call `std.cache_req_body()`
383    /// before reading if the body needs to survive for a backend (or a retry)
384    /// to read too - check first if you're not sure:
385    ///
386    /// ```
387    /// # mod varnish { pub use varnish_sys::vcl; }
388    /// # use varnish::vcl::{Ctx, BodyState};
389    /// # fn f(ctx: &mut Ctx) -> Result<(), Box<dyn std::error::Error>> {
390    /// match ctx.req_body_state()? {
391    ///     BodyState::Cached => {
392    ///         // safe: a cached body can be read here and still be read again
393    ///         // later (by a backend, or after a retry).
394    ///         let mut buf = Vec::new();
395    ///         ctx.req_body(&mut buf)?;
396    ///     }
397    ///     BodyState::None => {
398    ///         // no body at all - nothing to read, nothing to worry about.
399    ///     }
400    ///     BodyState::Length | BodyState::Chunked | BodyState::Eof => {
401    ///         // live and not cached: reading now consumes it. Only do this if
402    ///         // you're sure no backend/retry downstream also needs it, or call
403    ///         // `std.cache_req_body()` first if they might.
404    ///     }
405    ///     BodyState::Taken | BodyState::Error => {
406    ///         // already gone (consumed elsewhere) or failed - nothing left to read.
407    ///     }
408    /// }
409    /// # Ok(()) }
410    /// ```
411    ///
412    /// To consume the body without keeping it, pass [`std::io::sink()`] as `writer`.
413    ///
414    /// `writer` is only ever fed non-empty chunks — a custom `Write` impl
415    /// doesn't need to handle a zero-length `buf` in its `write`.
416    ///
417    /// Returns `Ok(false)` without touching `writer` if there is no body at all.
418    /// Returns `Ok(true)` once the full body has been copied into `writer`.
419    /// Returns `Err(_)` if called outside both a backend and a client context,
420    /// if the body stream itself failed to read, or if `writer` errors (the
421    /// `io::Error` is captured and turned into a [`VclError`]). Mirroring
422    /// upstream's `V1F_SendReq`, a failure while iterating an already-cached
423    /// body is not treated as fatal on its own (only a `writer` error is).
424    ///
425    /// Side effect (backend context only): if the body isn't already cached,
426    /// reading it marks the fetch as non-retryable (`bo.no_retry`), mirroring
427    /// `V1F_SendReq` — call `std.cache_req_body()` in `vcl_recv` first if the
428    /// backend may need to retry after reading the body.
429    pub fn req_body<W: Write>(&mut self, writer: &mut W) -> VclResult<bool> {
430        let state = self.req_body_state()?;
431        if state == BodyState::None {
432            return Ok(false);
433        }
434
435        if self.raw.bo.is_null() {
436            // client context: no busyobj yet, so none of the backend-only
437            // no_retry/doclose/err_code bookkeeping below applies - there's no
438            // fetch yet to retry or close. Read the read-once tradeoff warning
439            // above before relying on this branch.
440            let req = &mut *self
441                .req
442                .as_mut()
443                .ok_or("req.body/bereq.body isn't available in this context")?
444                .raw;
445            let mut writer_state = BodyWriterState {
446                writer,
447                error: None,
448            };
449            let state_ptr: *mut BodyWriterState<W> = &raw mut writer_state;
450            let rv = unsafe {
451                ffi::VRB_Iterate(
452                    req.wrk,
453                    req.vsl.as_mut_ptr(),
454                    req,
455                    Some(write_body_iterate::<W>),
456                    state_ptr.cast::<c_void>(),
457                )
458            };
459            return resolve_iterate_result(rv, &mut writer_state, "req.body read error");
460        }
461
462        let bo = unsafe { self.raw.bo.as_mut() }
463            .ok_or("bereq.body isn't available in this context (not a backend fetch)")?;
464
465        let mut writer_state = BodyWriterState {
466            writer,
467            error: None,
468        };
469        let state_ptr: *mut BodyWriterState<W> = &raw mut writer_state;
470
471        if state == BodyState::Cached {
472            // a previously-cached bereq.body (e.g. via std.cache_req_body(), or a
473            // retried fetch) takes priority, mirroring V1F_SendReq. Its return
474            // value is deliberately ignored, same as upstream's `(void)ObjIterate(...)`
475            // in V1F_SendReq: it conflates "callback stopped iteration" with an
476            // internal storage error, and upstream never treats it as fatal here.
477            // Untested: a failing cached-storage iteration isn't easily forced
478            // from VCL, so this specific claim rests on reading V1F_SendReq, not
479            // on a `.vtc` reproduction.
480            unsafe {
481                ffi::ObjIterate(
482                    bo.wrk,
483                    bo.bereq_body,
484                    state_ptr.cast::<c_void>(),
485                    Some(write_body_iterate::<W>),
486                    0,
487                );
488            }
489            if let Some(e) = writer_state.error.take() {
490                return Err(e.to_string().into());
491            }
492            Ok(true)
493        } else {
494            let rv = unsafe {
495                ffi::VRB_Iterate(
496                    bo.wrk,
497                    bo.vsl.as_mut_ptr(),
498                    bo.req,
499                    Some(write_body_iterate::<W>),
500                    state_ptr.cast::<c_void>(),
501                )
502            };
503
504            // bookkeeping mirrored from V1F_SendReq: needed regardless of whether
505            // the writer itself failed, since it reflects the now-(partially-)
506            // consumed client body stream.
507            let req = unsafe { &mut *bo.req };
508            unsafe {
509                if req.req_body_status != ffi::BS_CACHED.as_ptr() {
510                    bo.no_retry = c"bereq.body not cached".as_ptr();
511                }
512                // Only treat this as an upstream RX-body failure if the *writer*
513                // didn't cause it: mirroring vrb_pull, any early `func()` failure
514                // (including ours, from a `writer` error) also leaves the C side's
515                // `req_body_status` as `BS_ERROR` unless it happened to coincide
516                // with the last chunk read off the wire — so without this guard, a
517                // writer error on a multi-chunk body would incorrectly get flagged
518                // as a client-stream failure (`doclose`/400) here, even though the
519                // writer's own `Err` below already reports it correctly.
520                if writer_state.error.is_none() && req.req_body_status == ffi::BS_ERROR.as_ptr() {
521                    req.doclose = sc_to_ptr(StreamClose::RxBody);
522                    bo.err_code = 400;
523                }
524            }
525
526            resolve_iterate_result(rv, &mut writer_state, "bereq.body (streamed) read error")
527        }
528    }
529
530    /// Return a shared reference to the client request object, if present.
531    ///
532    /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
533    pub fn req(&self) -> Option<&Req<'_>> {
534        self.req.as_ref()
535    }
536
537    /// Return a mutable reference to the client request object, if present.
538    ///
539    /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
540    pub fn req_mut(&mut self) -> Option<&mut Req<'a>> {
541        self.req.as_mut()
542    }
543}
544
545/// Rust proxy for the C `req` struct.
546/// Its methods provide getters and setters for various fields that control how the client request
547/// is processed by Varnish.
548#[derive(Debug)]
549pub struct Req<'a> {
550    raw: &'a mut ffi::req,
551}
552
553impl Req<'_> {
554    /// Wrap a raw pointer into an object we can use.
555    pub(crate) unsafe fn from_ptr(p: *mut ffi::req) -> Option<Self> {
556        Some(Req { raw: p.as_mut()? })
557    }
558
559    /// Return whether this request bypasses the cache lookup and is always treated as a miss.
560    ///
561    /// Equivalent to `req.hash_always_miss` in VCL.
562    pub fn hash_always_miss(&self) -> bool {
563        self.raw.hash_always_miss() == 1
564    }
565
566    /// Force this request to be treated as a cache miss, skipping any existing cached object.
567    ///
568    /// Equivalent to setting `req.hash_always_miss` in VCL.
569    pub fn set_hash_always_miss(&mut self, val: bool) {
570        self.raw.set_hash_always_miss(val.into());
571    }
572
573    /// Return whether this request ignores busy (locked) cache objects and fetches from the backend instead of waiting.
574    ///
575    /// Equivalent to `req.hash_ignore_busy` in VCL.
576    pub fn hash_ignore_busy(&self) -> bool {
577        self.raw.hash_ignore_busy() == 1
578    }
579
580    /// Make this request skip waiting on busy cache objects and go straight to the backend.
581    ///
582    /// Equivalent to setting `req.hash_ignore_busy` in VCL.
583    pub fn set_hash_ignore_busy(&mut self, val: bool) {
584        self.raw.set_hash_ignore_busy(val.into());
585    }
586
587    /// Return whether this request ignores `Vary` headers during cache lookup.
588    ///
589    /// Equivalent to `req.hash_ignore_vary` in VCL.
590    pub fn hash_ignore_vary(&self) -> bool {
591        self.raw.hash_ignore_vary() == 1
592    }
593
594    /// Make this request ignore `Vary` headers during cache lookup, collapsing all variants into one cache key.
595    ///
596    /// Equivalent to setting `req.hash_ignore_vary` in VCL.
597    pub fn set_hash_ignore_vary(&mut self, val: bool) {
598        self.raw.set_hash_ignore_vary(val.into());
599    }
600}
601
602/// A struct holding both a native [`vrt_ctx`] struct and the space it points to.
603///
604/// As the name implies, this struct mainly exist to facilitate testing and should probably not be
605/// used elsewhere.
606#[derive(Debug)]
607pub struct TestCtx {
608    vrt_ctx: vrt_ctx,
609    test_ws: TestWS,
610}
611
612impl TestCtx {
613    /// Instantiate a [`vrt_ctx`], as well as the workspace (of size `sz`) it links to.
614    pub fn new(sz: usize) -> Self {
615        let mut test_ctx = Self {
616            vrt_ctx: vrt_ctx {
617                magic: VRT_CTX_MAGIC,
618                ..vrt_ctx::default()
619            },
620            test_ws: TestWS::new(sz),
621        };
622        test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
623        test_ctx
624    }
625
626    /// Return a [`Ctx`] wrapping this test context, for use in unit tests.
627    pub fn ctx(&mut self) -> Ctx<'_> {
628        Ctx::from_ref(&mut self.vrt_ctx)
629    }
630}
631
632/// Log a message outside of a request context using a VSL tag.
633///
634/// Useful in event handlers or other places where no [`Ctx`] is available.
635pub fn log(tag: LogTag, msg: impl AsRef<str>) {
636    let msg = msg.as_ref();
637    unsafe {
638        let vxids = ffi::vxids::default();
639        ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
640    }
641}
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646
647    #[test]
648    fn ctx_test() {
649        let mut test_ctx = TestCtx::new(100);
650        test_ctx.ctx();
651    }
652}
653
654/// This is an unsafe struct that holds the per-VCL state.
655/// It must be public because it is used by the macro-generated code.
656#[doc(hidden)]
657#[derive(Debug)]
658pub struct PerVclState<T> {
659    #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
660    pub fetch_filters: Vec<Box<ffi::vfp>>,
661    #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
662    pub delivery_filters: Vec<Box<ffi::vdp>>,
663    pub user_data: Option<Box<T>>,
664}
665
666// Implement the default trait that works even when `T` does not impl `Default`.
667impl<T> Default for PerVclState<T> {
668    fn default() -> Self {
669        Self {
670            fetch_filters: Vec::default(),
671            delivery_filters: Vec::default(),
672            user_data: None,
673        }
674    }
675}
676
677impl<T> PerVclState<T> {
678    pub fn get_user_data(&self) -> Option<&T> {
679        self.user_data.as_ref().map(AsRef::as_ref)
680    }
681}