gproxy-protocol 4.1.10

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use gproxy_protocol::{
    WireRequest,
    connection::{HeaderMap, Method},
    gemini::video::{GenerateVideoResponse, PredictLongRunningRequestBody, VideoOperation},
};
use serde_json::json;

#[test]
fn predict_long_running_serializes_developer_path_and_mldev_body() {
    let body: PredictLongRunningRequestBody = serde_json::from_value(json!({
        "instances":[{"prompt":"a red kite","image":{"bytesBase64Encoded":"AQI=","mimeType":"image/png"},"referenceImages":[{"image":{"bytesBase64Encoded":"AwQ=","mimeType":"image/jpeg"},"referenceType":"ASSET"}]}],
        "parameters":{"sampleCount":2,"durationSeconds":8,"aspectRatio":"16:9","resolution":"720p","personGeneration":"allow_adult","negativePrompt":"blur","enhancePrompt":true},
        "webhookConfig":{"uris":["https://example.test/hook"],"userMetadata":{"job":"42"}}
    })).unwrap();
    assert_eq!(body.instances[0].prompt.as_deref(), Some("a red kite"));
    assert_eq!(body.parameters.as_ref().unwrap().sample_count, Some(2));
    assert_eq!(
        body.webhook_config
            .as_ref()
            .unwrap()
            .user_metadata
            .as_ref()
            .unwrap()["job"],
        "42"
    );
    assert!(body.rest.is_empty());
    let request = WireRequest {
        method: Method::POST,
        path: "/v1beta/models/veo-3.0-generate-preview:predictLongRunning".into(),
        query: None,
        headers: HeaderMap::new(),
        body,
    };
    assert_eq!(
        request.path,
        "/v1beta/models/veo-3.0-generate-preview:predictLongRunning"
    );
    let value = serde_json::to_value(request.body).unwrap();
    assert_eq!(value["parameters"]["sampleCount"], 2);
    assert_eq!(value["parameters"]["durationSeconds"], 8);
    assert_eq!(value["webhookConfig"]["userMetadata"]["job"], "42");
    assert_eq!(
        value["instances"][0]["referenceImages"][0]["referenceType"],
        "ASSET"
    );
    assert!(value.get("model").is_none());
}

#[test]
fn operation_decodes_pending_success_filtered_and_error_states() {
    let pending: VideoOperation = serde_json::from_value(
        json!({"name":"operations/abc","done":false,"metadata":{"progress":42}}),
    )
    .unwrap();
    assert_eq!(pending.name.as_deref(), Some("operations/abc"));
    assert_eq!(pending.done, Some(false));
    assert!(pending.response.is_none());

    let completed: VideoOperation = serde_json::from_value(json!({"name":"operations/abc","done":true,"response":{"generateVideoResponse":{"generatedSamples":[{"video":{"uri":"https://cdn.test/v.mp4","encodedVideo":"AQI=","encoding":"video/mp4"}}],"raiMediaFilteredCount":1,"raiMediaFilteredReasons":["safety"]}}})).unwrap();
    assert!(completed.rest.is_empty());
    let response: GenerateVideoResponse =
        completed.response.unwrap().generate_video_response.unwrap();
    assert_eq!(
        response.generated_samples.unwrap()[0]
            .video
            .as_ref()
            .unwrap()
            .uri
            .as_deref(),
        Some("https://cdn.test/v.mp4")
    );
    assert_eq!(response.rai_media_filtered_count, Some(1));

    let failed: VideoOperation = serde_json::from_value(json!({"name":"operations/abc","done":true,"error":{"code":3,"message":"bad prompt","details":[{"@type":"type.googleapis.com/google.rpc.BadRequest","field":"prompt"}]}})).unwrap();
    assert_eq!(failed.error.unwrap().code, Some(3));
}

#[test]
fn declared_video_fields_cover_sources_aliases_and_arbitrary_metadata() {
    use gproxy_protocol::wire::DeclaredFields;
    let input:PredictLongRunningRequestBody=serde_json::from_value(json!({
        "instances":[{"prompt":"extend","image":{"bytesBase64Encoded":"AQI=","mimeType":"image/png","foreign":1},"video":{"uri":"https://example/video","encodedVideo":"AwQ=","encoding":"video/mp4","foreign":2},"lastFrame":{"bytesBase64Encoded":"BQY=","mimeType":"image/jpeg"},"referenceImages":[{"referenceType":"ASSET","image":{"bytesBase64Encoded":"Bwg=","mimeType":"image/png"}}],"foreign":3}],
        "webhook_config":{"uris":["https://example/hook"],"user_metadata":{"future":{"nested":true}},"foreign":4},"foreign":5
    })).unwrap();
    let input = input.into_declared();
    let instance = &input.instances[0];
    assert_eq!(
        instance.video.as_ref().unwrap().encoded_video.as_deref(),
        Some("AwQ=")
    );
    assert_eq!(
        instance.video.as_ref().unwrap().encoding.as_deref(),
        Some("video/mp4")
    );
    assert_eq!(
        instance
            .last_frame
            .as_ref()
            .unwrap()
            .bytes_base64_encoded
            .as_deref(),
        Some("BQY=")
    );
    assert_eq!(instance.reference_images.as_ref().unwrap().len(), 1);
    assert!(instance.rest.is_empty());
    assert!(instance.image.as_ref().unwrap().rest.is_empty());
    assert!(instance.video.as_ref().unwrap().rest.is_empty());
    let webhook = input.webhook_config.as_ref().unwrap();
    assert!(webhook.rest.is_empty());
    assert_eq!(
        webhook.user_metadata.as_ref().unwrap()["future"]["nested"],
        true
    );
    let wire = serde_json::to_value(input).unwrap();
    assert!(wire.get("webhook_config").is_none());
    assert_eq!(
        wire["webhookConfig"]["userMetadata"]["future"]["nested"],
        true
    );

    let input:VideoOperation=serde_json::from_value(json!({"name":"models/veo/operations/id","done":true,"metadata":{"future":1},"error":{"code":3,"message":"bad","details":[{"@type":"type.googleapis.com/google.rpc.BadRequest","future":2}],"foreign":3},"foreign":4})).unwrap();
    let input = input.into_declared();
    assert_eq!(input.metadata.unwrap()["future"], 1);
    let error = input.error.unwrap();
    assert_eq!(error.message.as_deref(), Some("bad"));
    assert_eq!(error.details.unwrap()[0]["future"], 2);
    assert!(error.rest.is_empty());
    assert!(input.rest.is_empty());
}