use pyo3::prelude::*;
use pyo3::types::{PyList, PyString, PyTuple};
#[pyclass(name = "Headers")]
#[derive(Debug, Clone)]
pub struct PyHeaders {
inner: http::HeaderMap,
}
impl PyHeaders {
pub fn from_header_map(inner: http::HeaderMap) -> Self {
Self { inner }
}
pub fn inner_ref(&self) -> &http::HeaderMap {
&self.inner
}
}
#[pymethods]
impl PyHeaders {
#[pyo3(signature = (name, default=None))]
fn get<'py>(
&self,
py: Python<'py>,
name: &str,
default: Option<&Bound<'py, PyAny>>,
) -> Bound<'py, PyAny> {
match self.inner.get(name).and_then(|v| v.to_str().ok()) {
Some(s) => PyString::new(py, s).into_any(),
None => match default {
Some(d) => d.clone(),
None => py.None().into_bound(py),
},
}
}
fn __contains__(&self, name: &str) -> bool {
self.inner.contains_key(name)
}
fn __getitem__(&self, name: &str) -> PyResult<String> {
self.inner
.get(name)
.and_then(|v| v.to_str().ok())
.map(str::to_string)
.ok_or_else(|| {
PyErr::new::<pyo3::exceptions::PyKeyError, _>(format!("header '{name}' not found"))
})
}
fn __len__(&self) -> usize {
self.inner.len()
}
fn __iter__(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
let names: Vec<String> = self.inner.keys().map(|k| k.as_str().to_owned()).collect();
Ok(PyList::new(py, names)?.into())
}
fn keys(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
let names: Vec<String> = self.inner.keys().map(|k| k.as_str().to_owned()).collect();
Ok(PyList::new(py, names)?.into())
}
fn values(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
let vals: Vec<String> = self
.inner
.values()
.filter_map(|v| v.to_str().ok())
.map(str::to_string)
.collect();
Ok(PyList::new(py, vals)?.into())
}
fn items(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
let mut result: Vec<Bound<'_, PyTuple>> = Vec::new();
for (k, v) in &self.inner {
let name = k.as_str();
if let Ok(val) = v.to_str() {
result.push(PyTuple::new(py, [name, val])?);
}
}
Ok(PyList::new(py, result)?.into())
}
fn get_list(&self, name: &str) -> PyResult<Vec<String>> {
let header_name = http::header::HeaderName::from_bytes(name.as_bytes()).map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid header name: {e}"))
})?;
let values: Vec<String> = self
.inner
.get_all(&header_name)
.iter()
.map(|v| String::from_utf8_lossy(v.as_bytes()).to_string())
.collect();
Ok(values)
}
fn __repr__(&self) -> String {
let pairs: Vec<String> = self
.inner
.iter()
.filter_map(|(k, v)| {
let name = k.as_str();
let val = if matches!(
name,
"authorization" | "proxy-authorization" | "cookie" | "set-cookie"
) {
"<redacted>"
} else {
v.to_str().ok()?
};
Some(format!("{name}: {val}"))
})
.collect();
format!("Headers({{{}}})", pairs.join(", "))
}
}