use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CreationAudioGenerationKind { BackgroundMusic, SoundEffect, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct CreationAudioAsset { pub task_id: String, pub provider: String, #[serde(default)] pub asset_object_id: Option, #[serde(default)] pub asset_kind: Option, pub audio_src: String, #[serde(default)] pub prompt: Option, #[serde(default)] pub title: Option, #[serde(default)] pub updated_at: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct CreateBackgroundMusicRequest { pub prompt: String, pub title: String, #[serde(default)] pub tags: Option, #[serde(default)] pub model: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct CreateSoundEffectRequest { pub prompt: String, #[serde(default)] pub duration: Option, #[serde(default)] pub seed: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AudioGenerationTaskResponse { pub kind: CreationAudioGenerationKind, pub task_id: String, pub provider: String, pub status: String, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CreationAudioStoragePrefix { PuzzleAssets, #[serde(rename = "match3d_assets")] Match3DAssets, CustomWorldScenes, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct PublishGeneratedAudioAssetRequest { pub entity_kind: String, pub entity_id: String, pub slot: String, pub asset_kind: String, #[serde(default)] pub profile_id: Option, #[serde(default)] pub storage_prefix: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct GeneratedAudioAssetResponse { pub kind: CreationAudioGenerationKind, pub task_id: String, pub provider: String, pub status: String, #[serde(default)] pub asset_object_id: Option, #[serde(default)] pub asset_kind: Option, #[serde(default)] pub audio_src: Option, } #[cfg(test)] mod tests { use super::*; use serde_json::json; #[test] fn creation_audio_contracts_use_camel_case_fields() { let request = PublishGeneratedAudioAssetRequest { entity_kind: "match3d_item".to_string(), entity_id: "match3d-item-1".to_string(), slot: "click_sound".to_string(), asset_kind: "match3d_click_sound".to_string(), profile_id: Some("profile-1".to_string()), storage_prefix: Some(CreationAudioStoragePrefix::Match3DAssets), }; let payload = serde_json::to_value(request).expect("request should serialize"); assert_eq!(payload["entityKind"], json!("match3d_item")); assert_eq!(payload["storagePrefix"], json!("match3d_assets")); let asset = CreationAudioAsset { task_id: "task-1".to_string(), provider: "vector-engine-suno".to_string(), asset_object_id: Some("assetobj_1".to_string()), asset_kind: Some("puzzle_background_music".to_string()), audio_src: "/generated-puzzle-assets/a.mp3".to_string(), prompt: Some("轻快音乐".to_string()), title: Some("拼图音乐".to_string()), updated_at: Some("2026-05-11T00:00:00Z".to_string()), }; let payload = serde_json::to_value(asset).expect("asset should serialize"); assert_eq!(payload["taskId"], json!("task-1")); assert_eq!(payload["audioSrc"], json!("/generated-puzzle-assets/a.mp3")); } }