153 lines
5.1 KiB
Rust
153 lines
5.1 KiB
Rust
use time::OffsetDateTime;
|
||
#[cfg(not(target_arch = "wasm32"))]
|
||
use uuid::Uuid;
|
||
|
||
/// 统一做必填字符串归一化,避免各模块散落重复的 `trim().to_string()`。
|
||
pub fn normalize_required_string(value: impl AsRef<str>) -> Option<String> {
|
||
let normalized = value.as_ref().trim();
|
||
if normalized.is_empty() {
|
||
return None;
|
||
}
|
||
|
||
Some(normalized.to_string())
|
||
}
|
||
|
||
/// 统一做可选字符串归一化,空白字符串一律视为 `None`。
|
||
pub fn normalize_optional_string(value: Option<String>) -> Option<String> {
|
||
value.and_then(normalize_required_string)
|
||
}
|
||
|
||
/// 统一做字符串列表归一化,逐项裁剪并丢弃空白项。
|
||
pub fn normalize_string_list(values: Vec<String>) -> Vec<String> {
|
||
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<String, String> {
|
||
value
|
||
.format(&time::format_description::well_known::Rfc3339)
|
||
.map_err(|error| error.to_string())
|
||
}
|
||
|
||
/// 统一解析 RFC3339 字符串,供模块自行补充更贴近业务的错误上下文。
|
||
pub fn parse_rfc3339(value: &str) -> Result<OffsetDateTime, String> {
|
||
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());
|
||
}
|
||
}
|