use crate::{Error, Service};
use serde::{Deserialize, Serialize};
#[cfg(feature = "typeinfo")]
use junction_typeinfo::TypeInfo;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
pub struct BackendId {
#[serde(flatten)]
pub service: Service,
pub port: u16,
}
impl std::fmt::Display for BackendId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.write_name(f)
}
}
impl std::str::FromStr for BackendId {
type Err = Error;
fn from_str(name: &str) -> Result<Self, Self::Err> {
let (name, port) = super::parse_port(name)?;
let port =
port.ok_or_else(|| Error::new_static("expected a fully qualified name with a port"))?;
let service = Service::from_str(name)?;
Ok(Self { service, port })
}
}
impl BackendId {
pub fn name(&self) -> String {
let mut buf = String::new();
self.write_name(&mut buf).unwrap();
buf
}
fn write_name(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
self.service.write_name(w)?;
write!(w, ":{port}", port = self.port)?;
Ok(())
}
#[doc(hidden)]
pub fn lb_config_route_name(&self) -> String {
let mut buf = String::new();
self.write_lb_config_route_name(&mut buf).unwrap();
buf
}
fn write_lb_config_route_name(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
self.service.write_lb_config_route_name(w)?;
write!(w, ":{port}", port = self.port)?;
Ok(())
}
#[doc(hidden)]
pub fn from_lb_config_route_name(name: &str) -> Result<Self, Error> {
let (name, port) = super::parse_port(name)?;
let port =
port.ok_or_else(|| Error::new_static("expected a fully qualified name with a port"))?;
let target = Service::from_lb_config_route_name(name)?;
Ok(Self {
service: target,
port,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
pub struct Backend {
pub id: BackendId,
pub lb: LbPolicy,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
#[serde(tag = "type")]
#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
pub enum LbPolicy {
RoundRobin,
RingHash(RingHashParams),
#[default]
Unspecified,
}
impl LbPolicy {
pub fn is_unspecified(&self) -> bool {
matches!(self, Self::Unspecified)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
pub struct RingHashParams {
#[serde(default = "default_min_ring_size", alias = "minRingSize")]
pub min_ring_size: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty", alias = "hashParams")]
pub hash_params: Vec<RequestHashPolicy>,
}
pub(crate) const fn default_min_ring_size() -> u32 {
1024
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
pub struct RequestHashPolicy {
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub terminal: bool,
#[serde(flatten)]
pub hasher: RequestHasher,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
pub enum RequestHasher {
#[serde(alias = "header")]
Header {
name: String,
},
#[serde(alias = "query")]
QueryParam {
name: String,
},
}
#[cfg(test)]
mod test {
use std::fmt::Debug;
use serde_json::json;
use super::*;
#[test]
fn test_lb_policy_json() {
assert_round_trip::<LbPolicy>(json!({
"type":"Unspecified",
}));
assert_round_trip::<LbPolicy>(json!({
"type":"RoundRobin",
}));
assert_round_trip::<LbPolicy>(json!({
"type":"RingHash",
"min_ring_size": 100,
"hash_params": [
{"type": "Header", "name": "x-user", "terminal": true},
{"type": "QueryParam", "name": "u"},
]
}));
}
#[test]
fn test_backend_json() {
assert_round_trip::<Backend>(json!({
"id": {"type": "kube", "name": "foo", "namespace": "bar", "port": 789},
"lb": {
"type": "Unspecified",
},
}))
}
#[track_caller]
fn assert_round_trip<T: Debug + Serialize + for<'a> Deserialize<'a>>(value: serde_json::Value) {
let from_json: T = serde_json::from_value(value.clone()).expect("failed to deserialize");
let round_tripped = serde_json::to_value(&from_json).expect("failed to serialize");
assert_eq!(value, round_tripped, "serialized value should round-trip")
}
}