Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 81 additions & 35 deletions src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use thiserror::Error;
pub enum TransportConversionError {
#[error("Invalid transport: {0}")]
InvalidTransport(Box<str>),
#[error("Missing // in docker:// in {0}")]
MissingDockerSlashes(Box<str>),
#[error("Missing ':' in imgref")]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't used anymore, but removing it makes semver break

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right...we could do a semver break though

MissingColon,
}
Expand All @@ -16,14 +18,14 @@ pub enum TransportConversionError {
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ImageReferenceError {
#[error("Unknown transport '{0}'")]
#[error("Invalid transport: {0}")]
InvalidTransport(Box<str>),
#[error("Missing // in docker:// in {0}")]
MissingDockerSlashes(Box<str>),
#[error("Missing ':' in {0}")]
MissingColon(Box<str>),
#[error("Invalid empty name in {0}")]
EmptyName(Box<str>),
#[error("Missing // in docker:// in {0}")]
MissingDockerSlashes(Box<str>),
}

/// A backend/transport for OCI/Docker images.
Expand Down Expand Up @@ -71,34 +73,36 @@ impl TryFrom<&str> for Transport {
/// Supports various transport types like "registry:", "oci:", "docker://", etc.
/// Returns an error for unknown transports or malformed references without colons.
fn try_from(imgref: &str) -> Result<Self, TransportConversionError> {
if let Some(colon_pos) = imgref.find(':') {
let transport_prefix = &imgref[..colon_pos];

let transport = match transport_prefix {
"registry" => Transport::Registry,
"oci" => Transport::OciDir,
"oci-archive" => Transport::OciArchive,
"docker-archive" => Transport::DockerArchive,
"containers-storage" => Transport::ContainerStorage,
"dir" => Transport::Dir,
"docker-daemon" => Transport::DockerDaemon,
"docker" => {
// Check if this is actually "docker://" format
if imgref[colon_pos..].starts_with("://") {
Transport::Registry
} else {
return Err(TransportConversionError::InvalidTransport(
transport_prefix.into(),
));
}
}
prefix => return Err(TransportConversionError::InvalidTransport(prefix.into())),
};
let (transport_name, rest) = match imgref.find(':') {
Some(colon_pos) => (&imgref[..colon_pos], &imgref[colon_pos..]),
Comment on lines +76 to +77
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer split_once for stuff like this

// A simple transport like "oci", "registry" was passed in
None => (imgref, ""),
};

return Ok(transport);
}
let transport = match transport_name {
"registry" => Transport::Registry,
"oci" => Transport::OciDir,
"oci-archive" => Transport::OciArchive,
"docker-archive" => Transport::DockerArchive,
"containers-storage" => Transport::ContainerStorage,
"dir" => Transport::Dir,
"docker-daemon" => Transport::DockerDaemon,
"docker" => {
// Check if this is actually "docker://" format
if rest.starts_with("://") {
Transport::Registry
} else {
return Err(
TransportConversionError::MissingDockerSlashes(imgref.into()).into(),
);
}
}
prefix => {
return Err(TransportConversionError::InvalidTransport(prefix.into()).into());
}
};

Err(TransportConversionError::MissingColon)
Ok(transport)
}
}

Expand Down Expand Up @@ -453,11 +457,11 @@ mod tests {
// Test missing colon (bare image reference without transport)
assert!(matches!(
Transport::try_from("docker.io/library/hello-world"),
Err(TransportConversionError::MissingColon)
Err(TransportConversionError::InvalidTransport(_))
));
assert!(matches!(
Transport::try_from("example.com/image"),
Err(TransportConversionError::MissingColon)
Err(TransportConversionError::InvalidTransport(_))
));

// Test invalid transport prefixes
Expand All @@ -473,13 +477,13 @@ mod tests {
// Test docker: without :// (should error)
assert!(matches!(
Transport::try_from("docker:example.com/image"),
Err(TransportConversionError::InvalidTransport(_))
Err(TransportConversionError::MissingDockerSlashes(_))
));

// Test empty string
assert!(matches!(
Transport::try_from(""),
Err(TransportConversionError::MissingColon)
Err(TransportConversionError::InvalidTransport(_))
));

// Test just colon
Expand All @@ -489,6 +493,45 @@ mod tests {
));
}

#[test]
fn test_bare_transport_parsing() {
// Test parsing bare transport names without image references
assert!(matches!(
Transport::try_from("registry"),
Ok(Transport::Registry)
));
assert!(matches!(Transport::try_from("oci"), Ok(Transport::OciDir)));
assert!(matches!(
Transport::try_from("oci-archive"),
Ok(Transport::OciArchive)
));
assert!(matches!(
Transport::try_from("docker-archive"),
Ok(Transport::DockerArchive)
));
assert!(matches!(
Transport::try_from("containers-storage"),
Ok(Transport::ContainerStorage)
));
assert!(matches!(Transport::try_from("dir"), Ok(Transport::Dir)));
assert!(matches!(
Transport::try_from("docker-daemon"),
Ok(Transport::DockerDaemon)
));

// Test that bare "docker" fails (needs docker://)
assert!(matches!(
Transport::try_from("docker"),
Err(TransportConversionError::MissingDockerSlashes(_))
));

// Test unknown bare transport
assert!(matches!(
Transport::try_from("unknown"),
Err(TransportConversionError::InvalidTransport(_))
));
}

#[test]
fn test_transport_edge_cases() {
// Test transport at end of string
Expand Down Expand Up @@ -520,8 +563,11 @@ mod tests {
let err = TransportConversionError::InvalidTransport("unknown".into());
assert_eq!(err.to_string(), "Invalid transport: unknown");

let err = TransportConversionError::MissingColon;
assert_eq!(err.to_string(), "Missing ':' in imgref");
let err = TransportConversionError::MissingDockerSlashes("docker:example.com".into());
assert_eq!(
err.to_string(),
"Missing // in docker:// in docker:example.com"
);
}

#[test]
Expand Down
Loading