From 6c996c6bc16331bb839d4f58bdff96391445d3d6 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 10 Dec 2025 02:17:58 -0500 Subject: [PATCH] third request --- .../r3_upload_response.json | 9 ++ .../r3_upload_response.txt | 1 - .../objects/begin_object_image_upload.rs | 134 ++++++++++++++++++ .../src/api/requests/objects/mod.rs | 3 +- 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.json delete mode 100644 crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.txt create mode 100644 crates/api_clients/world_labs_client/src/api/requests/objects/begin_object_image_upload.rs diff --git a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.json b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.json new file mode 100644 index 0000000000..fe911e5919 --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.json @@ -0,0 +1,9 @@ +{ + "upload_id": "cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg", + "upload_url": "https://storage.googleapis.com/wlt-training-gsc-marble-assets-prod/temp/objects/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=wlt-training-gsc-sa%40wlt-training-gsc.iam.gserviceaccount.com%2F20251210%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20251210T001237Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-content-length-range&X-Goog-Signature=338a4058e69f0f4d717e64ffbd9ac5609b63a67a43a9cb5027a65822edafee2dcf6769dafda8d82f812b0a009c53af77df6c8163e2d884bee2ab1a33ce63894e43f5faac07553528feadcc009f71f9b6195f6a2f73c1c3f8e5772a63d53c7c0ffc937d3c96b221cf760367bf435318f6c463b029d7879f26064acb5ab8e404d18a16b9263a0157b4edcfffe840b682a6e8198f342dcee51b928f235e1618aa98fdd7ee55bca8f27017738037b81b3041f12a72903c0decbc0d83b3fd39bb0edd3b750441bc21e9a226723876e4a52164b61659678e24f8af001451fa84a14c01b372d71cd55abaacd67e2b32df55f65a49f6ba0640b5239ab23b7eb45ad30d54", + "required_headers": { + "Content-Type": "image/jpeg", + "x-goog-content-length-range": "0,1048576000" + }, + "curl_example": "curl -X PUT -H 'Content-Type: image/jpeg' -H 'x-goog-content-length-range: 0,1048576000' --upload-file my-file 'https://storage.googleapis.com/wlt-training-gsc-marble-assets-prod/temp/objects/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=wlt-training-gsc-sa%40wlt-training-gsc.iam.gserviceaccount.com%2F20251210%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20251210T001237Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-content-length-range&X-Goog-Signature=338a4058e69f0f4d717e64ffbd9ac5609b63a67a43a9cb5027a65822edafee2dcf6769dafda8d82f812b0a009c53af77df6c8163e2d884bee2ab1a33ce63894e43f5faac07553528feadcc009f71f9b6195f6a2f73c1c3f8e5772a63d53c7c0ffc937d3c96b221cf760367bf435318f6c463b029d7879f26064acb5ab8e404d18a16b9263a0157b4edcfffe840b682a6e8198f342dcee51b928f235e1618aa98fdd7ee55bca8f27017738037b81b3041f12a72903c0decbc0d83b3fd39bb0edd3b750441bc21e9a226723876e4a52164b61659678e24f8af001451fa84a14c01b372d71cd55abaacd67e2b32df55f65a49f6ba0640b5239ab23b7eb45ad30d54'" +} diff --git a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.txt b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.txt deleted file mode 100644 index 1331e83388..0000000000 --- a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r3_upload_response.txt +++ /dev/null @@ -1 +0,0 @@ -{"upload_id":"cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg","upload_url":"https://storage.googleapis.com/wlt-training-gsc-marble-assets-prod/temp/objects/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=wlt-training-gsc-sa%40wlt-training-gsc.iam.gserviceaccount.com%2F20251210%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20251210T001237Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-content-length-range&X-Goog-Signature=338a4058e69f0f4d717e64ffbd9ac5609b63a67a43a9cb5027a65822edafee2dcf6769dafda8d82f812b0a009c53af77df6c8163e2d884bee2ab1a33ce63894e43f5faac07553528feadcc009f71f9b6195f6a2f73c1c3f8e5772a63d53c7c0ffc937d3c96b221cf760367bf435318f6c463b029d7879f26064acb5ab8e404d18a16b9263a0157b4edcfffe840b682a6e8198f342dcee51b928f235e1618aa98fdd7ee55bca8f27017738037b81b3041f12a72903c0decbc0d83b3fd39bb0edd3b750441bc21e9a226723876e4a52164b61659678e24f8af001451fa84a14c01b372d71cd55abaacd67e2b32df55f65a49f6ba0640b5239ab23b7eb45ad30d54","required_headers":{"Content-Type":"image/jpeg","x-goog-content-length-range":"0,1048576000"},"curl_example":"curl -X PUT -H 'Content-Type: image/jpeg' -H 'x-goog-content-length-range: 0,1048576000' --upload-file my-file 'https://storage.googleapis.com/wlt-training-gsc-marble-assets-prod/temp/objects/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=wlt-training-gsc-sa%40wlt-training-gsc.iam.gserviceaccount.com%2F20251210%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20251210T001237Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-content-length-range&X-Goog-Signature=338a4058e69f0f4d717e64ffbd9ac5609b63a67a43a9cb5027a65822edafee2dcf6769dafda8d82f812b0a009c53af77df6c8163e2d884bee2ab1a33ce63894e43f5faac07553528feadcc009f71f9b6195f6a2f73c1c3f8e5772a63d53c7c0ffc937d3c96b221cf760367bf435318f6c463b029d7879f26064acb5ab8e404d18a16b9263a0157b4edcfffe840b682a6e8198f342dcee51b928f235e1618aa98fdd7ee55bca8f27017738037b81b3041f12a72903c0decbc0d83b3fd39bb0edd3b750441bc21e9a226723876e4a52164b61659678e24f8af001451fa84a14c01b372d71cd55abaacd67e2b32df55f65a49f6ba0640b5239ab23b7eb45ad30d54'"} \ No newline at end of file diff --git a/crates/api_clients/world_labs_client/src/api/requests/objects/begin_object_image_upload.rs b/crates/api_clients/world_labs_client/src/api/requests/objects/begin_object_image_upload.rs new file mode 100644 index 0000000000..72568e6ed9 --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/requests/objects/begin_object_image_upload.rs @@ -0,0 +1,134 @@ +use crate::api::api_types::object_id::ObjectId; +use crate::api::api_types::upload_id::UploadId; +use crate::api::api_types::upload_mime_type::UploadMimeType; +use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE}; +use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken; +use crate::credentials::world_labs_cookies::WorldLabsCookies; +use crate::error::world_labs_client_error::WorldLabsClientError; +use crate::error::world_labs_error::WorldLabsError; +use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError; +use chrono::Utc; +use http_headers::names::{PRIORITY, SEC_FETCH_DEST, SEC_FETCH_MODE, SEC_FETCH_SITE, SEC_GPC}; +use http_headers::values::accept::ACCEPT_ALL; +use http_headers::values::cache_control::CACHE_CONTROL_NO_CACHE; +use http_headers::values::connection::CONNECTION_KEEP_ALIVE; +use http_headers::values::content_type::CONTENT_TYPE_APPLICATION_JSON; +use http_headers::values::pragma::PRAGMA_NO_CACHE; +use http_headers::values::priority::PRIORITY_4; +use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE}; +use http_headers::values::te::TE_TRAILERS; +use log::error; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use uuid::Uuid; +use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_LENGTH, CONTENT_TYPE, ORIGIN, PRAGMA, REFERER, TE}; +use wreq::Client; +use wreq_util::Emulation; + +const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects"; + +fn get_url(object_id: &ObjectId) -> String { + format!("{}/{}:upload", BASE_URL, object_id.0) +} + +pub struct BeginObjectImageUploadArgs<'a> { + pub cookies: &'a WorldLabsCookies, + pub bearer_token: &'a WorldLabsBearerToken, + pub upload_mime_type: UploadMimeType, + pub object_id: &'a ObjectId, + pub request_timeout: Option, +} + +pub struct BeginObjectImageUploadResponse { + pub upload_url: String, +} + +/// Marble Image-to-World +/// This request follows object attachment and signals upload. It returns the upload URL. +/// Request #3 (of ~10) +pub async fn begin_object_image_upload(args: BeginObjectImageUploadArgs<'_>) -> Result { + let client = Client::builder() + .emulation(Emulation::Firefox143) + .build() + .map_err(|err| WorldLabsClientError::WreqClientError(err))?; + + let url = get_url(args.object_id); + + let mut request_builder = client.post(url) + .header(ACCEPT, ACCEPT_ALL) + .header(ACCEPT_LANGUAGE, "en-US,en;q=0.5") + .header(ACCEPT_ENCODING, "gzip, deflate, br, zstd") + .header(REFERER, REFERER_VALUE) + .header(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON) + .header(AUTHORIZATION, args.bearer_token.to_bearer_token_string()) + .header(ORIGIN, ORIGIN_VALUE) + .header(SEC_GPC, "1") + .header(CONNECTION, CONNECTION_KEEP_ALIVE) + .header(SEC_FETCH_DEST, SEC_FETCH_DEST_EMPTY) + .header(SEC_FETCH_MODE, SEC_FETCH_MODE_CORS) + .header(SEC_FETCH_SITE, SEC_FETCH_SITE_CROSS_SITE) + .header(PRIORITY, PRIORITY_4) + .header(PRAGMA, PRAGMA_NO_CACHE) + .header(CACHE_CONTROL, CACHE_CONTROL_NO_CACHE) + .header(CONTENT_LENGTH, 0) + .header(TE, TE_TRAILERS); + + if let Some(timeout) = args.request_timeout { + request_builder = request_builder.timeout(timeout); + } + + let http_request = request_builder + .build() + .map_err(|err| { + error!("Error building request: {:?}", err); + WorldLabsClientError::WreqClientError(err) + })?; + + let response = client.execute(http_request) + .await + .map_err(|err| { + error!("Error during request execution: {:?}", err); + WorldLabsGenericApiError::WreqError(err) + })?; + + let status = response.status(); + + let response_body = response.text() + .await + .map_err(|err| { + error!("Error reading response body: {:?}", err); + WorldLabsGenericApiError::WreqError(err) + })?; + + // TODO: Handle errors (Cloudflare, Grok, etc.) + if !status.is_success() { + error!("Request returned an error (code {}) : {:?}", status.as_u16(), response_body); + //return Err(classify_general_http_status_code_and_body(status, response_body)); + } + + let response : RawResponse = serde_json::from_str(&response_body) + .map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?; + + Ok(BeginObjectImageUploadResponse { + upload_url: response.upload_url, + }) +} + +#[derive(Deserialize)] +struct RawResponse { + /// eg. "https://storage.googleapis.com/wlt-training-gsc-marble-assets-prod/temp/objects/{...}", + pub upload_url: String, +} + +#[cfg(test)] +mod tests { + use crate::api::api_types::object_id::ObjectId; + use crate::api::requests::objects::begin_object_image_upload::get_url; + + #[test] + fn test_get_url() { + let object_id = ObjectId("foo-bar-baz-bin".to_string()); + let expected = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects/foo-bar-baz-bin:upload"; + assert_eq!(get_url(&object_id), expected); + } +} diff --git a/crates/api_clients/world_labs_client/src/api/requests/objects/mod.rs b/crates/api_clients/world_labs_client/src/api/requests/objects/mod.rs index 054487fad6..9532225d29 100644 --- a/crates/api_clients/world_labs_client/src/api/requests/objects/mod.rs +++ b/crates/api_clients/world_labs_client/src/api/requests/objects/mod.rs @@ -1,2 +1,3 @@ pub mod create_object; -mod begin_object_image_attachment; \ No newline at end of file +pub mod begin_object_image_attachment; +pub mod begin_object_image_upload; \ No newline at end of file