oci_unpack/reference/mod.rs
1mod mediatype;
2mod parser;
3
4use crate::digest::Digest;
5
6pub use mediatype::MediaType;
7
8/// Errors from [`Reference::try_from`].
9#[derive(thiserror::Error, Debug)]
10pub enum ParseError {
11 #[error("Missing repository.")]
12 MissingRepository,
13
14 #[error("{0}")]
15 InvalidDigest(#[from] crate::digest::DigestError),
16}
17
18/// Reference to an image in an OCI registry.
19///
20/// The parser tries to be close to what `docker pull` does:
21///
22/// * If the reference does not include the hostname of the registry,
23/// it uses Docker Hub, and the repository namespace defaults to
24/// `library` if there is none. For example:
25///
26/// * `debian` is parsed as `registry-1.docker.io/library/debian`.
27/// * `nixos/nix` is parsed as `registry-1.docker.io/nixos/nix`.
28/// * It accepts any tag value after the last `:` character. If no tag
29/// is given, it uses `latest`.
30/// * It accepts a fixed digest (the last part after a `@` character), but
31/// only SHA256 and SHA512.
32///
33/// However, it does not try to be bug-for-bug compatible with Docker.
34///
35/// # Examples
36///
37/// ```
38/// # use oci_unpack::*;
39/// const REFERENCE: &str = "registry.example.com/foo/bar:1.23.4@sha256:123456789012345678901234567890123456789012345678901234567890ABCD";
40///
41/// let reference = Reference::try_from(REFERENCE).unwrap();
42/// assert_eq!(reference.registry, "registry.example.com");
43/// assert_eq!(reference.repository.namespace(), Some("foo"));
44/// assert_eq!(reference.repository.name(), "bar");
45/// assert_eq!(reference.tag, "1.23.4");
46///
47/// let digest = reference.digest.as_ref().unwrap();
48/// assert_eq!(digest.algorithm(), DigestAlgorithm::SHA256);
49/// assert_eq!(digest.hash_value(), "123456789012345678901234567890123456789012345678901234567890ABCD");
50/// ```
51///
52/// ```
53/// # use oci_unpack::*;
54/// let reference = Reference::try_from("debian:stable").unwrap();
55///
56/// assert_eq!(reference.repository.to_string(), "library/debian");
57/// assert_eq!(reference.tag, "stable");
58/// ```
59#[derive(Clone, Debug, PartialEq)]
60pub struct Reference<'a> {
61 /// Address of the registry server.
62 pub registry: &'a str,
63
64 /// Repository name.
65 pub repository: Repository<'a>,
66
67 /// Image tag.
68 pub tag: &'a str,
69
70 /// Manifest digest, if present.
71 pub digest: Option<Digest>,
72}
73
74/// Represents a repository name, like `library/debian`
75/// or `nixos/nix`.
76#[derive(Copy, Clone, Debug, PartialEq)]
77pub struct Repository<'a>(RepositoryInner<'a>);
78
79impl<'a> Repository<'a> {
80 pub(crate) fn components(namespace: &'a str, name: &'a str) -> Self {
81 Repository(RepositoryInner::Components(namespace, name))
82 }
83
84 pub(crate) fn full(name: &'a str) -> Self {
85 Repository(RepositoryInner::Full(name))
86 }
87
88 /// Return the name of this repository.
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// # use oci_unpack::*;
94 /// let reference = Reference::try_from("foo/bar:stable").unwrap();
95 /// assert_eq!(reference.repository.name(), "bar");
96 /// ```
97 pub fn name(&self) -> &str {
98 match self.0 {
99 RepositoryInner::Full(full) => full.split_once('/').map(|s| s.1).unwrap_or(full),
100 RepositoryInner::Components(_, name) => name,
101 }
102 }
103
104 /// Return the namespace of this repository, or `None` if
105 /// the repository does not contain a `/` character.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// # use oci_unpack::*;
111 /// let reference = Reference::try_from("foo/bar:stable").unwrap();
112 /// assert_eq!(reference.repository.namespace(), Some("foo"));
113 /// ```
114 pub fn namespace(&self) -> Option<&str> {
115 match self.0 {
116 RepositoryInner::Full(full) => full.split_once('/').map(|s| s.0),
117 RepositoryInner::Components(ns, _) => Some(ns),
118 }
119 }
120}
121
122#[derive(Copy, Clone, Debug, PartialEq)]
123enum RepositoryInner<'a> {
124 /// Full repository name. Namespace is optional.
125 Full(&'a str),
126
127 /// Namespace and name.
128 Components(&'a str, &'a str),
129}
130
131impl std::fmt::Display for Repository<'_> {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 match self.0 {
134 RepositoryInner::Full(full) => f.write_str(full),
135 RepositoryInner::Components(a, b) => write!(f, "{a}/{b}"),
136 }
137 }
138}
139
140impl<'a> TryFrom<&'a str> for Reference<'a> {
141 type Error = ParseError;
142
143 fn try_from(reference: &'a str) -> Result<Self, Self::Error> {
144 parser::parse(reference)
145 }
146}