//! 剧情写入命令与输入归一化。 //! //! 命令层只处理 story session scoped 的输入结构、字段裁剪和基础校验,不读取数据库, //! 也不兼容旧 `/api/runtime/story/*` 总入口。 use crate::errors::StorySessionFieldError; use serde::{Deserialize, Serialize}; use shared_kernel::{ normalize_optional_string as normalize_shared_optional_string, normalize_required_string, }; #[cfg(feature = "spacetime-types")] use spacetimedb::SpacetimeType; #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct StorySessionInput { pub story_session_id: String, pub runtime_session_id: String, pub actor_user_id: String, pub world_profile_id: String, pub initial_prompt: String, pub opening_summary: Option, pub created_at_micros: i64, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct StoryContinueInput { pub story_session_id: String, pub event_id: String, pub narrative_text: String, pub choice_function_id: Option, pub updated_at_micros: i64, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct StorySessionStateInput { pub story_session_id: String, } pub fn build_story_session_input( story_session_id: String, runtime_session_id: String, actor_user_id: String, world_profile_id: String, initial_prompt: String, opening_summary: Option, created_at_micros: i64, ) -> Result { let input = StorySessionInput { story_session_id: normalize_required_string(story_session_id).unwrap_or_default(), runtime_session_id: normalize_required_string(runtime_session_id).unwrap_or_default(), actor_user_id: normalize_required_string(actor_user_id).unwrap_or_default(), world_profile_id: normalize_required_string(world_profile_id).unwrap_or_default(), initial_prompt: normalize_required_string(initial_prompt).unwrap_or_default(), opening_summary: normalize_optional_value(opening_summary), created_at_micros, }; validate_story_session_input(&input)?; Ok(input) } pub fn build_story_session_state_input( story_session_id: String, ) -> Result { let input = StorySessionStateInput { story_session_id: normalize_required_string(story_session_id).unwrap_or_default(), }; validate_story_session_state_input(&input)?; Ok(input) } pub fn build_story_continue_input( story_session_id: String, event_id: String, narrative_text: String, choice_function_id: Option, updated_at_micros: i64, ) -> Result { let input = StoryContinueInput { story_session_id: normalize_required_string(story_session_id).unwrap_or_default(), event_id: normalize_required_string(event_id).unwrap_or_default(), narrative_text: normalize_required_string(narrative_text).unwrap_or_default(), choice_function_id: normalize_optional_value(choice_function_id), updated_at_micros, }; validate_story_continue_input(&input)?; Ok(input) } pub fn validate_story_session_input( input: &StorySessionInput, ) -> Result<(), StorySessionFieldError> { if normalize_required_string(&input.story_session_id).is_none() { return Err(StorySessionFieldError::MissingSessionId); } if normalize_required_string(&input.runtime_session_id).is_none() { return Err(StorySessionFieldError::MissingRuntimeSessionId); } if normalize_required_string(&input.actor_user_id).is_none() { return Err(StorySessionFieldError::MissingActorUserId); } if normalize_required_string(&input.world_profile_id).is_none() { return Err(StorySessionFieldError::MissingWorldProfileId); } if normalize_required_string(&input.initial_prompt).is_none() { return Err(StorySessionFieldError::MissingInitialPrompt); } Ok(()) } pub fn validate_story_session_state_input( input: &StorySessionStateInput, ) -> Result<(), StorySessionFieldError> { if normalize_required_string(&input.story_session_id).is_none() { return Err(StorySessionFieldError::MissingSessionId); } Ok(()) } pub fn validate_story_continue_input( input: &StoryContinueInput, ) -> Result<(), StorySessionFieldError> { if normalize_required_string(&input.story_session_id).is_none() { return Err(StorySessionFieldError::MissingSessionId); } if normalize_required_string(&input.event_id).is_none() { return Err(StorySessionFieldError::MissingEventId); } if normalize_required_string(&input.narrative_text).is_none() { return Err(StorySessionFieldError::MissingNarrativeText); } Ok(()) } pub fn normalize_optional_value(value: Option) -> Option { normalize_shared_optional_string(value) }