1use anyhow::{Context, Result};
2use serde::Deserialize;
3
4use super::Client;
5
6#[derive(Debug, Deserialize)]
7pub struct FileContent {
8 pub name: String,
9 pub path: String,
10 pub sha: String,
11 #[serde(default)]
12 pub content: Option<String>,
13 pub encoding: Option<String>,
14}
15
16impl Client {
17 pub async fn get_file(
19 &self,
20 repo: &str,
21 path: &str,
22 branch: Option<&str>,
23 ) -> Result<Option<FileContent>> {
24 let mut url = format!("/repos/{}/{repo}/contents/{path}", self.org);
25 if let Some(branch) = branch {
26 url.push_str(&format!("?ref={branch}"));
27 }
28
29 let resp = self.get(&url).await?;
30
31 match resp.status().as_u16() {
32 200 => {
33 let content = resp
34 .json()
35 .await
36 .context("Failed to parse file content response")?;
37 Ok(Some(content))
38 }
39 404 => Ok(None),
40 status => {
41 let body = resp.text().await.unwrap_or_default();
42 anyhow::bail!("Failed to get file {path} in {repo} (HTTP {status}): {body}");
43 }
44 }
45 }
46
47 pub fn decode_content(content: &FileContent) -> Result<String> {
49 let raw = content.content.as_deref().unwrap_or("");
50 let cleaned: String = raw.chars().filter(|c| !c.is_whitespace()).collect();
51 let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &cleaned)
52 .context("Failed to decode base64 content")?;
53 String::from_utf8(bytes).context("File content is not valid UTF-8")
54 }
55}