cat_gateway/db/event/config/
key.rsuse std::{fmt::Display, net::IpAddr, sync::LazyLock};
use jsonschema::{BasicOutput, Validator};
use serde_json::{json, Value};
use tracing::error;
use crate::utils::schema::{extract_json_schema_for, SCHEMA_VERSION};
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ConfigKey {
Frontend,
FrontendForIp(IpAddr),
}
impl Display for ConfigKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigKey::Frontend => write!(f, "config_key_frontend"),
ConfigKey::FrontendForIp(_) => write!(f, "config_key_frontend_ip"),
}
}
}
pub(crate) static FRONTEND_SCHEMA: LazyLock<Value> =
LazyLock::new(|| extract_json_schema_for("FrontendConfig"));
static FRONTEND_SCHEMA_VALIDATOR: LazyLock<Validator> =
LazyLock::new(|| schema_validator(&FRONTEND_SCHEMA));
static FRONTEND_DEFAULT: LazyLock<Value> =
LazyLock::new(|| load_json_lazy(include_str!("default/frontend.json")));
static FRONTEND_IP_DEFAULT: LazyLock<Value> =
LazyLock::new(|| load_json_lazy(include_str!("default/frontend_ip.json")));
fn schema_validator(schema: &Value) -> Validator {
Validator::options()
.with_draft(jsonschema::Draft::Draft202012)
.build(schema)
.unwrap_or_else(|err| {
error!(
id="schema_validator",
error=?err,
"Error creating JSON validator"
);
default_validator()
})
}
fn default_validator() -> Validator {
#[allow(clippy::expect_used)]
Validator::new(&json!({
"$schema": SCHEMA_VERSION,
"type": "object"
}))
.expect("Failed to create default JSON validator")
}
fn load_json_lazy(data: &str) -> Value {
serde_json::from_str(data).unwrap_or_else(|err| {
error!(id="load_json_lazy", error=?err, "Error parsing JSON");
json!({})
})
}
impl ConfigKey {
pub(super) fn to_id(&self) -> (String, String, String) {
match self {
ConfigKey::Frontend => ("frontend".to_string(), String::new(), String::new()),
ConfigKey::FrontendForIp(ip) => {
("frontend".to_string(), "ip".to_string(), ip.to_string())
},
}
}
pub(super) fn validate(&self, value: &Value) -> BasicOutput<'static> {
let validator = match self {
ConfigKey::Frontend | ConfigKey::FrontendForIp(_) => &*FRONTEND_SCHEMA_VALIDATOR,
};
validator.apply(value).basic()
}
pub(super) fn default(&self) -> Value {
match self {
ConfigKey::Frontend => FRONTEND_DEFAULT.clone(),
ConfigKey::FrontendForIp(_) => FRONTEND_IP_DEFAULT.clone(),
}
}
pub(crate) fn schema(&self) -> &Value {
match self {
ConfigKey::Frontend | ConfigKey::FrontendForIp(_) => &FRONTEND_SCHEMA,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_schema_for_schema() {
let invalid_schema = json!({
"title": "Invalid Example Schema",
"type": "object",
"properties": {
"invalidProperty": {
"type": "unknownType"
}
},
});
schema_validator(&invalid_schema);
}
#[test]
fn test_valid_validate_1() {
let value = json!({
"sentry": {
"dsn": "https://test.com"
}
});
let result = ConfigKey::Frontend.validate(&value);
assert!(result.is_valid());
println!("{:?}", serde_json::to_value(result).unwrap());
}
#[test]
fn test_valid_validate_2() {
let value = json!({});
let result = ConfigKey::Frontend.validate(&value);
assert!(result.is_valid());
println!("{:?}", serde_json::to_value(result).unwrap());
}
#[test]
fn test_invalid_validate() {
let value = json!([]);
let result = ConfigKey::Frontend.validate(&value);
assert!(!result.is_valid());
println!("{:?}", serde_json::to_value(result).unwrap());
}
#[test]
fn test_default() {
let result = ConfigKey::Frontend.default();
assert!(result.is_object());
}
#[test]
fn test_default_validator() {
let result = std::panic::catch_unwind(|| {
default_validator();
});
assert!(result.is_ok(), "default_validator panicked");
}
}