From 6f201fbf717ff037f588d9c4573f174e7cbb051f Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 20 May 2026 13:51:30 +0530 Subject: [PATCH 1/2] Allow parsing bare transport names without image references Allow parsing bare transport names like "oci", "registry" etc without it being embedded in an image reference. We want to be able to use the Transport enum and ImageReference structs from here in bootc to reduce code duplication, and this piece is required for that as we have a lot of tests/instances in bootc where we want to parse just the transport string Signed-off-by: Pragyan Poudyal --- src/transport.rs | 77 ++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/transport.rs b/src/transport.rs index c13361b..63b6efb 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -8,6 +8,8 @@ use thiserror::Error; pub enum TransportConversionError { #[error("Invalid transport: {0}")] InvalidTransport(Box), + #[error("Missing // in docker:// in {0}")] + MissingDockerSlashes(Box), #[error("Missing ':' in imgref")] MissingColon, } @@ -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), + #[error("Missing // in docker:// in {0}")] + MissingDockerSlashes(Box), #[error("Missing ':' in {0}")] MissingColon(Box), #[error("Invalid empty name in {0}")] EmptyName(Box), - #[error("Missing // in docker:// in {0}")] - MissingDockerSlashes(Box), } /// A backend/transport for OCI/Docker images. @@ -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 { - 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..]), + // 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) } } @@ -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 @@ -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 @@ -520,8 +524,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] From 5eacd314faa1e0a75c048802406b4558356aa472 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 20 May 2026 13:57:38 +0530 Subject: [PATCH 2/2] Add tests for bare transport parsing Signed-off-by: Pragyan Poudyal --- src/transport.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/transport.rs b/src/transport.rs index 63b6efb..c818495 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -493,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