use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HfSibling {
#[serde(rename = "rfilename")]
pub filename: Option<String>,
pub lfs: Option<HfLfs>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HfLfs {
pub sha256: Option<String>,
pub size: Option<u64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HfModelInfo {
pub id: Option<String>,
#[serde(rename = "pipeline_tag")]
pub pipeline_tag: Option<String>,
#[serde(rename = "lastModified")]
pub last_modified: Option<String>,
pub license: Option<String>,
#[serde(default)]
pub siblings: Vec<HfSibling>,
}
impl HfModelInfo {
#[must_use]
pub fn weight_sha256s(&self) -> Vec<String> {
self.siblings
.iter()
.filter_map(|s| s.lfs.as_ref().and_then(|l| l.sha256.clone()))
.filter(|h| !h.is_empty())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = r#"{
"id": "google-bert/bert-base-uncased",
"pipeline_tag": "fill-mask",
"lastModified": "2024-02-19T11:06:12.000Z",
"license": "apache-2.0",
"cardData": { "anything": "should be ignored" },
"siblings": [
{ "rfilename": "config.json" },
{
"rfilename": "model.safetensors",
"lfs": { "sha256": "aaaa", "size": 440473133 }
},
{
"rfilename": "pytorch_model.bin",
"lfs": { "sha256": "bbbb", "size": 440473133 }
}
]
}"#;
#[test]
fn parses_high_confidence_fields_and_ignores_card_data() {
let info: HfModelInfo = serde_json::from_str(SAMPLE).unwrap();
assert_eq!(info.id.as_deref(), Some("google-bert/bert-base-uncased"));
assert_eq!(info.pipeline_tag.as_deref(), Some("fill-mask"));
assert_eq!(
info.last_modified.as_deref(),
Some("2024-02-19T11:06:12.000Z")
);
assert_eq!(info.license.as_deref(), Some("apache-2.0"));
assert_eq!(info.siblings.len(), 3);
}
#[test]
fn collects_lfs_sha256_hashes_only() {
let info: HfModelInfo = serde_json::from_str(SAMPLE).unwrap();
let hashes = info.weight_sha256s();
assert_eq!(hashes, vec!["aaaa".to_string(), "bbbb".to_string()]);
}
#[test]
fn empty_response_yields_no_hashes() {
let info: HfModelInfo = serde_json::from_str("{}").unwrap();
assert!(info.weight_sha256s().is_empty());
assert!(info.pipeline_tag.is_none());
}
}