1#[derive(Debug, Clone)]
4pub struct ImageRef {
5 pub registry: String,
6 pub repository: String,
7 pub tag: String,
8}
9
10#[derive(Debug, thiserror::Error)]
11pub enum RegistryError {
12 #[error("invalid image reference: {0}")]
13 InvalidRef(String),
14 #[error("registry error: {0}")]
15 Registry(String),
16 #[error("HTTP error: {0}")]
17 Http(String),
18}
19
20impl ImageRef {
21 pub fn parse(reference: &str) -> Result<Self, RegistryError> {
22 let (name, tag) = if let Some((n, t)) = reference.rsplit_once(':') {
23 if n.contains('/') || !t.contains('/') {
25 (n.to_string(), t.to_string())
26 } else {
27 (reference.to_string(), "latest".to_string())
28 }
29 } else {
30 (reference.to_string(), "latest".to_string())
31 };
32
33 let (registry, repository) =
34 if name.contains('.') || name.contains(':') || name.contains("localhost") {
35 if let Some((reg, repo)) = name.split_once('/') {
37 (reg.to_string(), repo.to_string())
38 } else {
39 (name.clone(), name)
40 }
41 } else if name.contains('/') {
42 ("registry-1.docker.io".to_string(), name)
44 } else {
45 (
47 "registry-1.docker.io".to_string(),
48 format!("library/{}", name),
49 )
50 };
51
52 Ok(ImageRef {
53 registry,
54 repository,
55 tag,
56 })
57 }
58}
59
60impl std::fmt::Display for ImageRef {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "{}/{}:{}", self.registry, self.repository, self.tag)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn parse_simple_reference() {
72 let r = ImageRef::parse("ubuntu:22.04").unwrap();
73 assert_eq!(r.registry, "registry-1.docker.io");
74 assert_eq!(r.repository, "library/ubuntu");
75 assert_eq!(r.tag, "22.04");
76 }
77
78 #[test]
79 fn parse_full_reference() {
80 let r = ImageRef::parse("ghcr.io/owner/repo:v1.0").unwrap();
81 assert_eq!(r.registry, "ghcr.io");
82 assert_eq!(r.repository, "owner/repo");
83 assert_eq!(r.tag, "v1.0");
84 }
85
86 #[test]
87 fn parse_no_tag_defaults_latest() {
88 let r = ImageRef::parse("alpine").unwrap();
89 assert_eq!(r.tag, "latest");
90 }
91
92 #[test]
93 fn parse_with_port() {
94 let r = ImageRef::parse("localhost:5000/myimage:dev").unwrap();
95 assert_eq!(r.registry, "localhost:5000");
96 assert_eq!(r.repository, "myimage");
97 assert_eq!(r.tag, "dev");
98 }
99}