pforge_config/
validator.rs1use crate::{ConfigError, ForgeConfig, Result, ToolDef};
2use rustc_hash::FxHashSet;
3
4pub fn validate_config(config: &ForgeConfig) -> Result<()> {
5 let mut tool_names = FxHashSet::default();
7 for tool in &config.tools {
8 let name = tool.name();
9 if !tool_names.insert(name) {
10 return Err(ConfigError::DuplicateToolName(name.to_string()));
11 }
12 }
13
14 for tool in &config.tools {
16 if let ToolDef::Native { handler, .. } = tool {
17 validate_handler_path(&handler.path)?;
18 }
19 }
20
21 Ok(())
22}
23
24fn validate_handler_path(path: &str) -> Result<()> {
25 if path.is_empty() {
26 return Err(ConfigError::InvalidHandlerPath("empty path".to_string()));
27 }
28
29 if !path.contains("::") {
31 return Err(ConfigError::InvalidHandlerPath(format!(
32 "invalid format: {path} (expected format: module::function)"
33 )));
34 }
35
36 Ok(())
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use crate::*;
43
44 #[test]
45 fn test_validate_config_success() {
46 let config = ForgeConfig {
47 forge: ForgeMetadata {
48 name: "test".to_string(),
49 version: "1.0.0".to_string(),
50 transport: TransportType::Stdio,
51 optimization: OptimizationLevel::Debug,
52 },
53 tools: vec![ToolDef::Native {
54 name: "tool1".to_string(),
55 description: "Tool 1".to_string(),
56 handler: HandlerRef {
57 path: "module::handler".to_string(),
58 inline: None,
59 },
60 params: ParamSchema {
61 fields: rustc_hash::FxHashMap::default(),
62 },
63 timeout_ms: None,
64 }],
65 resources: vec![],
66 prompts: vec![],
67 state: None,
68 };
69
70 assert!(validate_config(&config).is_ok());
71 }
72
73 #[test]
74 fn test_validate_config_duplicate_tools() {
75 let config = ForgeConfig {
76 forge: ForgeMetadata {
77 name: "test".to_string(),
78 version: "1.0.0".to_string(),
79 transport: TransportType::Stdio,
80 optimization: OptimizationLevel::Debug,
81 },
82 tools: vec![
83 ToolDef::Native {
84 name: "duplicate".to_string(),
85 description: "Tool 1".to_string(),
86 handler: HandlerRef {
87 path: "module::handler1".to_string(),
88 inline: None,
89 },
90 params: ParamSchema {
91 fields: rustc_hash::FxHashMap::default(),
92 },
93 timeout_ms: None,
94 },
95 ToolDef::Native {
96 name: "duplicate".to_string(),
97 description: "Tool 2".to_string(),
98 handler: HandlerRef {
99 path: "module::handler2".to_string(),
100 inline: None,
101 },
102 params: ParamSchema {
103 fields: rustc_hash::FxHashMap::default(),
104 },
105 timeout_ms: None,
106 },
107 ],
108 resources: vec![],
109 prompts: vec![],
110 state: None,
111 };
112
113 let result = validate_config(&config);
114 assert!(result.is_err());
115 assert!(matches!(
116 result.unwrap_err(),
117 ConfigError::DuplicateToolName(_)
118 ));
119 }
120
121 #[test]
122 fn test_validate_handler_path_empty() {
123 let result = validate_handler_path("");
124 assert!(result.is_err());
125 assert!(matches!(
126 result.unwrap_err(),
127 ConfigError::InvalidHandlerPath(_)
128 ));
129 }
130
131 #[test]
132 fn test_validate_handler_path_invalid_format() {
133 let result = validate_handler_path("invalid_path");
134 assert!(result.is_err());
135 assert!(matches!(
136 result.unwrap_err(),
137 ConfigError::InvalidHandlerPath(_)
138 ));
139 }
140
141 #[test]
142 fn test_validate_handler_path_valid() {
143 let result = validate_handler_path("module::handler");
144 assert!(result.is_ok());
145 }
146}