1use hf_hub::api::tokio::ApiRepo;
10use serde::Deserialize;
11
12use crate::error::FetchError;
13
14#[derive(Debug, Clone)]
16pub struct RepoFile {
17 pub filename: String,
19 pub size: Option<u64>,
21 pub sha256: Option<String>,
23}
24
25pub async fn list_repo_files(repo: &ApiRepo, repo_id: String) -> Result<Vec<RepoFile>, FetchError> {
32 let info = repo.info().await.map_err(|e| {
33 let msg = e.to_string();
35 if msg.contains("404") {
36 FetchError::RepoNotFound { repo_id }
37 } else {
38 FetchError::Api(e)
39 }
40 })?;
41
42 let files = info
43 .siblings
44 .into_iter()
45 .map(|s| RepoFile {
46 filename: s.rfilename,
47 size: None,
48 sha256: None,
49 })
50 .collect();
51
52 Ok(files)
53}
54
55#[derive(Debug, Deserialize)]
59struct ApiSibling {
60 rfilename: String,
61 #[serde(default)]
62 size: Option<u64>,
63 #[serde(default)]
64 lfs: Option<ApiLfs>,
65}
66
67#[derive(Debug, Deserialize)]
69struct ApiLfs {
70 sha256: String,
71 size: u64,
72}
73
74#[derive(Debug, Deserialize)]
76struct ApiModelInfo {
77 siblings: Vec<ApiSibling>,
78}
79
80pub async fn list_repo_files_with_metadata(
90 repo_id: &str,
91 token: Option<&str>,
92 revision: Option<&str>,
93) -> Result<Vec<RepoFile>, FetchError> {
94 let mut url = format!("https://huggingface.co/api/models/{repo_id}?blobs=true");
95 if let Some(rev) = revision {
96 url = format!("{url}&revision={rev}");
97 }
98
99 let client = reqwest::Client::new();
100 let mut request = client.get(url.as_str());
102 if let Some(t) = token {
103 request = request.bearer_auth(t);
104 }
105
106 let response = request
107 .send()
108 .await
109 .map_err(|e| FetchError::Http(e.to_string()))?;
110
111 if response.status() == reqwest::StatusCode::NOT_FOUND {
112 return Err(FetchError::RepoNotFound {
113 repo_id: repo_id.to_owned(),
114 });
115 }
116
117 if !response.status().is_success() {
118 return Err(FetchError::Http(format!(
119 "HF API returned status {}",
120 response.status()
121 )));
122 }
123
124 let info: ApiModelInfo = response
125 .json()
126 .await
127 .map_err(|e| FetchError::Http(e.to_string()))?;
128
129 let files = info
130 .siblings
131 .into_iter()
132 .map(|s| {
133 let (size, sha256) = match s.lfs {
134 Some(lfs) => (Some(lfs.size), Some(lfs.sha256)),
135 None => (s.size, None),
136 };
137 RepoFile {
138 filename: s.rfilename,
139 size,
140 sha256,
141 }
142 })
143 .collect();
144
145 Ok(files)
146}