use crate::extensions::DeserializeExt;
use crate::static_assert;
use serde::de::{Error, Unexpected};
use serde::{Deserialize, Deserializer};
use serde_json::Value;
const HEALTH_ENDPOINT: &str = "/health";
const LIVE_ENDPOINT: &str = "/live";
const READY_ENDPOINT: &str = "/ready";
const METRICS_ENDPOINT: &str = "/metrics";
const VERSION_ENDPOINT: &str = "/version";
const HEALTH_PTR: &str = "/health";
const LIVE_PTR: &str = "/live";
const READY_PTR: &str = "/ready";
const METRICS_PTR: &str = "/metrics";
const VERSION_PTR: &str = "/version";
#[derive(Debug, Default, Clone, Deserialize)]
pub struct ManagementConfig {
pub endpoints: Endpoints,
}
#[derive(Debug, Clone)]
pub struct Endpoints {
pub health: Endpoint,
pub live: Endpoint,
pub ready: Endpoint,
pub metrics: Endpoint,
pub version: Endpoint,
}
#[allow(clippy::indexing_slicing)]
impl<'de> Deserialize<'de> for Endpoints {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
static_assert!(HEALTH_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(LIVE_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(READY_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(METRICS_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(VERSION_ENDPOINT.as_bytes()[0] == b'/');
let value = Value::deserialize(deserializer)?;
let health = value
.pointer_and_deserialize::<_, D::Error>(HEALTH_PTR)
.unwrap_or_else(|_| Endpoint(HEALTH_ENDPOINT.to_owned()));
let live = value
.pointer_and_deserialize::<_, D::Error>(LIVE_PTR)
.unwrap_or_else(|_| Endpoint(LIVE_ENDPOINT.to_owned()));
let ready = value
.pointer_and_deserialize::<_, D::Error>(READY_PTR)
.unwrap_or_else(|_| Endpoint(READY_ENDPOINT.to_owned()));
let metrics = value
.pointer_and_deserialize::<_, D::Error>(METRICS_PTR)
.unwrap_or_else(|_| Endpoint(METRICS_ENDPOINT.to_owned()));
let version = value
.pointer_and_deserialize::<_, D::Error>(VERSION_PTR)
.unwrap_or_else(|_| Endpoint(VERSION_ENDPOINT.to_owned()));
Ok(Endpoints {
health,
live,
ready,
metrics,
version,
})
}
}
#[allow(clippy::indexing_slicing)]
impl Default for Endpoints {
fn default() -> Self {
static_assert!(HEALTH_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(LIVE_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(READY_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(METRICS_ENDPOINT.as_bytes()[0] == b'/');
static_assert!(VERSION_ENDPOINT.as_bytes()[0] == b'/');
Self {
health: Endpoint(HEALTH_ENDPOINT.to_owned()),
live: Endpoint(LIVE_ENDPOINT.to_owned()),
ready: Endpoint(READY_ENDPOINT.to_owned()),
metrics: Endpoint(METRICS_ENDPOINT.to_owned()),
version: Endpoint(VERSION_ENDPOINT.to_owned()),
}
}
}
#[derive(Debug, Clone)]
pub struct Endpoint(String);
impl Endpoint {
pub fn new(path: &str) -> Result<Self, &'static str> {
if path.starts_with('/') {
Ok(Endpoint(path.to_owned()))
} else {
Err("Endpoint must start with a `/`")
}
}
}
impl<'de> Deserialize<'de> for Endpoint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let endpoint = String::deserialize(deserializer)?;
Endpoint::new(endpoint.as_str())
.map_err(|err| D::Error::invalid_value(Unexpected::Str(&endpoint), &err))
}
}
impl AsRef<str> for Endpoint {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}