1use std::{fs::File, io::BufReader, path::Path};
10
11use anyhow::Context as _;
12
13pub mod backend;
14pub mod common;
15pub mod disagg_router;
16pub mod discovery;
17pub mod endpoint_type;
18pub mod engines;
19pub mod entrypoint;
20pub mod gguf;
21pub mod grpc;
22pub mod http;
23pub mod hub;
24pub mod kv_router;
26pub mod local_model;
27pub mod migration;
28pub mod mocker;
29pub mod model_card;
30pub mod model_type;
31pub mod namespace;
32pub mod perf;
33pub mod preprocessor;
34pub mod protocols;
35pub mod recorder;
36pub mod request_template;
37pub mod tokenizers;
38pub mod tokens;
39pub mod types;
40pub mod utils;
41
42#[cfg(feature = "block-manager")]
43pub mod block_manager;
44
45#[cfg(feature = "cuda")]
46pub mod cuda;
47
48pub fn file_json_field<T: serde::de::DeserializeOwned>(
65 json_file_path: &Path,
66 field_name: &str,
67) -> anyhow::Result<T> {
68 let file = File::open(json_file_path)
70 .with_context(|| format!("Failed to open file: {:?}", json_file_path))?;
71 let reader = BufReader::new(file);
72
73 let json_data: serde_json::Value = serde_json::from_reader(reader)
77 .with_context(|| format!("Failed to parse JSON from file: {:?}", json_file_path))?;
78
79 let map = json_data.as_object().ok_or_else(|| {
81 anyhow::anyhow!("JSON root is not an object in file: {:?}", json_file_path)
82 })?;
83
84 let field_value = map.get(field_name).ok_or_else(|| {
86 anyhow::anyhow!(
87 "Field '{}' not found in JSON file: {:?}",
88 field_name,
89 json_file_path
90 )
91 })?;
92
93 serde_json::from_value(field_value.clone()).with_context(|| {
96 format!(
97 "Failed to deserialize field '{}' (value: {:?}) to the expected type from file: {:?}",
98 field_name, field_value, json_file_path
99 )
100 })
101}
102
103pub fn log_json_err(filename: &str, json: &str, err: &serde_json::Error) {
105 const ERROR_PREFIX: &str = ">> ";
106
107 if !(err.is_syntax() || err.is_data()) {
109 return;
110 }
111 let line = err.line().saturating_sub(1);
113 let column = err.column().saturating_sub(1);
114
115 let json_lines: Vec<&str> = json.lines().collect();
116 if json_lines.is_empty() {
117 tracing::error!("JSON parsing error in {filename}: File is empty.");
118 return;
119 }
120
121 let start_index = (line - 2).max(0);
123 let end_index = (line + 3).min(json_lines.len());
125
126 let mut context_lines: Vec<String> = (start_index..end_index)
128 .map(|i| {
129 if i == line {
130 format!("{ERROR_PREFIX}{}", json_lines[i])
131 } else {
132 format!("{:06} {}", i + 1, json_lines[i])
134 }
135 })
136 .collect();
137
138 let col_indicator = "_".to_string().repeat(column + ERROR_PREFIX.len()) + "^";
140 let error_in_context_idx = line - start_index;
141 if error_in_context_idx < context_lines.len() {
142 context_lines.insert(error_in_context_idx + 1, col_indicator);
143 }
144
145 tracing::error!(
146 "JSON parsing error in {filename}: Line {}, column {}:\n{}",
147 err.line(),
148 err.column(),
149 context_lines.join("\n")
150 );
151}
152
153#[cfg(test)]
154mod file_json_field_tests {
155 use super::file_json_field;
156 use serde::Deserialize;
157 use std::fs::File;
158 use std::io::Write;
159 use std::path::{Path, PathBuf};
160 use tempfile::tempdir;
161
162 fn create_temp_json_file(dir: &Path, file_name: &str, content: &str) -> PathBuf {
164 let file_path = dir.join(file_name);
165 let mut file = File::create(&file_path)
166 .unwrap_or_else(|_| panic!("Failed to create test file: {:?}", file_path));
167 file.write_all(content.as_bytes())
168 .unwrap_or_else(|_| panic!("Failed to write to test file: {:?}", file_path));
169 file_path
170 }
171
172 #[derive(Debug, PartialEq, Deserialize)]
174 struct MyConfig {
175 version: String,
176 enabled: bool,
177 count: u32,
178 }
179
180 #[test]
181 fn test_success_basic() {
182 let tmp_dir = tempdir().unwrap();
183 let file_path = create_temp_json_file(
184 tmp_dir.path(),
185 "test_basic.json",
186 r#"{ "name": "Rust", "age": 30, "is_active": true }"#,
187 );
188
189 let name: String = file_json_field(&file_path, "name").unwrap();
190 assert_eq!(name, "Rust");
191
192 let age: i32 = file_json_field(&file_path, "age").unwrap();
193 assert_eq!(age, 30);
194
195 let is_active: bool = file_json_field(&file_path, "is_active").unwrap();
196 assert!(is_active);
197 }
198
199 #[test]
200 fn test_success_custom_struct_field() {
201 let tmp_dir = tempdir().unwrap();
202 let file_path = create_temp_json_file(
203 tmp_dir.path(),
204 "test_struct.json",
205 r#"{
206 "config": {
207 "version": "1.0.0",
208 "enabled": true,
209 "count": 123
210 },
211 "other_field": "value"
212 }"#,
213 );
214
215 let config: MyConfig = file_json_field(&file_path, "config").unwrap();
216 assert_eq!(
217 config,
218 MyConfig {
219 version: "1.0.0".to_string(),
220 enabled: true,
221 count: 123,
222 }
223 );
224 }
225
226 #[test]
227 fn test_file_not_found() {
228 let tmp_dir = tempdir().unwrap();
229 let non_existent_path = tmp_dir.path().join("non_existent.json");
230
231 let result: anyhow::Result<String> = file_json_field(&non_existent_path, "field");
232 assert!(result.is_err());
233 let err = result.unwrap_err();
234 assert!(err.to_string().contains("Failed to open file"));
235 }
236
237 #[test]
238 fn test_invalid_json_syntax() {
239 let tmp_dir = tempdir().unwrap();
240 let file_path = create_temp_json_file(
241 tmp_dir.path(),
242 "invalid.json",
243 r#"{ "key": "value", "bad_syntax": }"#, );
245
246 let result: anyhow::Result<String> = file_json_field(&file_path, "key");
247 assert!(result.is_err());
248 let err = result.unwrap_err();
249 assert!(err.to_string().contains("Failed to parse JSON from file"));
250 }
251
252 #[test]
253 fn test_json_root_not_object_array() {
254 let tmp_dir = tempdir().unwrap();
255 let file_path = create_temp_json_file(
256 tmp_dir.path(),
257 "root_array.json",
258 r#"[ { "item": 1 }, { "item": 2 } ]"#, );
260
261 let result: anyhow::Result<String> = file_json_field(&file_path, "item");
262 assert!(result.is_err());
263 let err = result.unwrap_err();
264 assert!(err.to_string().contains("JSON root is not an object"));
265 }
266
267 #[test]
268 fn test_json_root_not_object_primitive() {
269 let tmp_dir = tempdir().unwrap();
270 let file_path = create_temp_json_file(
271 tmp_dir.path(),
272 "root_primitive.json",
273 r#""just_a_string""#, );
275
276 let result: anyhow::Result<String> = file_json_field(&file_path, "field");
277 assert!(result.is_err());
278 let err = result.unwrap_err();
279 assert!(err.to_string().contains("JSON root is not an object"));
280 }
281
282 #[test]
283 fn test_field_not_found() {
284 let tmp_dir = tempdir().unwrap();
285 let file_path = create_temp_json_file(
286 tmp_dir.path(),
287 "missing_field.json",
288 r#"{ "existing_field": "hello" }"#,
289 );
290
291 let result: anyhow::Result<String> = file_json_field(&file_path, "non_existent_field");
292 assert!(result.is_err());
293 let err = result.unwrap_err();
294 assert!(
295 err.to_string()
296 .contains("Field 'non_existent_field' not found")
297 );
298 }
299
300 #[test]
301 fn test_field_type_mismatch() {
302 let tmp_dir = tempdir().unwrap();
303 let file_path = create_temp_json_file(
304 tmp_dir.path(),
305 "type_mismatch.json",
306 r#"{ "count": "not_an_integer" }"#,
307 );
308
309 let result: anyhow::Result<u32> = file_json_field(&file_path, "count");
310 assert!(result.is_err());
311 let err = result.unwrap_err();
312 assert!(
313 err.to_string()
314 .contains("Failed to deserialize field 'count'")
315 );
316 }
317
318 #[test]
319 fn test_empty_file() {
320 let tmp_dir = tempdir().unwrap();
321 let file_path = create_temp_json_file(tmp_dir.path(), "empty.json", "");
322
323 let result: anyhow::Result<String> = file_json_field(&file_path, "field");
324 assert!(result.is_err());
325 let err = result.unwrap_err();
326 assert!(err.to_string().contains("Failed to parse JSON from file"));
327 }
328}