signal_gateway_code_tool/
config.rs1use serde::Deserialize;
4use std::{path::PathBuf, str::FromStr};
5
6#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
8#[serde(try_from = "String")]
9pub struct GitHubRepo {
10 pub owner: String,
12 pub repo: String,
14}
15
16impl FromStr for GitHubRepo {
17 type Err = String;
18
19 fn from_str(s: &str) -> Result<Self, Self::Err> {
20 let parts: Vec<&str> = s.split('/').collect();
21 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
22 return Err(format!(
23 "invalid GitHub repo '{}': expected 'owner/repo' format",
24 s
25 ));
26 }
27 Ok(Self {
28 owner: parts[0].to_string(),
29 repo: parts[1].to_string(),
30 })
31 }
32}
33
34impl TryFrom<String> for GitHubRepo {
35 type Error = String;
36
37 fn try_from(s: String) -> Result<Self, Self::Error> {
38 s.parse()
39 }
40}
41
42#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
44pub enum Source {
45 #[serde(rename = "github")]
47 GitHub {
48 repo: GitHubRepo,
50 token_file: Option<PathBuf>,
53 },
54 #[serde(rename = "file")]
56 File {
57 path: PathBuf,
59 },
60}
61
62#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
64pub struct CodeToolConfig {
65 pub name: String,
67 #[serde(flatten)]
69 pub source: Source,
70 #[serde(default)]
74 pub glob: Vec<String>,
75 #[serde(default)]
78 pub include_non_utf8: bool,
79 #[serde(default)]
82 pub summary_file: Option<String>,
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_parse_github_source() {
91 let json = r#"{
92 "name": "my-app",
93 "github": {
94 "repo": "owner/repo-name",
95 "token_file": "/path/to/token"
96 }
97 }"#;
98
99 let config: CodeToolConfig = serde_json::from_str(json).unwrap();
100 assert_eq!(config.name, "my-app");
101 assert_eq!(
102 config.source,
103 Source::GitHub {
104 repo: GitHubRepo {
105 owner: "owner".to_string(),
106 repo: "repo-name".to_string(),
107 },
108 token_file: Some(PathBuf::from("/path/to/token")),
109 }
110 );
111 assert!(config.glob.is_empty());
112 assert!(!config.include_non_utf8);
113 }
114
115 #[test]
116 fn test_parse_github_source_no_token() {
117 let json = r#"{
118 "name": "public-app",
119 "github": {
120 "repo": "org/public-repo"
121 }
122 }"#;
123
124 let config: CodeToolConfig = serde_json::from_str(json).unwrap();
125 assert_eq!(config.name, "public-app");
126 assert_eq!(
127 config.source,
128 Source::GitHub {
129 repo: GitHubRepo {
130 owner: "org".to_string(),
131 repo: "public-repo".to_string(),
132 },
133 token_file: None,
134 }
135 );
136 }
137
138 #[test]
139 fn test_parse_file_source() {
140 let json = r#"{
141 "name": "local-app",
142 "file": {
143 "path": "/tmp/source.tar.gz"
144 }
145 }"#;
146
147 let config: CodeToolConfig = serde_json::from_str(json).unwrap();
148 assert_eq!(config.name, "local-app");
149 assert_eq!(
150 config.source,
151 Source::File {
152 path: PathBuf::from("/tmp/source.tar.gz"),
153 }
154 );
155 }
156
157 #[test]
158 fn test_parse_with_glob_and_options() {
159 let json = r#"{
160 "name": "filtered-app",
161 "github": {
162 "repo": "owner/repo"
163 },
164 "glob": ["**/*.rs", "Cargo.toml"],
165 "include_non_utf8": true
166 }"#;
167
168 let config: CodeToolConfig = serde_json::from_str(json).unwrap();
169 assert_eq!(config.name, "filtered-app");
170 assert_eq!(config.glob, vec!["**/*.rs", "Cargo.toml"]);
171 assert!(config.include_non_utf8);
172 }
173
174 #[test]
175 fn test_parse_github_repo_string() {
176 let repo: GitHubRepo = "owner/repo".parse().unwrap();
177 assert_eq!(repo.owner, "owner");
178 assert_eq!(repo.repo, "repo");
179 }
180
181 #[test]
182 fn test_parse_github_repo_invalid() {
183 assert!("invalid".parse::<GitHubRepo>().is_err());
184 assert!("".parse::<GitHubRepo>().is_err());
185 assert!("/repo".parse::<GitHubRepo>().is_err());
186 assert!("owner/".parse::<GitHubRepo>().is_err());
187 assert!("a/b/c".parse::<GitHubRepo>().is_err());
188 }
189}