rust_assistant/app.rs
1//! The `app` module.
2//!
3//! This module contains the core application logic for the Rust Assistant.
4//! It typically includes structures and functions responsible for initializing
5//! and running the application, handling high-level operations, and coordinating
6//! between other modules.
7//!
8use crate::cache::{Crate, CrateCache, CrateTar, FileContent};
9use crate::download::CrateDownloader;
10use crate::github::{GithubClient, Issue, IssueEvent, Repository};
11use crate::{
12 CrateVersion, CrateVersionPath, Directory, FileLineRange, Item, ItemQuery, Line, LineQuery,
13};
14
15/// The `RustAssistant` struct, providing functionalities to interact with crates and their contents.
16///
17/// This struct encapsulates methods for downloading crates, reading their content,
18/// and performing searches within them.
19#[derive(Clone)]
20pub struct RustAssistant {
21 downloader: CrateDownloader,
22 cache: CrateCache,
23 github: GithubClient,
24}
25
26impl From<(CrateDownloader, CrateCache, GithubClient)> for RustAssistant {
27 /// Creates a new `RustAssistant` instance from a tuple of dependencies.
28 fn from((downloader, cache, github): (CrateDownloader, CrateCache, GithubClient)) -> Self {
29 Self {
30 downloader,
31 cache,
32 github,
33 }
34 }
35}
36
37impl RustAssistant {
38 /// Retrieves a crate from the cache or downloads it if not already cached.
39 ///
40 /// # Arguments
41 /// * `crate_version` - A reference to `CrateVersion` specifying the crate to retrieve.
42 ///
43 /// # Returns
44 /// A `Result` wrapping the `Crate`, or an error if the operation fails.
45 pub async fn get_crate(&self, crate_version: &CrateVersion) -> anyhow::Result<Crate> {
46 Ok(match self.cache.get_crate(crate_version) {
47 None => {
48 let data = self.downloader.download_crate_file(crate_version).await?;
49 let crate_tar = CrateTar::from((crate_version.clone(), data));
50 let krate =
51 tokio::task::spawn_blocking(move || Crate::try_from(crate_tar)).await??;
52 self.cache.set_crate(crate_version.clone(), krate);
53 self.cache
54 .get_crate(crate_version)
55 .ok_or_else(|| anyhow::anyhow!("Failed to get crate: {}", crate_version))?
56 }
57 Some(crate_tar) => crate_tar,
58 })
59 }
60
61 /// Retrieves the content of a file within a specified crate and range.
62 ///
63 /// # Arguments
64 /// * `crate_version_path` - A reference to `CrateVersionPath` specifying the crate and file path.
65 /// * `file_line_range` - A `FileLineRange` specifying the range of lines to retrieve.
66 ///
67 /// # Returns
68 /// A `Result` wrapping an `Option<CrateFileContent>`, or an error if the operation fails.
69 pub async fn get_file_content(
70 &self,
71 crate_version_path: &CrateVersionPath,
72 file_line_range: FileLineRange,
73 ) -> anyhow::Result<Option<FileContent>> {
74 let krate = self.get_crate(&crate_version_path.crate_version).await?;
75
76 let path = crate_version_path.path.clone();
77 tokio::task::spawn_blocking(move || {
78 krate.get_file_by_file_line_range(path.as_ref(), file_line_range)
79 })
80 .await?
81 }
82
83 /// Reads the content of a directory within a specified crate.
84 ///
85 /// # Arguments
86 /// * `crate_version_path` - A `CrateVersionPath` specifying the crate and directory path.
87 ///
88 /// # Returns
89 /// A `Result` wrapping an `Option<Directory>`, or an error if the operation fails.
90 pub async fn read_directory(
91 &self,
92 crate_version_path: CrateVersionPath,
93 ) -> anyhow::Result<Option<Directory>> {
94 let krate = self.get_crate(&crate_version_path.crate_version).await?;
95 Ok(krate
96 .read_directory(crate_version_path.path.as_ref())
97 .cloned())
98 }
99
100 /// Searches for items in a crate based on a query.
101 ///
102 /// # Arguments
103 /// * `crate_version` - A reference to `CrateVersion` specifying the crate to search in.
104 /// * `query` - An `ItemQuery` specifying the search criteria.
105 ///
106 /// # Returns
107 /// A `Result` wrapping a `Vec<Item>`, or an error if the operation fails.
108 pub async fn search_item(
109 &self,
110 crate_version: &CrateVersion,
111 query: impl Into<ItemQuery>,
112 ) -> anyhow::Result<Vec<Item>> {
113 let krate = self.get_crate(crate_version).await?;
114 let query = query.into();
115 Ok(tokio::task::spawn_blocking(move || krate.search_item(&query)).await?)
116 }
117
118 /// Searches for lines in a crate's files based on a query.
119 ///
120 /// # Arguments
121 /// * `crate_version` - A reference to `CrateVersion` specifying the crate to search in.
122 /// * `query` - A `LineQuery` specifying the search criteria.
123 ///
124 /// # Returns
125 /// A `Result` wrapping a `Vec<Line>`, or an error if the operation fails.
126 pub async fn search_line(
127 &self,
128 crate_version: &CrateVersion,
129 query: impl Into<LineQuery>,
130 ) -> anyhow::Result<Vec<Line>> {
131 let krate = self.get_crate(crate_version).await?;
132 let query = query.into();
133 tokio::task::spawn_blocking(move || krate.search_line(&query)).await?
134 }
135
136 /// Reads the content of a file within a specified GitHub repository.
137 ///
138 /// # Arguments
139 /// * `repo` - A reference to `Repository` specifying the GitHub repository.
140 /// * `path` - A `&str` specifying the file path.
141 /// * `branch` - An optional `&str` specifying the branch name.
142 ///
143 /// # Returns
144 /// A `Result` wrapping a `FileContent`, or an error if the operation fails.
145 ///
146 pub async fn read_github_repository_file(
147 &self,
148 repo: &Repository,
149 path: &str,
150 branch: impl Into<Option<&str>>,
151 ) -> anyhow::Result<Option<FileContent>> {
152 self.github.get_file(repo, path, branch).await
153 }
154
155 /// Reads the content of a directory within a specified GitHub repository.
156 ///
157 /// # Arguments
158 /// * `repo` - A reference to `Repository` specifying the GitHub repository.
159 /// * `path` - A `&str` specifying the directory path.
160 /// * `branch` - An optional `&str` specifying the branch name.
161 ///
162 /// # Returns
163 /// A `Result` wrapping a `Directory`, or an error if the operation fails.
164 ///
165 pub async fn read_github_repository_directory(
166 &self,
167 repo: &Repository,
168 path: &str,
169 branch: impl Into<Option<&str>>,
170 ) -> anyhow::Result<Option<Directory>> {
171 self.github.read_dir(repo, path, branch).await
172 }
173
174 /// Searches for issues in a specified GitHub repository based on a query.
175 ///
176 /// # Arguments
177 /// * `repo` - A reference to `Repository` specifying the GitHub repository.
178 /// * `query` - A `&str` specifying the query string.
179 ///
180 /// # Returns
181 /// A `Result` wrapping a `Vec<Issue>`, or an error if the operation fails.
182 ///
183 pub async fn search_github_repository_for_issues(
184 &self,
185 repo: &Repository,
186 query: &str,
187 ) -> anyhow::Result<Vec<Issue>> {
188 self.github.search_for_issues(repo, query).await
189 }
190
191 /// Retrieves the timeline of an issue in a specified GitHub repository.
192 ///
193 /// # Arguments
194 /// * `repo` - A reference to `Repository` specifying the GitHub repository.
195 /// * `issue_number` - A `u64` specifying the issue number.
196 ///
197 /// # Returns
198 /// A `Result` wrapping a `Vec<IssueEvent>`, or an error if the operation fails.
199 ///
200 pub async fn get_github_repository_issue_timeline(
201 &self,
202 repo: &Repository,
203 issue_number: u64,
204 ) -> anyhow::Result<Vec<IssueEvent>> {
205 self.github.get_issue_timeline(repo, issue_number).await
206 }
207
208 /// Retrieves the branches of a specified GitHub repository.
209 ///
210 pub async fn get_github_repository_branches(
211 &self,
212 repo: &Repository,
213 ) -> anyhow::Result<Vec<String>> {
214 self.github.get_repo_branches(repo).await
215 }
216}