use std::fmt::Debug;
use std::io::Write;
#[cfg(any(feature = "chrono", feature = "time"))]
use std::io::{self, Cursor};
use std::sync::OnceLock;
#[cfg(feature = "chrono")]
use chrono::format::{Fixed, Item};
#[cfg(feature = "chrono")]
use chrono::{DateTime, Utc};
use ruststream::{HeaderMap, Str};
#[cfg(feature = "time")]
use time::OffsetDateTime;
#[cfg(feature = "time")]
use time::format_description::well_known::Rfc3339;
use super::InboxSpec;
use super::database::QueueDatabase;
use super::engine::Events;
use super::spec::Declaration;
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a headers struct: a flattened `#[field(headers)]` field holds one",
label = "this type does not derive `InboxHeaders`",
note = "derive `InboxHeaders` for `{Self}` and describe the queue table on it, or drop \
`#[sqlx(flatten)]` to read the field as a header column"
)]
pub trait InboxHeaders: Sized + Send + Sync + 'static {
#[doc(hidden)]
type Id: Clone + Debug + Send + Sync + 'static;
#[doc(hidden)]
type Settings: Declaration;
#[doc(hidden)]
const TABLE: InboxSpec<Self::Settings>;
#[doc(hidden)]
const NAMES: &'static [&'static str];
#[doc(hidden)]
fn id(&self) -> &Self::Id;
#[doc(hidden)]
fn header_map(&self) -> HeaderMap;
}
#[doc(hidden)]
pub trait Assembled {
fn header_map(&self) -> HeaderMap;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a header value: a field of a headers struct without a role becomes \
a header",
label = "a header field of this type",
note = "implement `HeaderField` for `{Self}`, or give the field a role with `#[field(..)]`"
)]
pub trait HeaderField {
fn header(&self) -> Option<Vec<u8>>;
}
impl HeaderField for String {
fn header(&self) -> Option<Vec<u8>> {
Some(self.clone().into_bytes())
}
}
impl HeaderField for Vec<u8> {
fn header(&self) -> Option<Vec<u8>> {
Some(self.clone())
}
}
impl HeaderField for bool {
fn header(&self) -> Option<Vec<u8>> {
Some(if *self {
b"true".to_vec()
} else {
b"false".to_vec()
})
}
}
const DECIMAL_TEXT: usize = 20;
macro_rules! decimal {
($($integer:ty),*) => {
$(
impl HeaderField for $integer {
fn header(&self) -> Option<Vec<u8>> {
let mut text = [0_u8; DECIMAL_TEXT];
let mut rest = &mut text[..];
write!(rest, "{self}").ok()?;
let written = DECIMAL_TEXT - rest.len();
Some(text[..written].to_vec())
}
}
)*
};
}
decimal!(i8, i16, i32, i64, u8, u16, u32, u64);
impl<T: HeaderField> HeaderField for Option<T> {
fn header(&self) -> Option<Vec<u8>> {
self.as_ref().and_then(HeaderField::header)
}
}
#[cfg(any(feature = "chrono", feature = "time"))]
const TIME_TEXT: usize = 48;
#[cfg(any(feature = "chrono", feature = "time"))]
fn exact_text(
write: impl FnOnce(&mut Cursor<[u8; TIME_TEXT]>) -> io::Result<()>,
) -> Option<Vec<u8>> {
let mut text = Cursor::new([0_u8; TIME_TEXT]);
write(&mut text).ok()?;
let written = usize::try_from(text.position()).ok()?;
Some(text.get_ref()[..written].to_vec())
}
#[cfg(feature = "chrono")]
impl HeaderField for DateTime<Utc> {
fn header(&self) -> Option<Vec<u8>> {
let rfc3339 = [Item::Fixed(Fixed::RFC3339)];
exact_text(|text| write!(text, "{}", self.format_with_items(rfc3339.iter())))
}
}
#[cfg(feature = "time")]
impl HeaderField for OffsetDateTime {
fn header(&self) -> Option<Vec<u8>> {
exact_text(|text| {
self.format_into(text, &Rfc3339)
.map(drop)
.map_err(io::Error::other)
})
}
}
#[doc(hidden)]
pub(crate) fn put_header(headers: &mut HeaderMap, name: &'static str, field: &impl HeaderField) {
if let Some(value) = field.header() {
headers.insert(Str::from_static(name), value);
}
}
#[must_use]
#[doc(hidden)]
pub(crate) fn unnamed_header<'h>(headers: &'h HeaderMap, names: &[&str]) -> Option<&'h str> {
headers
.iter()
.map(|(name, _)| name)
.find(|name| !names.iter().any(|known| known.eq_ignore_ascii_case(name)))
}
#[doc(hidden)]
pub trait HeaderCell<DB: QueueDatabase, Row: Events<DB>>: Default + Send + Sync + 'static {
fn take(row: &mut Row) -> Self;
fn is_unset(&self) -> bool;
fn read<'c>(&'c self, row: Option<&Row>) -> &'c HeaderMap;
}
impl<DB: QueueDatabase, Row: Events<DB>> HeaderCell<DB, Row> for HeaderMap {
fn take(row: &mut Row) -> Self {
Row::take_headers(row)
}
fn is_unset(&self) -> bool {
self.is_empty()
}
fn read<'c>(&'c self, _: Option<&Row>) -> &'c HeaderMap {
self
}
}
#[doc(hidden)]
#[derive(Debug, Default)]
pub struct LazyHeaders(OnceLock<HeaderMap>);
impl<DB, Row> HeaderCell<DB, Row> for LazyHeaders
where
DB: QueueDatabase,
Row: Events<DB> + Assembled,
{
fn take(_: &mut Row) -> Self {
Self::default()
}
fn is_unset(&self) -> bool {
self.0.get().is_none()
}
fn read<'c>(&'c self, row: Option<&Row>) -> &'c HeaderMap {
self.built(row)
}
}
impl LazyHeaders {
fn built<Row: Assembled>(&self, row: Option<&Row>) -> &HeaderMap {
self.0
.get_or_init(|| row.map_or_else(HeaderMap::new, Assembled::header_map))
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use ruststream::HeaderMap;
use super::{Assembled, HeaderField, LazyHeaders, put_header, unnamed_header};
struct Tenant {
name: &'static str,
reads: Cell<usize>,
}
impl HeaderField for Tenant {
fn header(&self) -> Option<Vec<u8>> {
self.reads.set(self.reads.get() + 1);
Some(self.name.as_bytes().to_vec())
}
}
struct Job {
tenant: Tenant,
trace: Option<String>,
}
impl Assembled for Job {
fn header_map(&self) -> HeaderMap {
let mut headers = HeaderMap::new();
put_header(&mut headers, "tenant", &self.tenant);
put_header(&mut headers, "trace", &self.trace);
headers
}
}
fn job(trace: Option<&str>) -> Job {
Job {
tenant: Tenant {
name: "acme",
reads: Cell::new(0),
},
trace: trace.map(str::to_owned),
}
}
#[test]
fn the_map_is_built_on_the_first_read_and_only_once() {
let job = job(Some("t-1"));
let cell = LazyHeaders::default();
assert_eq!(job.tenant.reads.get(), 0, "nothing is built before a read");
let headers = cell.built(Some(&job));
assert_eq!(headers.get_str("tenant"), Some("acme"));
assert_eq!(headers.get_str("trace"), Some("t-1"));
assert_eq!(cell.built(Some(&job)).len(), 2);
assert_eq!(job.tenant.reads.get(), 1, "the second read reuses the map");
}
#[test]
fn a_field_holding_none_is_left_out_and_a_missing_row_reads_empty() {
let job = job(None);
let cell = LazyHeaders::default();
let headers = cell.built(Some(&job));
assert_eq!(headers.get_str("tenant"), Some("acme"));
assert!(!headers.contains("trace"), "a NULL trace is no header");
assert!(LazyHeaders::default().built(None::<&Job>).is_empty());
}
#[test]
fn a_header_no_field_is_named_for_is_unfit() {
let headers: HeaderMap = [("Tenant", "acme"), ("x-other", "1")].into_iter().collect();
assert_eq!(
unnamed_header(&headers, &["tenant", "trace"]),
Some("x-other")
);
assert_eq!(unnamed_header(&headers, &["tenant", "x-other"]), None);
}
}