From b7ce8db51da58d40968bc6bd27b0214dd3a827b8 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Wed, 7 Oct 2026 23:30:28 -0400 Subject: [PATCH] Replace media-types varient with media-types constants Signed-off-by: Scott Andrews --- Cargo.lock | 34 +-- Cargo.toml | 2 +- Makefile | 2 +- components/client/client.wac | 6 + .../{client => internal-client}/Cargo.toml | 2 +- components/internal-client/README.md | 3 + .../{client => internal-client}/src/lib.rs | 216 ++++++------------ .../tests/blobs.rs | 0 .../tests/manifests.rs | 0 components/wit/worlds.wit | 1 + wit/client.wit | 59 +---- 11 files changed, 114 insertions(+), 211 deletions(-) create mode 100644 components/client/client.wac rename components/{client => internal-client}/Cargo.toml (95%) create mode 100644 components/internal-client/README.md rename components/{client => internal-client}/src/lib.rs (92%) rename components/{client => internal-client}/tests/blobs.rs (100%) rename components/{client => internal-client}/tests/manifests.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 0868ee6..e6720dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,23 +172,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "client" -version = "0.1.0" -dependencies = [ - "chrono", - "httpdate", - "regex", - "serde", - "serde_json", - "sha2 0.11.0", - "test-harness", - "tokio", - "url", - "wasmtime", - "wit-bindgen", -] - [[package]] name = "cobs" version = "0.3.0" @@ -909,6 +892,23 @@ dependencies = [ "serde_core", ] +[[package]] +name = "internal-client" +version = "0.1.0" +dependencies = [ + "chrono", + "httpdate", + "regex", + "serde", + "serde_json", + "sha2 0.11.0", + "test-harness", + "tokio", + "url", + "wasmtime", + "wit-bindgen", +] + [[package]] name = "io-extras" version = "0.19.0" diff --git a/Cargo.toml b/Cargo.toml index a8d8a46..6f2d185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" members = [ - "components/client", + "components/internal-client", "crates/test-harness", ] diff --git a/Makefile b/Makefile index 6434fb5..1b65a01 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,7 @@ SIGN ?= true PUBLISH_LOG ?= # the files that can be published, e.g. client.wasm, published from target/components/client/client.wasm -PUBLISH_FILES := interface.wasm $(foreach component,$(filter-out dep-% test-%,$(COMPONENTS)),$(component).wasm $(component).debug.wasm) +PUBLISH_FILES := interface.wasm $(foreach component,$(filter-out dep-% internal-% test-%,$(COMPONENTS)),$(component).wasm $(component).debug.wasm) .PHONY: publish ## Publish each component in the target/components directory publish: $(addprefix publish-,$(PUBLISH_FILES)) diff --git a/components/client/client.wac b/components/client/client.wac new file mode 100644 index 0000000..076d462 --- /dev/null +++ b/components/client/client.wac @@ -0,0 +1,6 @@ +package componentized:oci; + +export new local:internal-client { + media-types: new local:media-types {}.media-types, + ... +}...; diff --git a/components/client/Cargo.toml b/components/internal-client/Cargo.toml similarity index 95% rename from components/client/Cargo.toml rename to components/internal-client/Cargo.toml index 0a4e9a3..b94d109 100644 --- a/components/client/Cargo.toml +++ b/components/internal-client/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "client" +name = "internal-client" version = "0.1.0" edition = "2024" license = "Apache-2.0" diff --git a/components/internal-client/README.md b/components/internal-client/README.md new file mode 100644 index 0000000..1c586a3 --- /dev/null +++ b/components/internal-client/README.md @@ -0,0 +1,3 @@ +# `client` + +OCI image client component. diff --git a/components/client/src/lib.rs b/components/internal-client/src/lib.rs similarity index 92% rename from components/client/src/lib.rs rename to components/internal-client/src/lib.rs index 8e8c486..65b9060 100644 --- a/components/client/src/lib.rs +++ b/components/internal-client/src/lib.rs @@ -8,12 +8,16 @@ use sha2::Digest as _; use url::Url; use wit_bindgen::{FutureReader, StreamReader, StreamResult}; +#[cfg(not(test))] +use crate::componentized::oci::media_types; + use crate::{ - componentized::http::client::{self as http, HttpResponse}, + componentized::{ + http::client::{self as http, HttpResponse}, + }, exports::componentized::oci::client::{ - Config, Digest, DigestAlgorithm, ErrorCode, Guest, Instant, Manifest, - MediaType::{self, Other}, - MediaTypeSuffix, OciDescriptorV1, OciImageConfigV1, OciImageConfigV1Config, + Config, Digest, DigestAlgorithm, ErrorCode, Guest, Instant, Manifest, MediaType, + OciDescriptorV1, OciImageConfigV1, OciImageConfigV1Config, OciImageConfigV1ContentAddresses, OciImageConfigV1HistoryEntry, OciImageIndexManifestV1, OciImageIndexManifestV1Manifest, OciImageIndexManifestV1ManifestPlatform, OciImageManifestV1, Reference, RetryAfter, WasmConfigV0, WasmConfigV0Component, @@ -25,11 +29,6 @@ pub(crate) struct OCIClient; /// the size of each read from the registry while streaming a blob const BLOB_CHUNK_SIZE: usize = 64 * 1024; -/// the manifest media types the client accepts, a registry may serve a different manifest for a -/// tag depending on the media types accepted -const MANIFEST_ACCEPT: &str = - "application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json"; - impl Guest for OCIClient { #[allow(async_fn_in_trait)] fn parse_reference(reference: String) -> Result { @@ -53,7 +52,16 @@ impl Guest for OCIClient { if let Some(digest) = digest { return Ok(digest); } - let headers = vec![("Accept".to_string(), MANIFEST_ACCEPT.to_string())]; + let headers = vec![ + ( + "Accept".to_string(), + media_types::application_vnd_oci_image_manifest_v1_json(), + ), + ( + "Accept".to_string(), + media_types::application_vnd_oci_image_index_v1_json(), + ), + ]; // the same manifest `get-manifest` gets for the tag let bytes = Transport::fetch(url, headers, None) @@ -128,7 +136,16 @@ impl Guest for OCIClient { #[allow(async_fn_in_trait)] async fn get_manifest(reference: Reference) -> Result { let (url, digest) = Self::manifest_url(reference); - let headers = vec![("Accept".to_string(), MANIFEST_ACCEPT.to_string())]; + let headers = vec![ + ( + "Accept".to_string(), + media_types::application_vnd_oci_image_manifest_v1_json(), + ), + ( + "Accept".to_string(), + media_types::application_vnd_oci_image_index_v1_json(), + ), + ]; // a manifest requested by tag has no digest to verify let raw = Transport::fetch(url, headers, digest) .await? @@ -317,67 +334,17 @@ impl OCIClient { }) } - fn normalize_media_type(media_type: &str) -> MediaType { - match media_type { - "application/vnd.oci.descriptor.v1+json" => { - MediaType::ApplicationVndOciDescriptorV1(MediaTypeSuffix::Json) - } - "application/vnd.oci.layout.header.v1+json" => { - MediaType::ApplicationVndOciLayoutHeaderV1(MediaTypeSuffix::Json) - } - "application/vnd.oci.image.index.v1+json" => { - MediaType::ApplicationVndOciImageIndexV1(MediaTypeSuffix::Json) - } - "application/vnd.oci.image.manifest.v1+json" => { - MediaType::ApplicationVndOciImageManifestV1(MediaTypeSuffix::Json) - } - "application/vnd.oci.image.config.v1+json`" => { - MediaType::ApplicationVndOciImageConfigV1(MediaTypeSuffix::Json) - } - "application/vnd.oci.image.layer.v1.tar" => { - MediaType::ApplicationVndOciImageLayerV1Tar(MediaTypeSuffix::Other(None)) - } - "application/vnd.oci.image.layer.v1.tar+gzip" => { - MediaType::ApplicationVndOciImageLayerV1Tar(MediaTypeSuffix::Gzip) - } - "application/vnd.oci.image.layer.v1.tar+zstd" => { - MediaType::ApplicationVndOciImageLayerV1Tar(MediaTypeSuffix::Zstd) - } - "application/vnd.oci.image.layer.nondistributable.v1.tar" => { - MediaType::ApplicationVndOciImageLayerNondistributableV1Tar(MediaTypeSuffix::Other( - None, - )) - } - "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" => { - MediaType::ApplicationVndOciImageLayerNondistributableV1Tar(MediaTypeSuffix::Gzip) - } - "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd" => { - MediaType::ApplicationVndOciImageLayerNondistributableV1Tar(MediaTypeSuffix::Zstd) - } - "application/vnd.oci.empty.v1+json" => { - MediaType::ApplicationVndOciEmptyV1(MediaTypeSuffix::Json) - } - "application/vnd.wasm.config.v0+json" => { - MediaType::ApplicationVndWasmConfigV0(MediaTypeSuffix::Json) - } - "application/wasm" => MediaType::ApplicationWasm, - _ => Other(media_type.to_owned()), - } - } - fn parse_manifest(v: &Value, field: &str) -> Result, ErrorCode> { let media_type = Self::required( Self::parse_media_type(&v["mediaType"], &format!("{field}.mediaType")), "mediaType", )?; - match media_type { - MediaType::ApplicationVndOciImageIndexV1(MediaTypeSuffix::Json) => { - Self::parse_oci_image_index_v1(&v, field) - } - MediaType::ApplicationVndOciImageManifestV1(MediaTypeSuffix::Json) => { - Self::parse_oci_image_manifest_v1(&v, field) - } - _ => Ok(Some(Manifest::Other(serde_json::to_vec(v)?))), + if media_type == media_types::application_vnd_oci_image_index_v1_json() { + Self::parse_oci_image_index_v1(&v, field) + } else if media_type == media_types::application_vnd_oci_image_manifest_v1_json() { + Self::parse_oci_image_manifest_v1(&v, field) + } else { + Ok(Some(Manifest::Other(serde_json::to_vec(v)?))) } } @@ -554,14 +521,12 @@ impl OCIClient { .map(|mt| mt.or(default_media_type)), &format!("{field}.mediaType"), )?; - match media_type { - MediaType::ApplicationVndOciImageConfigV1(MediaTypeSuffix::Json) => { - Self::parse_oci_image_config_v1(&v, field) - } - MediaType::ApplicationVndWasmConfigV0(MediaTypeSuffix::Json) => { - Self::parse_wasm_config_v0(&v, field) - } - _ => Ok(Some(Config::Other(serde_json::to_vec(v)?))), + if media_type == media_types::application_vnd_oci_image_config_v1_json() { + Self::parse_oci_image_config_v1(&v, field) + } else if media_type == media_types::application_vnd_wasm_config_v0_json() { + Self::parse_wasm_config_v0(&v, field) + } else { + Ok(Some(Config::Other(serde_json::to_vec(v)?))) } } @@ -812,7 +777,7 @@ impl OCIClient { } fn parse_media_type(v: &Value, field: &str) -> Result, ErrorCode> { - Ok(Self::parse_string(v, field)?.map(|v| Self::normalize_media_type(&v))) + Self::parse_string(v, field) } fn parse_instant(v: &Value, field: &str) -> Result, ErrorCode> { @@ -1352,64 +1317,6 @@ impl PartialEq for Instant { } } -impl PartialEq for MediaType { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - ( - MediaType::ApplicationVndOciDescriptorV1(this), - MediaType::ApplicationVndOciDescriptorV1(other), - ) => this == other, - ( - MediaType::ApplicationVndOciLayoutHeaderV1(this), - MediaType::ApplicationVndOciLayoutHeaderV1(other), - ) => this == other, - ( - MediaType::ApplicationVndOciImageIndexV1(this), - MediaType::ApplicationVndOciImageIndexV1(other), - ) => this == other, - ( - MediaType::ApplicationVndOciImageManifestV1(this), - MediaType::ApplicationVndOciImageManifestV1(other), - ) => this == other, - ( - MediaType::ApplicationVndOciImageConfigV1(this), - MediaType::ApplicationVndOciImageConfigV1(other), - ) => this == other, - ( - MediaType::ApplicationVndOciImageLayerV1Tar(this), - MediaType::ApplicationVndOciImageLayerV1Tar(other), - ) => this == other, - ( - MediaType::ApplicationVndOciEmptyV1(this), - MediaType::ApplicationVndOciEmptyV1(other), - ) => this == other, - ( - MediaType::ApplicationVndOciImageLayerNondistributableV1Tar(this), - MediaType::ApplicationVndOciImageLayerNondistributableV1Tar(other), - ) => this == other, - ( - MediaType::ApplicationVndWasmConfigV0(this), - MediaType::ApplicationVndWasmConfigV0(other), - ) => this == other, - (MediaType::ApplicationWasm, MediaType::ApplicationWasm) => true, - (Other(this), Other(other)) => this == other, - _ => false, - } - } -} - -impl PartialEq for MediaTypeSuffix { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (MediaTypeSuffix::Json, MediaTypeSuffix::Json) => true, - (MediaTypeSuffix::Gzip, MediaTypeSuffix::Gzip) => true, - (MediaTypeSuffix::Zstd, MediaTypeSuffix::Zstd) => true, - (MediaTypeSuffix::Other(this), MediaTypeSuffix::Other(other)) => this == other, - _ => false, - } - } -} - impl PartialEq for Manifest { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -1565,6 +1472,27 @@ wit_bindgen::generate!({ export!(OCIClient); +/// The values of the imported `media-types` used by the client, unit tests run natively without +/// a component to import them from. +#[cfg(test)] +mod media_types { + pub fn application_vnd_oci_image_index_v1_json() -> String { + "application/vnd.oci.image.index.v1+json".to_string() + } + pub fn application_vnd_oci_image_manifest_v1_json() -> String { + "application/vnd.oci.image.manifest.v1+json".to_string() + } + pub fn application_vnd_oci_image_config_v1_json() -> String { + "application/vnd.oci.image.config.v1+json".to_string() + } + pub fn application_vnd_wasm_config_v0_json() -> String { + "application/vnd.wasm.config.v0+json".to_string() + } + pub fn application_wasm() -> String { + "application/wasm".to_string() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1955,7 +1883,7 @@ mod tests { config: OciDescriptorV1 { annotations: None, digest: Digest { algorithm: DigestAlgorithm::Sha256, encoded: "80d83bbdaa82cff96584c99217c29fd17bc7e5f0424c0cb26aa3831ea18132b4".to_string() }, - media_type: MediaType::ApplicationVndWasmConfigV0(MediaTypeSuffix::Json), + media_type: media_types::application_vnd_wasm_config_v0_json(), size: 345, urls: None, artifact_type: None, @@ -1966,13 +1894,13 @@ mod tests { ("org.opencontainers.image.title".to_string(), "client.wasm".to_string()), ])), digest: Digest { algorithm: DigestAlgorithm::Sha256, encoded: "ee7ff5c9588e997b4a54b6d351b52a5ca4f6980377f59e48c778f48a23b483db".to_string() }, - media_type: MediaType::ApplicationWasm, + media_type: media_types::application_wasm(), size: 1894585, urls: None, artifact_type: None, data: None, }], - media_type: MediaType::ApplicationVndOciImageManifestV1(MediaTypeSuffix::Json), + media_type: media_types::application_vnd_oci_image_manifest_v1_json(), schema_version: Some(2 as u8), subject: None, })), @@ -2007,12 +1935,12 @@ mod tests { }), "").unwrap(), Some(Manifest::OciImageIndexV1(OciImageIndexManifestV1 { schema_version: Some(2 as u8), - media_type: MediaType::ApplicationVndOciImageIndexV1(MediaTypeSuffix::Json), + media_type: media_types::application_vnd_oci_image_index_v1_json(), artifact_type: None, manifests: vec![ OciImageIndexManifestV1Manifest{ annotations:None, - media_type:MediaType::Other("application/vnd.docker.distribution.manifest.v2+json".to_string()), + media_type: MediaType::from("application/vnd.docker.distribution.manifest.v2+json"), size:743, platform:Some(OciImageIndexManifestV1ManifestPlatform{ architecture:"amd64".to_string(), @@ -2025,11 +1953,11 @@ mod tests { digest: Digest { algorithm: DigestAlgorithm::Sha256, encoded: "9434033b4008b51c0c9270dda9315ea4229901fee28a7980085091e9fd4b62b8".to_string() }, urls: None, data: None, - artifact_type: Some(MediaType::Other("application/vnd.docker.container.image.v1+json".to_string())) + artifact_type: Some(MediaType::from("application/vnd.docker.container.image.v1+json")), }, OciImageIndexManifestV1Manifest{ annotations:None, - media_type:MediaType::Other("application/vnd.docker.distribution.manifest.v2+json".to_string()), + media_type: MediaType::from("application/vnd.docker.distribution.manifest.v2+json"), size:743, platform:Some(OciImageIndexManifestV1ManifestPlatform{ architecture:"arm64".to_string(), @@ -2040,7 +1968,7 @@ mod tests { digest: Digest { algorithm: DigestAlgorithm::Sha256, encoded: "eac7a2bcae76b2bc5b5fed23033ffba56283462e315c2035b6ab5b2c8c80bd34".to_string() }, urls: None, data: None, - artifact_type: Some(MediaType::Other("application/vnd.docker.container.image.v1+json".to_string())), + artifact_type: Some(MediaType::from("application/vnd.docker.container.image.v1+json")), }, ], subject: None, @@ -2104,7 +2032,7 @@ mod tests { }, "User": "65532" } - }), "", Some(MediaType::ApplicationVndOciImageConfigV1(MediaTypeSuffix::Json))).unwrap(), + }), "", Some(media_types::application_vnd_oci_image_config_v1_json())).unwrap(), Some(Config::OciImageV1(OciImageConfigV1 { architecture: "amd64".to_string(), author: Some("github.com/ko-build/ko".to_string()), @@ -2194,7 +2122,7 @@ mod tests { } }), "", - Some(MediaType::ApplicationVndWasmConfigV0(MediaTypeSuffix::Json)) + Some(media_types::application_vnd_wasm_config_v0_json()) ) .unwrap(), Some(Config::WasmV0(WasmConfigV0 { diff --git a/components/client/tests/blobs.rs b/components/internal-client/tests/blobs.rs similarity index 100% rename from components/client/tests/blobs.rs rename to components/internal-client/tests/blobs.rs diff --git a/components/client/tests/manifests.rs b/components/internal-client/tests/manifests.rs similarity index 100% rename from components/client/tests/manifests.rs rename to components/internal-client/tests/manifests.rs diff --git a/components/wit/worlds.wit b/components/wit/worlds.wit index e097d5c..13b4b22 100644 --- a/components/wit/worlds.wit +++ b/components/wit/worlds.wit @@ -1,6 +1,7 @@ package componentized:oci-components; world client { + import componentized:oci/media-types@0.1.0-dev; import componentized:http/client@0.1.0; export componentized:oci/client@0.1.0-dev; } diff --git a/wit/client.wit b/wit/client.wit index a228ea9..793dac6 100644 --- a/wit/client.wit +++ b/wit/client.wit @@ -45,48 +45,13 @@ interface client { delay-seconds(u32), } - /// common media types for oci artifacts + /// Media type describing the content semantics and encoding + /// https://specs.opencontainers.org/image-spec/media-types/ @since(version = 0.1.0-dev) - variant media-type { - /// content descriptor - application-vnd-oci-descriptor-v1(media-type-suffix), - /// oci layout - application-vnd-oci-layout-header-v1(media-type-suffix), - /// image index - application-vnd-oci-image-index-v1(media-type-suffix), - /// image manifest - application-vnd-oci-image-manifest-v1(media-type-suffix), - /// image config - application-vnd-oci-image-config-v1(media-type-suffix), - /// layer, as a tar archive - application-vnd-oci-image-layer-v1-tar(media-type-suffix), - /// empty for unused descriptors - application-vnd-oci-empty-v1(media-type-suffix), - /// layer, as a tar archive - application-vnd-oci-image-layer-nondistributable-v1-tar(media-type-suffix), - /// wasm config - application-vnd-wasm-config-v0(media-type-suffix), - /// wasm - application-wasm, - /// all other values - other(string), - } - - /// encoding for a resource representation - @since(version = 0.1.0-dev) - variant media-type-suffix { - /// json encoded - json, - /// compressed with gzip - gzip, - /// compressed with zstd - zstd, - /// other encoding - other(option), - } + type media-type = string; /// OCI Descriptor v1 - /// https://github.com/opencontainers/image-spec/blob/main/descriptor.md + /// https://specs.opencontainers.org/image-spec/descriptor/ @since(version = 0.1.0-dev) record oci-descriptor-v1 { /// media type of the referenced content @@ -117,7 +82,7 @@ interface client { } /// OCI Image Manifest v1 - /// https://github.com/opencontainers/image-spec/blob/main/manifest.md + /// https://specs.opencontainers.org/image-spec/manifest/ @since(version = 0.1.0-dev) record oci-image-manifest-v1 { /// manifest schema version, must be 2 @@ -137,7 +102,7 @@ interface client { } /// OCI Image Index v1 - /// https://github.com/opencontainers/image-spec/blob/main/image-index.md + /// https://specs.opencontainers.org/image-spec/image-index/ @since(version = 0.1.0-dev) record oci-image-index-manifest-v1 { /// manifest schema version, must be 2 @@ -155,7 +120,7 @@ interface client { } /// OCI Image Index v1 - manifest entry - /// https://github.com/opencontainers/image-spec/blob/main/image-index.md + /// https://specs.opencontainers.org/image-spec/image-index/ @since(version = 0.1.0-dev) record oci-image-index-manifest-v1-manifest { /// media type of the referenced content @@ -180,7 +145,7 @@ interface client { } /// OCI Image Index v1 - manifest entry platform - /// https://github.com/opencontainers/image-spec/blob/main/image-index.md + /// https://specs.opencontainers.org/image-spec/image-index/ @since(version = 0.1.0-dev) record oci-image-index-manifest-v1-manifest-platform { /// CPU architecture which the binaries in this image are built to run on @@ -207,7 +172,7 @@ interface client { } /// OCI Image Config v1 - /// https://github.com/opencontainers/image-spec/blob/main/config.md + /// https://specs.opencontainers.org/image-spec/config/ @since(version = 0.1.0-dev) record oci-image-config-v1 { /// combined date and time at which the image was created @@ -233,7 +198,7 @@ interface client { } /// OCI Image Config v1 - config - /// https://github.com/opencontainers/image-spec/blob/main/config.md + /// https://specs.opencontainers.org/image-spec/config/ @since(version = 0.1.0-dev) record oci-image-config-v1-config { /// username or UID which is a platform-specific structure that allows specific control over which user the process run as @@ -259,7 +224,7 @@ interface client { } /// OCI Image Config v1 - content address - /// https://github.com/opencontainers/image-spec/blob/main/config.md + /// https://specs.opencontainers.org/image-spec/config/ @since(version = 0.1.0-dev) record oci-image-config-v1-content-addresses { /// must be 'layers' @@ -269,7 +234,7 @@ interface client { } /// OCI Image Config v1 - history entry - /// https://github.com/opencontainers/image-spec/blob/main/config.md + /// https://specs.opencontainers.org/image-spec/config/ @since(version = 0.1.0-dev) record oci-image-config-v1-history-entry { /// combined date and time at which the layer was created