init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
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());
}
}