use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct StartMatch3DRunRequest { pub profile_id: String, #[serde(default)] pub item_type_count_override: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ClickMatch3DItemRequest { #[serde(default)] pub run_id: Option, pub item_instance_id: String, pub client_snapshot_version: u64, pub client_event_id: String, pub clicked_at_ms: u64, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct StopMatch3DRunRequest { pub client_action_id: String, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Match3DItemSnapshotResponse { pub item_instance_id: String, pub item_type_id: String, pub visual_key: String, pub x: f32, pub y: f32, pub radius: f32, pub layer: u32, pub state: String, pub clickable: bool, #[serde(default)] pub tray_slot_index: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Match3DTraySlotResponse { pub slot_index: u32, #[serde(default)] pub item_instance_id: Option, #[serde(default)] pub item_type_id: Option, #[serde(default)] pub visual_key: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Match3DRunSnapshotResponse { pub run_id: String, pub profile_id: String, pub owner_user_id: String, pub status: String, /// 对外 HTTP 快照版本。领域层内部字段名为 board_version,facade 需要在这里完成映射。 pub snapshot_version: u64, pub started_at_ms: u64, pub duration_limit_ms: u64, #[serde(default)] pub server_now_ms: Option, pub remaining_ms: u64, pub clear_count: u32, pub total_item_count: u32, pub cleared_item_count: u32, pub items: Vec, pub tray_slots: Vec, #[serde(default)] pub failure_reason: Option, #[serde(default)] pub last_confirmed_action_id: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Match3DClickConfirmationResponse { pub accepted: bool, #[serde(default)] pub reject_reason: Option, #[serde(default)] pub entered_slot_index: Option, pub cleared_item_instance_ids: Vec, pub run: Match3DRunSnapshotResponse, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Match3DRunResponse { pub run: Match3DRunSnapshotResponse, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Match3DClickResponse { pub confirmation: Match3DClickConfirmationResponse, } #[cfg(test)] mod tests { use super::*; use serde_json::json; #[test] fn click_match3d_item_request_uses_camel_case() { let payload = serde_json::to_value(ClickMatch3DItemRequest { run_id: Some("run-1".to_string()), item_instance_id: "item-1".to_string(), client_snapshot_version: 7, client_event_id: "event-1".to_string(), clicked_at_ms: 12_345, }) .expect("payload should serialize"); assert_eq!(payload["runId"], json!("run-1")); assert_eq!(payload["itemInstanceId"], json!("item-1")); assert_eq!(payload["clientSnapshotVersion"], json!(7)); assert_eq!(payload["clientEventId"], json!("event-1")); assert_eq!(payload["clickedAtMs"], json!(12_345)); } }