use std::ffi::{c_int, c_uint, c_void, CStr};
use crate::ffi;
use crate::ffi::{vrt_ctx, VRT_call, VRT_check_call, VRT_fail, VRT_handled, VRT_CTX_MAGIC};
use crate::vcl::{subroutine::Subroutine, HttpHeaders, LogTag, TestWS, VclError, Workspace};
#[derive(Debug)]
pub struct Ctx<'a> {
pub raw: &'a mut vrt_ctx,
pub http_req: Option<HttpHeaders<'a>>,
pub http_req_top: Option<HttpHeaders<'a>>,
pub http_resp: Option<HttpHeaders<'a>>,
pub http_bereq: Option<HttpHeaders<'a>>,
pub http_beresp: Option<HttpHeaders<'a>>,
pub ws: Workspace<'a>,
req: Option<Req<'a>>,
}
impl<'a> Ctx<'a> {
pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
Self::from_ref(
ptr.cast_mut()
.as_mut()
.expect("vrt_ctx pointer must not be null"),
)
}
pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
assert_eq!(raw.magic, VRT_CTX_MAGIC);
Self {
http_req: HttpHeaders::from_ptr(raw.http_req),
http_req_top: HttpHeaders::from_ptr(raw.http_req_top),
http_resp: HttpHeaders::from_ptr(raw.http_resp),
http_bereq: HttpHeaders::from_ptr(raw.http_bereq),
http_beresp: HttpHeaders::from_ptr(raw.http_beresp),
ws: Workspace::from_ptr(raw.ws),
req: unsafe { Req::from_ptr(raw.req) },
raw,
}
}
pub fn fail(&mut self, msg: impl Into<VclError>) {
let msg = msg.into();
let msg = msg.as_str();
unsafe {
VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
}
}
pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
unsafe {
let vsl = self.raw.vsl;
if vsl.is_null() {
log(tag, msg);
} else {
let msg = ffi::txt::from_str(msg.as_ref());
ffi::VSLbt(vsl, tag, msg);
}
}
}
pub fn local_socket(&self) -> Result<&'a str, VclError> {
if self.raw.req.is_null() && self.raw.bo.is_null() {
return Err("local.socket isn't available in this context".into());
}
let raw = unsafe { ffi::VRT_r_local_socket(self.raw) };
let cstr = <&CStr>::from(raw);
Ok(cstr.to_str()?)
}
pub fn local_endpoint(&self) -> Result<&'a str, VclError> {
if self.raw.req.is_null() && self.raw.bo.is_null() {
return Err("local.endpoint isn't available in this context".into());
}
let raw = unsafe { ffi::VRT_r_local_endpoint(self.raw) };
let cstr = <&CStr>::from(raw);
Ok(cstr.to_str()?)
}
pub fn call_sub(&mut self, sub: Subroutine) -> Result<bool, VclError> {
self.check_call_sub(sub)?;
unsafe { VRT_call(self.raw, sub.vcl_ptr()) };
Ok(self.is_handled())
}
pub fn check_call_sub(&self, sub: Subroutine) -> Result<(), VclError> {
let result = unsafe { VRT_check_call(self.raw, sub.vcl_ptr()) };
if result.0.is_null() {
Ok(())
} else {
let msg = unsafe { CStr::from_ptr(result.0) }
.to_string_lossy()
.into_owned();
Err(VclError::new(msg))
}
}
pub fn is_handled(&self) -> bool {
unsafe { VRT_handled(self.raw) != 0 }
}
pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
unsafe extern "C" fn chunk_collector(
priv_: *mut c_void,
_flush: c_uint,
ptr: *const c_void,
len: isize,
) -> c_int {
let v = priv_
.cast::<Vec<&[u8]>>()
.as_mut()
.expect("cached_req_body callback priv pointer must not be null");
let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
v.push(buf);
0
}
let req = &mut *self.req.as_mut().ok_or("req object isn't available")?.raw;
unsafe {
if req.req_body_status != ffi::BS_CACHED.as_ptr() {
return Err("request body hasn't been previously cached".into());
}
}
let mut v: Box<Vec<&'a [u8]>> = Box::default();
let p: *mut Vec<&'a [u8]> = &raw mut *v;
match unsafe {
ffi::VRB_Iterate(
req.wrk,
req.vsl.as_mut_ptr(),
req,
Some(chunk_collector),
p.cast::<c_void>(),
)
} {
0 => Ok(*v),
_ => Err("req.body iteration failed".into()),
}
}
pub fn req(&self) -> Option<&Req<'_>> {
self.req.as_ref()
}
pub fn req_mut(&mut self) -> Option<&mut Req<'a>> {
self.req.as_mut()
}
}
#[derive(Debug)]
pub struct Req<'a> {
raw: &'a mut ffi::req,
}
impl Req<'_> {
pub(crate) unsafe fn from_ptr(p: *mut ffi::req) -> Option<Self> {
Some(Req { raw: p.as_mut()? })
}
pub fn hash_always_miss(&self) -> bool {
self.raw.hash_always_miss() == 1
}
pub fn set_hash_always_miss(&mut self, val: bool) {
self.raw.set_hash_always_miss(val.into());
}
pub fn hash_ignore_busy(&self) -> bool {
self.raw.hash_ignore_busy() == 1
}
pub fn set_hash_ignore_busy(&mut self, val: bool) {
self.raw.set_hash_ignore_busy(val.into());
}
pub fn hash_ignore_vary(&self) -> bool {
self.raw.hash_ignore_vary() == 1
}
pub fn set_hash_ignore_vary(&mut self, val: bool) {
self.raw.set_hash_ignore_vary(val.into());
}
}
#[derive(Debug)]
pub struct TestCtx {
vrt_ctx: vrt_ctx,
test_ws: TestWS,
}
impl TestCtx {
pub fn new(sz: usize) -> Self {
let mut test_ctx = Self {
vrt_ctx: vrt_ctx {
magic: VRT_CTX_MAGIC,
..vrt_ctx::default()
},
test_ws: TestWS::new(sz),
};
test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
test_ctx
}
pub fn ctx(&mut self) -> Ctx<'_> {
Ctx::from_ref(&mut self.vrt_ctx)
}
}
pub fn log(tag: LogTag, msg: impl AsRef<str>) {
let msg = msg.as_ref();
unsafe {
let vxids = ffi::vxids::default();
ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ctx_test() {
let mut test_ctx = TestCtx::new(100);
test_ctx.ctx();
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct PerVclState<T> {
#[expect(clippy::vec_box)] pub fetch_filters: Vec<Box<ffi::vfp>>,
#[expect(clippy::vec_box)] pub delivery_filters: Vec<Box<ffi::vdp>>,
pub user_data: Option<Box<T>>,
}
impl<T> Default for PerVclState<T> {
fn default() -> Self {
Self {
fetch_filters: Vec::default(),
delivery_filters: Vec::default(),
user_data: None,
}
}
}
impl<T> PerVclState<T> {
pub fn get_user_data(&self) -> Option<&T> {
self.user_data.as_ref().map(AsRef::as_ref)
}
}