gproxy-protocol 4.1.0

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use super::request::{ResolvedVideoResource, VideoResourceRole};
use crate::{
    Rest,
    transform::TransformError,
    wire::{gemini::video as g, openai::video as o},
};
use std::collections::BTreeMap;

pub(super) fn image_resource(
    reference: &str,
    resources: &BTreeMap<String, ResolvedVideoResource>,
    role: VideoResourceRole,
) -> Result<g::VideoImage, TransformError> {
    let resource = bound(reference, resources, "image/")?;
    let _ = role;
    Ok(g::VideoImage {
        bytes_base64_encoded: Some(resource.bytes_base64_encoded.clone().ok_or_else(|| {
            TransformError::missing_metadata(format!("video resource {reference} bytes"))
        })?),
        mime_type: Some(resource.mime_type.clone().ok_or_else(|| {
            TransformError::missing_metadata(format!("video resource {reference} mime"))
        })?),
        rest: Rest::new(),
    })
}

pub(super) fn frame_role(frame: &o::FrameImage) -> VideoResourceRole {
    match frame.frame_type {
        o::FrameType::FirstFrame => VideoResourceRole::FirstFrame,
        o::FrameType::LastFrame => VideoResourceRole::LastFrame,
    }
}

pub(super) fn frame_from_resource(
    image: &g::VideoImage,
    frame_type: o::FrameType,
    resources: &BTreeMap<String, ResolvedVideoResource>,
) -> Result<o::FrameImage, TransformError> {
    Ok(o::FrameImage::builder(
        o::VideoImageType::ImageUrl,
        o::ReferenceUrl::builder(published_url(image, resources)?).build(),
        frame_type,
    )
    .build())
}

pub(super) fn published_url(
    image: &g::VideoImage,
    resources: &BTreeMap<String, ResolvedVideoResource>,
) -> Result<String, TransformError> {
    let reference = image
        .bytes_base64_encoded
        .as_deref()
        .ok_or_else(|| TransformError::missing_metadata("reference image bytes"))?;
    let resource = bound(reference, resources, "image/")?;
    if resource.bytes_base64_encoded.as_deref() != Some(reference)
        || resource.mime_type.as_ref() != image.mime_type.as_ref()
    {
        return Err(TransformError::shape(
            "image.resource",
            "published resource does not match source bytes and MIME",
        ));
    }
    url(resource
        .url
        .as_deref()
        .ok_or_else(|| TransformError::missing_metadata("published image URL"))?)
}

pub(super) fn video_url(
    video: &g::Video,
    resources: &BTreeMap<String, ResolvedVideoResource>,
) -> Result<String, TransformError> {
    if let Some(uri) = &video.uri {
        if uri.is_empty() {
            return Err(TransformError::invalid_result(
                "instances.video.uri",
                "empty video URI",
            ));
        }
        return url(uri);
    }
    let encoded = video
        .encoded_video
        .as_deref()
        .ok_or_else(|| TransformError::missing_metadata("instances.video.uri or encodedVideo"))?;
    let resource = bound(encoded, resources, "video/")?;
    if resource.bytes_base64_encoded.as_deref() != Some(encoded)
        || resource.mime_type.as_ref() != video.encoding.as_ref()
    {
        return Err(TransformError::shape(
            "video.resource",
            "published resource does not match bytes/MIME",
        ));
    }
    url(resource
        .url
        .as_deref()
        .ok_or_else(|| TransformError::missing_metadata("published video URL"))?)
}

pub(super) fn bound<'a>(
    key: &str,
    resources: &'a BTreeMap<String, ResolvedVideoResource>,
    mime_prefix: &str,
) -> Result<&'a ResolvedVideoResource, TransformError> {
    use base64::Engine;
    let resource = resources
        .get(key)
        .ok_or_else(|| TransformError::missing_metadata("bound video/image resource"))?;
    if resource.reference != key {
        return Err(TransformError::shape(
            "resource.reference",
            "resource key and declared reference disagree",
        ));
    }
    let bytes = resource
        .bytes_base64_encoded
        .as_deref()
        .ok_or_else(|| TransformError::missing_metadata("resource bytes"))?;
    base64::engine::general_purpose::STANDARD
        .decode(bytes)
        .map_err(|e| TransformError::shape("resource.bytes", e.to_string()))?;
    let mime = resource
        .mime_type
        .as_deref()
        .ok_or_else(|| TransformError::missing_metadata("resource MIME"))?;
    if !mime.starts_with(mime_prefix) {
        return Err(TransformError::shape(
            "resource MIME",
            "resource MIME differs from required media role",
        ));
    }
    Ok(resource)
}

pub(super) fn url(value: &str) -> Result<String, TransformError> {
    let uri: http::Uri = value
        .parse()
        .map_err(|e: http::uri::InvalidUri| TransformError::shape("resource.url", e.to_string()))?;
    if !matches!(uri.scheme_str(), Some("http" | "https"))
        || uri.authority().is_none()
        || uri.authority().unwrap().as_str().contains('@')
    {
        return Err(TransformError::shape(
            "resource.url",
            "absolute public HTTP(S) URL required",
        ));
    }
    Ok(value.to_owned())
}