use time::OffsetDateTime; #[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; /// 统一做必填字符串归一化,避免各模块散落重复的 `trim().to_string()`。 pub fn normalize_required_string(value: impl AsRef) -> Option { let normalized = value.as_ref().trim(); if normalized.is_empty() { return None; } Some(normalized.to_string()) } /// 统一做可选字符串归一化,空白字符串一律视为 `None`。 pub fn normalize_optional_string(value: Option) -> Option { value.and_then(normalize_required_string) } /// 统一做字符串列表归一化,逐项裁剪并丢弃空白项。 pub fn normalize_string_list(values: Vec) -> Vec { values .into_iter() .filter_map(|value| normalize_required_string(value)) .collect() } /// 统一生成“前缀 + 十六进制微秒种子”的稳定 ID,适合业务对象主键。 pub fn build_prefixed_seed_id(prefix: &str, seed_micros: i64) -> String { format!("{prefix}{seed_micros:x}") } /// 统一生成“前缀 + UUID simple”随机 ID,适合会话态或一次性票据主键。 #[cfg(not(target_arch = "wasm32"))] pub fn build_prefixed_uuid_id(prefix: &str) -> String { format!("{prefix}{}", Uuid::new_v4().simple()) } /// SpacetimeDB 的 wasm32 模块不应走浏览器/本地随机 UUID 生成。 #[cfg(target_arch = "wasm32")] pub fn build_prefixed_uuid_id(_prefix: &str) -> String { panic!( "shared-kernel::build_prefixed_uuid_id 不支持 wasm32,请改用显式 ID 或 SpacetimeDB 上下文生成能力" ) } /// 统一生成 UUID simple 字符串,供 token、随机种子等轻量场景复用。 #[cfg(not(target_arch = "wasm32"))] pub fn new_uuid_simple_string() -> String { Uuid::new_v4().simple().to_string() } /// SpacetimeDB 的 wasm32 模块不应走浏览器/本地随机 UUID 生成。 #[cfg(target_arch = "wasm32")] pub fn new_uuid_simple_string() -> String { panic!( "shared-kernel::new_uuid_simple_string 不支持 wasm32,请改用显式 ID 或 SpacetimeDB 上下文生成能力" ) } /// 统一格式化微秒时间戳,当前阶段固定为 `seconds.microsZ` 文本口径。 pub fn format_timestamp_micros(micros: i64) -> String { let seconds = micros.div_euclid(1_000_000); let subsec_micros = micros.rem_euclid(1_000_000); format!("{seconds}.{subsec_micros:06}Z") } /// 统一把 `OffsetDateTime` 转成 Unix 微秒时间戳,避免各模块重复手写纳秒除法。 pub fn offset_datetime_to_unix_micros(value: OffsetDateTime) -> i64 { (value.unix_timestamp_nanos() / 1_000) as i64 } /// 统一格式化 RFC3339 字符串,避免每个模块自己拼格式化错误文案。 pub fn format_rfc3339(value: OffsetDateTime) -> Result { value .format(&time::format_description::well_known::Rfc3339) .map_err(|error| error.to_string()) } /// 统一解析 RFC3339 字符串,供模块自行补充更贴近业务的错误上下文。 pub fn parse_rfc3339(value: &str) -> Result { OffsetDateTime::parse(value, &time::format_description::well_known::Rfc3339) .map_err(|error| error.to_string()) } #[cfg(test)] mod tests { use super::*; #[test] fn normalize_required_string_trims_and_filters_blank() { assert_eq!( normalize_required_string(" hero_001 "), Some("hero_001".to_string()) ); assert_eq!(normalize_required_string(" "), None); } #[test] fn normalize_optional_string_filters_blank() { assert_eq!( normalize_optional_string(Some(" profile_001 ".to_string())), Some("profile_001".to_string()) ); assert_eq!(normalize_optional_string(Some(" ".to_string())), None); assert_eq!(normalize_optional_string(None), None); } #[test] fn normalize_string_list_trims_and_filters_blank() { assert_eq!( normalize_string_list(vec![ " alpha ".to_string(), "".to_string(), " ".to_string(), "beta".to_string() ]), vec!["alpha".to_string(), "beta".to_string()] ); } #[test] fn build_prefixed_seed_id_uses_hex_seed() { assert_eq!(build_prefixed_seed_id("assetobj_", 255), "assetobj_ff"); } #[test] fn format_timestamp_micros_is_stable() { assert_eq!( format_timestamp_micros(1_713_686_401_234_567), "1713686401.234567Z" ); } #[test] fn offset_datetime_to_unix_micros_is_stable() { let value = OffsetDateTime::UNIX_EPOCH + time::Duration::seconds(1_713_686_401) + time::Duration::microseconds(234_567); assert_eq!(offset_datetime_to_unix_micros(value), 1_713_686_401_234_567); } #[test] fn format_and_parse_rfc3339_round_trip() { let now = OffsetDateTime::UNIX_EPOCH + time::Duration::seconds(1_713_686_400); let text = format_rfc3339(now).expect("rfc3339 should format"); let parsed = parse_rfc3339(&text).expect("rfc3339 should parse"); assert_eq!(parsed.unix_timestamp(), now.unix_timestamp()); } }