greentic-component 0.5.1

High-level component loader and store for Greentic components
Documentation
use anyhow::{Result, anyhow};
use bytes::Bytes;
use oci_distribution::Reference;
use oci_distribution::client::{Client, ClientConfig};
use oci_distribution::secrets::RegistryAuth;

pub async fn fetch(reference: &str) -> Result<Bytes> {
    let reference: Reference = reference
        .parse()
        .map_err(|err| anyhow!("invalid OCI reference '{reference}': {err}"))?;

    let client = Client::new(ClientConfig::default());
    let auth = RegistryAuth::Anonymous;
    let accepted_media_types = accepted_media_types();
    let image = client.pull(&reference, &auth, accepted_media_types).await?;

    let bytes = select_component_layer(
        image
            .layers
            .into_iter()
            .map(|layer| (layer.media_type, layer.data)),
    )
    .ok_or_else(|| anyhow!("no suitable component layer found for {reference}"))?;
    Ok(Bytes::from(bytes))
}

fn accepted_media_types() -> Vec<&'static str> {
    vec![
        "application/wasm",
        "application/octet-stream",
        "application/vnd.module.wasm.content.layer.v1+wasm",
        "application/vnd.module.wasm.content.layer.v1+tar",
    ]
}

fn select_component_layer(layers: impl IntoIterator<Item = (String, Vec<u8>)>) -> Option<Vec<u8>> {
    for (media_type, data) in layers {
        let media_type = media_type.to_ascii_lowercase();
        if media_type.contains("application/wasm") || media_type.contains("octet-stream") {
            return Some(data);
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn fetch_rejects_invalid_oci_reference_before_network() {
        let err = fetch("not a valid reference")
            .await
            .expect_err("should fail");
        assert!(err.to_string().contains("invalid OCI reference"));
    }

    #[test]
    fn accepted_media_types_include_wasm_and_octet_stream_variants() {
        let media_types = accepted_media_types();
        assert!(media_types.contains(&"application/wasm"));
        assert!(media_types.contains(&"application/octet-stream"));
        assert!(media_types.contains(&"application/vnd.module.wasm.content.layer.v1+wasm"));
    }

    #[test]
    fn select_component_layer_prefers_first_matching_wasm_payload() {
        let selected = select_component_layer(vec![
            ("application/json".to_string(), b"meta".to_vec()),
            ("application/octet-stream".to_string(), b"wasm".to_vec()),
            ("application/wasm".to_string(), b"second".to_vec()),
        ]);

        assert_eq!(selected, Some(b"wasm".to_vec()));
    }

    #[test]
    fn select_component_layer_returns_none_when_no_supported_layers_exist() {
        let selected = select_component_layer(vec![
            ("application/json".to_string(), b"meta".to_vec()),
            ("text/plain".to_string(), b"readme".to_vec()),
        ]);

        assert!(selected.is_none());
    }
}