use std::collections::BTreeMap;
use http::{Uri, Version};
use name::GroupId;
use crate::{conn::net::SocketBindOptions, proxy::Matcher};
macro_rules! impl_group_variants {
($($name:ident $(($ty:ty))?,)*) => {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum GroupKey {
$($name,)*
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
enum GroupVariant {
$($name $(($ty))?,)*
}
}
}
impl_group_variants! {
Request(Group),
Emulate(Group),
Named(GroupId),
Uri(Uri),
Version(Version),
Proxy(Matcher),
SocketBind(Option<SocketBindOptions>),
}
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
pub struct Group(BTreeMap<GroupKey, GroupVariant>);
impl Group {
#[inline]
pub fn new<N: Into<GroupId>>(name: N) -> Self {
Group(BTreeMap::from([(
GroupKey::Named,
GroupVariant::Named(name.into()),
)]))
}
#[inline]
pub(crate) fn uri(&mut self, uri: Uri) -> &mut Self {
self.extend(GroupKey::Uri, GroupVariant::Uri(uri))
}
#[inline]
pub(crate) fn version(&mut self, version: Option<Version>) -> &mut Self {
self.extend(GroupKey::Version, version.map(GroupVariant::Version))
}
#[inline]
pub(crate) fn proxy(&mut self, proxy: Option<Matcher>) -> &mut Self {
self.extend(GroupKey::Proxy, proxy.map(GroupVariant::Proxy))
}
#[inline]
pub(crate) fn socket_bind(&mut self, opts: Option<SocketBindOptions>) -> &mut Self {
self.extend(GroupKey::SocketBind, GroupVariant::SocketBind(opts))
}
#[inline]
pub(crate) fn request(&mut self, group: Group) -> &mut Self {
self.extend(GroupKey::Request, GroupVariant::Request(group))
}
#[inline]
pub(crate) fn emulate(&mut self, group: Group) -> &mut Self {
self.extend(GroupKey::Emulate, GroupVariant::Emulate(group))
}
#[inline]
fn extend<T: Into<Option<GroupVariant>>>(&mut self, id: GroupKey, entry: T) -> &mut Self {
if let Some(entry) = entry.into() {
self.0.insert(id, entry);
}
self
}
}
impl From<u64> for Group {
#[inline]
fn from(value: u64) -> Self {
Group::new(value)
}
}
impl From<&'static str> for Group {
#[inline]
fn from(value: &'static str) -> Self {
Group::new(value)
}
}
impl From<String> for Group {
#[inline]
fn from(value: String) -> Self {
Group::new(value)
}
}
impl From<Box<str>> for Group {
#[inline]
fn from(value: Box<str>) -> Self {
Group::new(value)
}
}
mod name {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GroupId {
Borrowed(&'static str),
Owned(Box<str>),
Number(u64),
}
impl From<&'static str> for GroupId {
#[inline]
fn from(value: &'static str) -> Self {
Self::Borrowed(value)
}
}
impl From<String> for GroupId {
#[inline]
fn from(value: String) -> Self {
Self::Owned(value.into_boxed_str())
}
}
impl From<Box<str>> for GroupId {
#[inline]
fn from(value: Box<str>) -> Self {
Self::Owned(value)
}
}
impl From<u64> for GroupId {
#[inline]
fn from(value: u64) -> Self {
Self::Number(value)
}
}
}
#[cfg(test)]
mod tests {
use std::hash::{DefaultHasher, Hash, Hasher};
use super::*;
#[test]
fn test_group_identity_invariance() {
let mut g1 = Group::default();
g1.extend(GroupKey::Named, GroupVariant::Named("worker".into()));
g1.extend(GroupKey::Version, GroupVariant::Version(Version::HTTP_2));
let mut g2 = Group::default();
g2.extend(GroupKey::Version, GroupVariant::Version(Version::HTTP_2));
g2.extend(GroupKey::Named, GroupVariant::Named("worker".into()));
let mut h1 = DefaultHasher::new();
g1.hash(&mut h1);
let mut h2 = DefaultHasher::new();
g2.hash(&mut h2);
assert_eq!(
h1.finish(),
h2.finish(),
"Request groups must maintain identical hashes regardless of criteria insertion order"
);
}
}