149 lines
5.0 KiB
Rust
149 lines
5.0 KiB
Rust
//! 剧情写入命令与输入归一化。
|
|
//!
|
|
//! 命令层只处理 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<String>,
|
|
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<String>,
|
|
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<String>,
|
|
created_at_micros: i64,
|
|
) -> Result<StorySessionInput, StorySessionFieldError> {
|
|
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<StorySessionStateInput, StorySessionFieldError> {
|
|
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<String>,
|
|
updated_at_micros: i64,
|
|
) -> Result<StoryContinueInput, StorySessionFieldError> {
|
|
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<String>) -> Option<String> {
|
|
normalize_shared_optional_string(value)
|
|
}
|