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}