1use anyhow::Context;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5use crate::{fetch, DockerHubClient};
6
7#[derive(Serialize, Deserialize, Debug)]
8pub struct Category {
9 name: String,
10 slug: String,
11}
12
13#[derive(Serialize, Deserialize, Debug)]
14pub struct Repository {
15 pub name: String,
17
18 namespace: String,
20
21 repository_type: String,
23
24 status: usize,
25
26 status_description: String,
27
28 description: String,
31
32 is_private: bool,
33
34 star_count: usize,
35
36 pull_count: usize,
37
38 last_updated: DateTime<Utc>,
39
40 last_modified: DateTime<Utc>,
41
42 date_registered: DateTime<Utc>,
43
44 affiliation: String,
46
47 media_types: Vec<String>,
48
49 content_types: Vec<String>,
50
51 categories: Vec<Category>,
52
53 storage_size: u64,
55}
56
57impl DockerHubClient {
58 pub async fn list_repositories(&self, org: &str) -> anyhow::Result<Vec<Repository>> {
66 let url = self
67 .url
68 .join(&format!("v2/namespaces/{}/repositories", org)) .context("failed formatting the url with the provided org")?;
70
71 fetch::<Repository>(&self.client, &url)
72 .await
73 .context("fetching the provided url failed")
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use serde_json::json;
81
82 #[test]
83 fn test_repository_serde() {
84 let value = json!({
85 "name": "ollama",
86 "namespace": "ollama",
87 "repository_type": "image",
88 "status": 1,
89 "status_description": "active",
90 "description": "The easiest way to get up and running with large language models.",
91 "is_private": false,
92 "star_count": 1183,
93 "pull_count": 13256501,
94 "last_updated": "2025-03-04T04:01:22.754331Z",
95 "last_modified": "2024-10-16T13:48:34.145251Z",
96 "date_registered": "2023-06-29T23:27:34.326426Z",
97 "affiliation": "",
98 "media_types": [
99 "application/vnd.docker.container.image.v1+json",
100 "application/vnd.docker.distribution.manifest.list.v2+json",
101 "application/vnd.oci.image.config.v1+json",
102 "application/vnd.oci.image.index.v1+json"
103 ],
104 "content_types": [
105 "image"
106 ],
107 "categories": [
108 {
109 "name": "Machine Learning & AI",
110 "slug": "machine-learning-and-ai"
111 },
112 {
113 "name": "Developer Tools",
114 "slug": "developer-tools"
115 }
116 ],
117 "storage_size": 662988133055 as u64,
118 });
119
120 let repository = serde_json::from_value::<Repository>(value)
121 .context("failed to deserialize the repository payload")
122 .unwrap();
123
124 println!("{repository:#?}");
125 }
126
127 #[tokio::test]
128 async fn test_list_repositories() -> anyhow::Result<()> {
129 let pat =
130 std::env::var("DOCKER_PAT").context("environment variable `DOCKER_PAT` is not set")?;
131 let dh =
132 DockerHubClient::new(&pat).context("the docker hub client couldn't be instantiated")?;
133
134 println!("{:#?}", dh.list_repositories("ollama").await);
135
136 Ok(())
137 }
138}