cat_gateway/db/event/config/
key.rs1use std::{fmt::Display, net::IpAddr, sync::LazyLock};
4
5use jsonschema::{BasicOutput, Validator};
6use serde_json::{json, Value};
7use tracing::error;
8
9use crate::{
10 service::utilities::json::load_json,
11 utils::schema::{extract_json_schema_for, SCHEMA_VERSION},
12};
13
14#[derive(Debug, Clone, PartialEq)]
16pub(crate) enum ConfigKey {
17 Frontend,
19 FrontendForIp(IpAddr),
21}
22
23impl Display for ConfigKey {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 ConfigKey::Frontend => write!(f, "config_key_frontend"),
27 ConfigKey::FrontendForIp(_) => write!(f, "config_key_frontend_ip"),
28 }
29 }
30}
31
32pub(crate) static FRONTEND_SCHEMA: LazyLock<Value> =
34 LazyLock::new(|| extract_json_schema_for("FrontendConfig"));
35
36static FRONTEND_SCHEMA_VALIDATOR: LazyLock<Validator> =
38 LazyLock::new(|| schema_validator(&FRONTEND_SCHEMA));
39
40static FRONTEND_DEFAULT: LazyLock<Value> =
42 LazyLock::new(|| load_json(include_str!("default/frontend.json")));
43
44static FRONTEND_IP_DEFAULT: LazyLock<Value> =
46 LazyLock::new(|| load_json(include_str!("default/frontend_ip.json")));
47
48fn schema_validator(schema: &Value) -> Validator {
51 Validator::options()
52 .with_draft(jsonschema::Draft::Draft202012)
53 .build(schema)
54 .unwrap_or_else(|err| {
55 error!(
56 id="schema_validator",
57 error=?err,
58 "Error creating JSON validator"
59 );
60
61 default_validator()
62 })
63}
64
65fn default_validator() -> Validator {
68 #[allow(clippy::expect_used)]
69 Validator::new(&json!({
70 "$schema": SCHEMA_VERSION,
71 "type": "object"
72 }))
73 .expect("Failed to create default JSON validator")
74}
75
76impl ConfigKey {
77 pub(super) fn to_id(&self) -> (String, String, String) {
79 match self {
80 ConfigKey::Frontend => ("frontend".to_string(), String::new(), String::new()),
81 ConfigKey::FrontendForIp(ip) => {
82 ("frontend".to_string(), "ip".to_string(), ip.to_string())
83 },
84 }
85 }
86
87 pub(super) fn validate(&self, value: &Value) -> BasicOutput<'static> {
89 let validator = match self {
91 ConfigKey::Frontend | ConfigKey::FrontendForIp(_) => &*FRONTEND_SCHEMA_VALIDATOR,
92 };
93
94 validator.apply(value).basic()
96 }
97
98 pub(super) fn default(&self) -> Value {
100 match self {
102 ConfigKey::Frontend => FRONTEND_DEFAULT.clone(),
103 ConfigKey::FrontendForIp(_) => FRONTEND_IP_DEFAULT.clone(),
104 }
105 }
106
107 pub(crate) fn schema(&self) -> &Value {
109 match self {
110 ConfigKey::Frontend | ConfigKey::FrontendForIp(_) => &FRONTEND_SCHEMA,
111 }
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use serde_json::json;
118
119 use super::*;
120
121 #[test]
122 fn test_schema_for_schema() {
123 let invalid_schema = json!({
125 "title": "Invalid Example Schema",
126 "type": "object",
127
128 "properties": {
129 "invalidProperty": {
130 "type": "unknownType"
131 }
132 },
133
134 });
135 schema_validator(&invalid_schema);
137 }
138
139 #[test]
140 fn test_valid_validate_1() {
141 let value = json!({
142 "sentry": {
143 "dsn": "https://test.com"
144 }
145 });
146 let result = ConfigKey::Frontend.validate(&value);
147 assert!(result.is_valid());
148 println!("{:?}", serde_json::to_value(result).unwrap());
149 }
150
151 #[test]
152 fn test_valid_validate_2() {
153 let value = json!({});
154 let result = ConfigKey::Frontend.validate(&value);
155 assert!(result.is_valid());
156 println!("{:?}", serde_json::to_value(result).unwrap());
157 }
158
159 #[test]
160 fn test_invalid_validate() {
161 let value = json!([]);
162 let result = ConfigKey::Frontend.validate(&value);
163 assert!(!result.is_valid());
164 println!("{:?}", serde_json::to_value(result).unwrap());
165 }
166
167 #[test]
168 fn test_default() {
169 let result = ConfigKey::Frontend.default();
170 assert!(result.is_object());
171 }
172
173 #[test]
174 fn test_default_validator() {
175 let result = std::panic::catch_unwind(|| {
176 default_validator();
177 });
178 assert!(result.is_ok(), "default_validator panicked");
180 }
181
182 #[test]
185 fn test_default_files() {
186 assert!(!FRONTEND_DEFAULT.as_object().unwrap().is_empty());
187 }
188}