varnish_sys/txt.rs
1use std::ffi::{c_char, CStr};
2use std::slice::from_raw_parts;
3use std::str::from_utf8;
4
5use crate::ffi::txt;
6use crate::vcl::StrOrBytes;
7
8impl txt {
9 /// Internal helper to create a `txt` struct from a byte slice.
10 /// The entire slice is assumed to not contain any null bytes.
11 fn from_bytes(s: &[u8]) -> Self {
12 Self {
13 b: s.as_ptr().cast::<c_char>(),
14 e: unsafe { s.as_ptr().add(s.len()).cast::<c_char>() },
15 }
16 }
17
18 /// FIXME: This method is only used when calling [`crate::ffi::VSLbt`],
19 /// and current implementation creates a string without a null terminator to pass it in.
20 /// Going forward, we should probably refactor it to avoid extra string allocation.
21 #[expect(clippy::should_implement_trait)]
22 pub fn from_str(s: &str) -> Self {
23 Self::from_bytes(s.as_bytes())
24 }
25
26 pub fn from_cstr(s: &CStr) -> Self {
27 Self::from_bytes(s.to_bytes())
28 }
29
30 /// Convert the `txt` struct to a `&[u8]`.
31 /// We want to explicitly differentiate between empty (`None`) and null (`Some([])`) strings.
32 #[expect(clippy::wrong_self_convention)] // TODO: drop Copy trait for txt?
33 pub fn to_slice(&self) -> Option<&[u8]> {
34 if self.b.is_null() {
35 None
36 } else {
37 // SAFETY: We assume that txt instance was created correctly,
38 // so the pointers are valid and the end is after the beginning.
39 // Txt instances are part of ffi, so inherently unsafe.
40 unsafe {
41 Some(from_raw_parts(
42 self.b.cast::<u8>(),
43 self.e.offset_from(self.b) as usize,
44 ))
45 }
46 }
47 }
48
49 /// Convert the `txt` struct to a `StrOrBytes` enum.
50 #[expect(clippy::wrong_self_convention)] // TODO: drop Copy trait for txt?
51 pub fn to_str(&self) -> Option<StrOrBytes<'_>> {
52 self.to_slice().map(StrOrBytes::from)
53 }
54
55 /// Parse the `txt` struct as a header, returning a tuple with the key and value,
56 /// trimming the value of leading whitespace.
57 pub fn parse_header(&self) -> Option<(&str, StrOrBytes<'_>)> {
58 // We expect varnishd to always given us a string with a ':' in it
59 // If it's not the case, blow up as it might be a sign of a bigger problem.
60 let slice = self.to_slice()?;
61 let index = slice
62 .iter()
63 .position(|c| *c == b':')
64 .expect("headers should always have a :");
65
66 let (key_slice, value_slice) = slice.split_at(index);
67
68 Some((
69 from_utf8(key_slice).expect("header names must be UTF-8"),
70 value_slice[1..].trim_ascii_start().into(),
71 ))
72 }
73}