diff --git a/server-rs/crates/platform-tripo/src/common/client.rs b/server-rs/crates/platform-tripo/src/common/client.rs index 5940541c0..8e756d537 100644 --- a/server-rs/crates/platform-tripo/src/common/client.rs +++ b/server-rs/crates/platform-tripo/src/common/client.rs @@ -1,8 +1,8 @@ use tripo3d_sdk::TripoClient; use super::{ - TripoDownloadedModel, TripoError, TripoModelUrl, TripoSettings, TripoTaskHandle, - TripoTaskSnapshot, map_task, validate_task_id, + TripoDownloadedModel, TripoError, TripoSettings, TripoTaskHandle, TripoTaskSnapshot, TripoUrl, + map_task, validate_task_id, }; pub struct TripoProviderClient { @@ -49,7 +49,7 @@ impl TripoProviderClient { task_id: handle.task_id.clone(), message: "completed task has no model URL".into(), })?; - let url = TripoModelUrl::parse(&downloaded.url).map_err(|error| match error { + let url = TripoUrl::parse(&downloaded.url).map_err(|error| match error { TripoError::OutputSchema { message, .. } => TripoError::OutputSchema { task_id: handle.task_id.clone(), message, diff --git a/server-rs/crates/platform-tripo/src/common/mapping.rs b/server-rs/crates/platform-tripo/src/common/mapping.rs index 529233af8..fd4254990 100644 --- a/server-rs/crates/platform-tripo/src/common/mapping.rs +++ b/server-rs/crates/platform-tripo/src/common/mapping.rs @@ -2,8 +2,13 @@ use shared_contracts::model3d::common::Model3dTaskStatus; use tripo3d_sdk::{Task, TaskOutput, TaskStatus}; use super::{ - TripoError, TripoModelUrl, TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, - TripoTaskSnapshot, TripoTaskType, + TripoError, TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, TripoTaskSnapshot, + TripoTaskType, TripoUrl, +}; +use crate::{ + image_to_model::result::TripoImageToModelResult, + multiview_to_model::result::TripoMultiviewToModelResult, + text_to_model::result::TripoTextToModelResult, }; pub(crate) fn map_task(task: Task) -> Result { @@ -13,6 +18,7 @@ pub(crate) fn map_task(task: Task) -> Result { let output = match status { Model3dTaskStatus::Completed => Some(map_output( &task_id, + &task_type, task.output .as_ref() .ok_or_else(|| TripoError::OutputSchema { @@ -66,52 +72,51 @@ fn map_status(status: TaskStatus) -> Result { } } -fn map_output(task_id: &str, output: &TaskOutput) -> Result { - let mut model_urls = Vec::new(); - for candidate in output - .model_urls - .iter() - .flat_map(|urls| urls.iter()) - .chain(output.model_url.iter()) - .chain(output.model.iter()) - .chain(output.pbr_model.iter()) - .chain(output.base_model.iter()) - { - if !model_urls - .iter() - .any(|url: &TripoModelUrl| url.as_str() == candidate) - { - model_urls.push(parse_url(candidate, task_id)?); - } - } - if model_urls.is_empty() { - return Err(TripoError::OutputSchema { - task_id: task_id.into(), - message: "completed task output contains no model URL".into(), - }); - } +fn map_output( + task_id: &str, + task_type: &TripoTaskType, + output: &TaskOutput, +) -> Result { + let model_url = required_url(task_id, "model_url", output.model_url.as_deref())?; + let rendered_image_url = required_url( + task_id, + "rendered_image_url", + output.rendered_image_url.as_deref(), + )?; - let rendered_image_url = output - .rendered_image_url - .as_ref() - .or(output.rendered_image.as_ref()) - .map(|value| parse_url(value, task_id)) - .transpose()?; - let generated_image_url = output - .generated_image_url - .as_ref() - .map(|value| parse_url(value, task_id)) - .transpose()?; - - Ok(TripoTaskOutput { - model_urls, - rendered_image_url, - generated_image_url, - }) + match task_type { + TripoTaskType::TextToModel => Ok(TripoTaskOutput::TextToModel(TripoTextToModelResult { + model_url, + rendered_image_url, + generated_image_url: required_url( + task_id, + "generated_image_url", + output.generated_image_url.as_deref(), + )?, + })), + TripoTaskType::ImageToModel => Ok(TripoTaskOutput::ImageToModel(TripoImageToModelResult { + model_url, + rendered_image_url, + })), + TripoTaskType::MultiviewToModel => Ok(TripoTaskOutput::MultiviewToModel( + TripoMultiviewToModelResult { + model_url, + rendered_image_url, + }, + )), + } } -fn parse_url(value: &str, task_id: &str) -> Result { - TripoModelUrl::parse(value).map_err(|error| match error { +fn required_url(task_id: &str, field: &str, value: Option<&str>) -> Result { + let value = value.ok_or_else(|| TripoError::OutputSchema { + task_id: task_id.into(), + message: format!("completed task output is missing required {field}"), + })?; + parse_url(value, task_id) +} + +fn parse_url(value: &str, task_id: &str) -> Result { + TripoUrl::parse(value).map_err(|error| match error { TripoError::OutputSchema { message, .. } => TripoError::OutputSchema { task_id: task_id.into(), message, diff --git a/server-rs/crates/platform-tripo/src/common/mod.rs b/server-rs/crates/platform-tripo/src/common/mod.rs index e1396d64c..810bd6da4 100644 --- a/server-rs/crates/platform-tripo/src/common/mod.rs +++ b/server-rs/crates/platform-tripo/src/common/mod.rs @@ -10,8 +10,8 @@ pub use config::TripoSettings; pub use error::{TripoError, TripoField, TripoValidationReason}; pub(crate) use mapping::map_task; pub use types::{ - TripoDownloadedModel, TripoModelUrl, TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, - TripoTaskSnapshot, TripoTaskType, + TripoDownloadedModel, TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, TripoTaskSnapshot, + TripoTaskType, TripoUrl, }; pub(crate) use validation::{ TripoGenerationOptions, validate_generation_options, validate_task_id, diff --git a/server-rs/crates/platform-tripo/src/common/types.rs b/server-rs/crates/platform-tripo/src/common/types.rs index 7a2f09241..527d7b02d 100644 --- a/server-rs/crates/platform-tripo/src/common/types.rs +++ b/server-rs/crates/platform-tripo/src/common/types.rs @@ -4,7 +4,11 @@ use url::Url; use shared_contracts::model3d::common::Model3dTaskStatus; -use crate::common::TripoError; +use crate::{ + common::TripoError, image_to_model::result::TripoImageToModelResult, + multiview_to_model::result::TripoMultiviewToModelResult, + text_to_model::result::TripoTextToModelResult, +}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct TripoTaskHandle { @@ -19,18 +23,18 @@ pub enum TripoTaskType { } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct TripoModelUrl(Url); +pub struct TripoUrl(Url); -impl TripoModelUrl { +impl TripoUrl { pub(crate) fn parse(value: &str) -> Result { let url = Url::parse(value).map_err(|error| TripoError::OutputSchema { task_id: "unknown".into(), - message: format!("model URL is invalid: {error}"), + message: format!("URL is invalid: {error}"), })?; if !matches!(url.scheme(), "http" | "https") { return Err(TripoError::OutputSchema { task_id: "unknown".into(), - message: "model URL must use http or https".into(), + message: "URL must use http or https".into(), }); } Ok(Self(url)) @@ -41,7 +45,7 @@ impl TripoModelUrl { } } -impl fmt::Display for TripoModelUrl { +impl fmt::Display for TripoUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } @@ -54,12 +58,10 @@ pub struct TripoTaskFailure { } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct TripoTaskOutput { - pub model_urls: Vec, - pub rendered_image_url: Option, - pub generated_image_url: Option, - // TODO(tripo-docs): add fixed per-task output fields after the provider - // contract is supplied and verified against real task responses. +pub enum TripoTaskOutput { + TextToModel(TripoTextToModelResult), + ImageToModel(TripoImageToModelResult), + MultiviewToModel(TripoMultiviewToModelResult), } #[derive(Clone, Debug, Eq, PartialEq)] @@ -76,7 +78,7 @@ pub struct TripoTaskSnapshot { #[derive(Clone, Debug, Eq, PartialEq)] pub struct TripoDownloadedModel { - pub url: TripoModelUrl, + pub url: TripoUrl, pub content_type: Option, pub data: Vec, } diff --git a/server-rs/crates/platform-tripo/src/image_to_model/mod.rs b/server-rs/crates/platform-tripo/src/image_to_model/mod.rs index b79c47fca..61ca8d331 100644 --- a/server-rs/crates/platform-tripo/src/image_to_model/mod.rs +++ b/server-rs/crates/platform-tripo/src/image_to_model/mod.rs @@ -1 +1,2 @@ mod client; +pub mod result; diff --git a/server-rs/crates/platform-tripo/src/image_to_model/result.rs b/server-rs/crates/platform-tripo/src/image_to_model/result.rs new file mode 100644 index 000000000..0283a6e41 --- /dev/null +++ b/server-rs/crates/platform-tripo/src/image_to_model/result.rs @@ -0,0 +1,7 @@ +use crate::common::TripoUrl; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TripoImageToModelResult { + pub model_url: TripoUrl, + pub rendered_image_url: TripoUrl, +} diff --git a/server-rs/crates/platform-tripo/src/lib.rs b/server-rs/crates/platform-tripo/src/lib.rs index 156754e59..1bf20cfea 100644 --- a/server-rs/crates/platform-tripo/src/lib.rs +++ b/server-rs/crates/platform-tripo/src/lib.rs @@ -4,12 +4,12 @@ //! DTOs and `TripoError`, never `tripo3d_sdk` types or its poller API. mod common; -mod image_to_model; -mod multiview_to_model; -mod text_to_model; +pub mod image_to_model; +pub mod multiview_to_model; +pub mod text_to_model; pub use common::{ - TripoDownloadedModel, TripoError, TripoField, TripoModelUrl, TripoProviderClient, - TripoSettings, TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, TripoTaskSnapshot, - TripoTaskType, TripoValidationReason, + TripoDownloadedModel, TripoError, TripoField, TripoProviderClient, TripoSettings, + TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, TripoTaskSnapshot, TripoTaskType, TripoUrl, + TripoValidationReason, }; diff --git a/server-rs/crates/platform-tripo/src/multiview_to_model/mod.rs b/server-rs/crates/platform-tripo/src/multiview_to_model/mod.rs index b79c47fca..61ca8d331 100644 --- a/server-rs/crates/platform-tripo/src/multiview_to_model/mod.rs +++ b/server-rs/crates/platform-tripo/src/multiview_to_model/mod.rs @@ -1 +1,2 @@ mod client; +pub mod result; diff --git a/server-rs/crates/platform-tripo/src/multiview_to_model/result.rs b/server-rs/crates/platform-tripo/src/multiview_to_model/result.rs new file mode 100644 index 000000000..257ba8ac8 --- /dev/null +++ b/server-rs/crates/platform-tripo/src/multiview_to_model/result.rs @@ -0,0 +1,7 @@ +use crate::common::TripoUrl; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TripoMultiviewToModelResult { + pub model_url: TripoUrl, + pub rendered_image_url: TripoUrl, +} diff --git a/server-rs/crates/platform-tripo/src/text_to_model/mod.rs b/server-rs/crates/platform-tripo/src/text_to_model/mod.rs index 66a22e439..8899f51c1 100644 --- a/server-rs/crates/platform-tripo/src/text_to_model/mod.rs +++ b/server-rs/crates/platform-tripo/src/text_to_model/mod.rs @@ -1,2 +1,3 @@ mod client; +pub mod result; mod validation; diff --git a/server-rs/crates/platform-tripo/src/text_to_model/result.rs b/server-rs/crates/platform-tripo/src/text_to_model/result.rs new file mode 100644 index 000000000..5b4f5a2bf --- /dev/null +++ b/server-rs/crates/platform-tripo/src/text_to_model/result.rs @@ -0,0 +1,8 @@ +use crate::common::TripoUrl; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TripoTextToModelResult { + pub model_url: TripoUrl, + pub rendered_image_url: TripoUrl, + pub generated_image_url: TripoUrl, +}