1pub use crate::error::SourceError;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone)]
12#[allow(dead_code)]
13pub enum SourceType {
14 GitHub { owner: String, repo: String },
15 FileSystem { path: PathBuf },
16 Stdin,
17}
18
19#[derive(Debug, Clone)]
22#[allow(dead_code)]
23pub struct SourceContent {
24 pub source_type: SourceType,
25 pub content: String,
26 pub metadata: std::collections::HashMap<String, String>,
27}
28
29pub struct SourceResolver;
31
32impl SourceResolver {
33 pub fn resolve_github(
35 owner: &str,
36 repo: &str,
37 issue_id: &str,
38 ) -> Result<SourceContent, SourceError> {
39 if owner.is_empty() || repo.is_empty() {
41 return Err(SourceError::InvalidFormat {
42 reason: "GitHub owner and repo must be specified".to_string(),
43 });
44 }
45
46 if issue_id.parse::<u32>().is_err() {
47 return Err(SourceError::InvalidFormat {
48 reason: "Issue ID must be a valid number".to_string(),
49 });
50 }
51
52 let content = format!(
55 "GitHub issue #{issue_id} from {owner}/{repo}\n\nThis is a simulated issue description that would be fetched from the GitHub API."
56 );
57
58 let mut metadata = std::collections::HashMap::new();
59 metadata.insert("owner".to_string(), owner.to_string());
60 metadata.insert("repo".to_string(), repo.to_string());
61 metadata.insert("issue_id".to_string(), issue_id.to_string());
62
63 Ok(SourceContent {
64 source_type: SourceType::GitHub {
65 owner: owner.to_string(),
66 repo: repo.to_string(),
67 },
68 content,
69 metadata,
70 })
71 }
72
73 pub fn resolve_filesystem(path: &PathBuf) -> Result<SourceContent, SourceError> {
75 if !path.exists() {
76 return Err(SourceError::FileSystemNotFound {
77 path: path.display().to_string(),
78 });
79 }
80
81 let content = if path.is_file() {
82 std::fs::read_to_string(path).map_err(|_| SourceError::FileSystemNotFound {
83 path: path.display().to_string(),
84 })?
85 } else if path.is_dir() {
86 format!(
87 "Directory source: {}\n\nThis would contain a summary of the directory contents and relevant files.",
88 path.display()
89 )
90 } else {
91 return Err(SourceError::FileSystemNotFound {
92 path: path.display().to_string(),
93 });
94 };
95
96 let mut metadata = std::collections::HashMap::new();
97 metadata.insert("path".to_string(), path.display().to_string());
98 metadata.insert(
99 "type".to_string(),
100 if path.is_file() { "file" } else { "directory" }.to_string(),
101 );
102
103 Ok(SourceContent {
104 source_type: SourceType::FileSystem { path: path.clone() },
105 content,
106 metadata,
107 })
108 }
109
110 pub fn resolve_stdin() -> Result<SourceContent, SourceError> {
112 use std::io::Read;
113
114 let mut buffer = String::new();
115 std::io::stdin()
116 .read_to_string(&mut buffer)
117 .map_err(|e| SourceError::StdinReadFailed {
118 reason: e.to_string(),
119 })?;
120
121 if buffer.trim().is_empty() {
122 return Err(SourceError::EmptyInput);
123 }
124
125 let mut metadata = std::collections::HashMap::new();
126 metadata.insert("length".to_string(), buffer.len().to_string());
127
128 Ok(SourceContent {
129 source_type: SourceType::Stdin,
130 content: buffer,
131 metadata,
132 })
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::error::UserFriendlyError;
140
141 #[test]
142 fn test_github_source_resolution() {
143 let result = SourceResolver::resolve_github("owner", "repo", "123");
144 assert!(result.is_ok());
145
146 let content = result.unwrap();
147 assert!(content.content.contains("GitHub issue #123"));
148 assert_eq!(content.metadata.get("owner"), Some(&"owner".to_string()));
149 }
150
151 #[test]
152 fn test_github_source_invalid_issue() {
153 let result = SourceResolver::resolve_github("owner", "repo", "invalid");
154 assert!(result.is_err());
155
156 if let Err(SourceError::InvalidFormat { reason }) = result {
157 assert!(reason.contains("valid number"));
158 } else {
159 panic!("Expected InvalidFormat error");
160 }
161 }
162
163 #[test]
164 fn test_filesystem_source_not_found() {
165 let path = PathBuf::from("__nonexistent_test_path_7f8e9d6c5b4a3__");
168 let result = SourceResolver::resolve_filesystem(&path);
169 assert!(result.is_err());
170
171 if let Err(SourceError::FileSystemNotFound { path: error_path }) = result {
172 assert!(error_path.contains("nonexistent"));
173 } else {
174 panic!("Expected FileSystemNotFound error");
175 }
176 }
177
178 #[test]
179 fn test_source_error_user_friendly_messages() {
180 let error = SourceError::GitHubAuthFailed {
181 reason: "authentication failed".to_string(),
182 };
183
184 assert!(error.user_message().contains("authentication failed"));
185 assert!(!error.suggestions().is_empty());
186 assert!(error.suggestions().iter().any(|s| s.contains("auth")));
187 }
188}