建立 AGC 资源 kind Rust 枚举与 TypeScript 绑定
新增 GameCreationAppAssetKind 及 Unknown 解析边界 通过 ts-rs 生成 shell 专属 TypeScript 联合类型 补充未知值上下文日志与 Rust/绑定生成测试
This commit is contained in:
+2
@@ -4900,6 +4900,8 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -14,7 +14,7 @@ cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-i
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false }
|
||||
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false, features = ["ts-bindings"] }
|
||||
tauri-build = { version = "2.6.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
@@ -50,7 +50,7 @@ portable-pty = "0.9"
|
||||
percent-encoding = "2"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "native-tls", "stream"] }
|
||||
regex = "1"
|
||||
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false }
|
||||
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false, features = ["ts-bindings"] }
|
||||
tauri = { version = "2.11.2", features = [] }
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] }
|
||||
|
||||
@@ -15,6 +15,7 @@ use platform_llm::{
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared_contracts::game_creation_app::GameCreationAppAssetKind;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use ts_rs::TS;
|
||||
@@ -645,5 +646,30 @@ mod tests {
|
||||
Node::export_all(&config).expect("Node TypeScript export succeeds");
|
||||
TextComponent::export_all(&config).expect("TextComponent TypeScript export succeeds");
|
||||
FontSource::export_all(&config).expect("FontSource TypeScript export succeeds");
|
||||
|
||||
let game_creation_config = Config::new().with_out_dir(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../src/contracts/generated/"
|
||||
));
|
||||
GameCreationAppAssetKind::export_all(&game_creation_config)
|
||||
.expect("GameCreationAppAssetKind TypeScript export succeeds");
|
||||
let generated = std::fs::read_to_string(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../src/contracts/generated/GameCreationAppAssetKind.ts"
|
||||
))
|
||||
.expect("generated GameCreationAppAssetKind binding exists");
|
||||
for value in [
|
||||
"\"image\"",
|
||||
"\"audio\"",
|
||||
"\"sound-effect\"",
|
||||
"\"background-music\"",
|
||||
"\"font\"",
|
||||
"\"unknown\"",
|
||||
] {
|
||||
assert!(
|
||||
generated.contains(value),
|
||||
"generated binding misses {value}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* GameCreationApp manifest 资源 kind 的唯一 Rust 类型。
|
||||
*
|
||||
* JSON 使用 kebab-case;`Unknown` 只表示解析边界遇到尚未登记的输入,
|
||||
* 正常资源写入路径不应主动选择它。
|
||||
*/
|
||||
export type GameCreationAppAssetKind =
|
||||
| 'image'
|
||||
| 'scene'
|
||||
| 'character'
|
||||
| 'character-animation'
|
||||
| 'icon'
|
||||
| 'icon-spritesheet'
|
||||
| 'icon-spec'
|
||||
| 'ui-design'
|
||||
| 'ui-design-doc'
|
||||
| 'publication-material'
|
||||
| 'spec'
|
||||
| 'video'
|
||||
| 'audio'
|
||||
| 'sound-effect'
|
||||
| 'background-music'
|
||||
| 'font'
|
||||
| 'document'
|
||||
| 'code'
|
||||
| 'unknown';
|
||||
@@ -3,7 +3,7 @@
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Milestone | `docs/project-memory/plans/【里程碑】AGC资源kind枚举化Rust绑定与内部收紧-2026-09-15.md` |
|
||||
| Status | ready |
|
||||
| Status | in-progress |
|
||||
| Owner | Codex |
|
||||
|
||||
## 修改边界
|
||||
@@ -29,6 +29,7 @@
|
||||
2. 为 enum 增加可选 ts-rs derive,默认 shared-contracts 构建不引入 ts-rs。
|
||||
3. 让 shell 的 shared-contracts 依赖打开绑定 feature,在 shell 专属 contracts/generated 目录生成 TS。
|
||||
4. 增加 Rust serde round-trip 与 Unknown 解析/序列化测试;增加 TS 生成文件存在和值集合测试。
|
||||
未知值通过 `parse_with_context` 收口到 `Unknown`,并在启用 shell 的 `kind-observability` feature 时记录原始值与解析上下文。
|
||||
5. 运行 shared-contracts 与 shell 定向测试;通过后暂停,等待下一个内部字段收紧切片。
|
||||
|
||||
## 验证命令
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Version | 1.0 |
|
||||
| Status | planned |
|
||||
| Status | in-progress |
|
||||
| Date | 2026-09-15 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` |
|
||||
|
||||
|
||||
Generated
+57
-33
@@ -52,17 +52,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
@@ -182,7 +171,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -193,7 +182,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1133,7 +1122,7 @@ dependencies = [
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"socket2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1148,7 +1137,7 @@ dependencies = [
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1451,7 +1440,7 @@ version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457481173e6db5ca9fa2be93a58df8f4c7be639587aeb4853b526c6cf87db4e6"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
"egui",
|
||||
@@ -1486,7 +1475,7 @@ version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"bitflags 2.13.0",
|
||||
"emath",
|
||||
"epaint",
|
||||
@@ -1503,7 +1492,7 @@ version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e4d209971c84b2352a06174abdba701af1e552ce56b144d96f2bd50a3c91236"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
"egui",
|
||||
@@ -1598,7 +1587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "009d0dd3c2163823a0abdb899451ecbc78798dec545ee91b43aff1fa790bab62"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
"ecolor",
|
||||
"emath",
|
||||
@@ -1628,7 +1617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2062,9 +2051,6 @@ name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
@@ -2094,6 +2080,11 @@ name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -3084,7 +3075,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3739,7 +3730,7 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527735ac204efb9fa3884bfd9224d016c5735fabe1d394ebed145b40e7545b99"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"async-trait",
|
||||
"blake2",
|
||||
"bstr",
|
||||
@@ -3776,7 +3767,7 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a7ffe2f5acf9f94fd255cfd1438866bc9124f8f0c7d42562bd3f853df2094b7"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"async-trait",
|
||||
"brotli",
|
||||
"bstr",
|
||||
@@ -3914,7 +3905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6705a26ad89d241a989a5395641931ba37076f5ab5fbd19ee92402414a43af32"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown 0.17.1",
|
||||
"parking_lot",
|
||||
"rand 0.8.6",
|
||||
]
|
||||
@@ -4856,7 +4847,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5247,6 +5238,8 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5420,7 +5413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5529,7 +5522,7 @@ version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c4e9d07e21ad665c45736871e5005d8457ab3d2cdd7806e351aa5e1c8c2ac32"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
"hashbrown 0.16.1",
|
||||
@@ -5815,7 +5808,16 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6284,6 +6286,28 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "12.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8"
|
||||
dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "12.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -6901,7 +6925,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7134,7 +7158,7 @@ version = "0.30.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"ahash",
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.13.0",
|
||||
|
||||
@@ -8,8 +8,12 @@ license.workspace = true
|
||||
# 默认给 api-server 等原生后端暴露资产上传 DTO;SpacetimeDB WASM 路径通过 workspace 依赖关闭默认 feature。
|
||||
default = ["oss-contracts"]
|
||||
oss-contracts = []
|
||||
ts-bindings = ["dep:ts-rs", "kind-observability"]
|
||||
kind-observability = ["dep:tracing"]
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
ts-rs = { version = "12.0.1", optional = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
mod asset_kind;
|
||||
pub use asset_kind::GameCreationAppAssetKind;
|
||||
|
||||
pub const GAME_CREATION_APP_MANIFEST_SCHEMA_VERSION: &str = "game-creation-app.manifest.v1";
|
||||
pub const GAME_CREATION_AGENT_RUN_SCHEMA_VERSION: &str = "game-creator-agent-run.v1";
|
||||
pub const GAME_CREATION_AGENT_RUN_MAX_PASSES: u8 = 3;
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// GameCreationApp manifest 资源 kind 的唯一 Rust 类型。
|
||||
///
|
||||
/// JSON 使用 kebab-case;`Unknown` 只表示解析边界遇到尚未登记的输入,
|
||||
/// 正常资源写入路径不应主动选择它。
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-bindings", ts(export_to = "GameCreationAppAssetKind.ts"))]
|
||||
pub enum GameCreationAppAssetKind {
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "image"))]
|
||||
Image,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "scene"))]
|
||||
Scene,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "character"))]
|
||||
Character,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "character-animation"))]
|
||||
CharacterAnimation,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "icon"))]
|
||||
Icon,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "icon-spritesheet"))]
|
||||
IconSpritesheet,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "icon-spec"))]
|
||||
IconSpec,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "ui-design"))]
|
||||
UiDesign,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "ui-design-doc"))]
|
||||
UiDesignDoc,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "publication-material"))]
|
||||
PublicationMaterial,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "spec"))]
|
||||
Spec,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "video"))]
|
||||
Video,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "audio"))]
|
||||
Audio,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "sound-effect"))]
|
||||
SoundEffect,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "background-music"))]
|
||||
BackgroundMusic,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "font"))]
|
||||
Font,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "document"))]
|
||||
Document,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "code"))]
|
||||
Code,
|
||||
#[cfg_attr(feature = "ts-bindings", ts(rename = "unknown"))]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl GameCreationAppAssetKind {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Image => "image",
|
||||
Self::Scene => "scene",
|
||||
Self::Character => "character",
|
||||
Self::CharacterAnimation => "character-animation",
|
||||
Self::Icon => "icon",
|
||||
Self::IconSpritesheet => "icon-spritesheet",
|
||||
Self::IconSpec => "icon-spec",
|
||||
Self::UiDesign => "ui-design",
|
||||
Self::UiDesignDoc => "ui-design-doc",
|
||||
Self::PublicationMaterial => "publication-material",
|
||||
Self::Spec => "spec",
|
||||
Self::Video => "video",
|
||||
Self::Audio => "audio",
|
||||
Self::SoundEffect => "sound-effect",
|
||||
Self::BackgroundMusic => "background-music",
|
||||
Self::Font => "font",
|
||||
Self::Document => "document",
|
||||
Self::Code => "code",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str_lossy(value: &str) -> Self {
|
||||
match value {
|
||||
"image" => Self::Image,
|
||||
"scene" => Self::Scene,
|
||||
"character" => Self::Character,
|
||||
"character-animation" => Self::CharacterAnimation,
|
||||
"icon" => Self::Icon,
|
||||
"icon-spritesheet" => Self::IconSpritesheet,
|
||||
"icon-spec" => Self::IconSpec,
|
||||
"ui-design" => Self::UiDesign,
|
||||
"ui-design-doc" => Self::UiDesignDoc,
|
||||
"publication-material" => Self::PublicationMaterial,
|
||||
"spec" => Self::Spec,
|
||||
"video" => Self::Video,
|
||||
"audio" => Self::Audio,
|
||||
"sound-effect" => Self::SoundEffect,
|
||||
"background-music" => Self::BackgroundMusic,
|
||||
"font" => Self::Font,
|
||||
"document" => Self::Document,
|
||||
"code" => Self::Code,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析外部 manifest/API 字符串;未知值收口到 `Unknown` 并留下可检索的原始值与上下文。
|
||||
pub fn parse_with_context(value: &str, context: &'static str) -> Self {
|
||||
let parsed = Self::from_str_lossy(value);
|
||||
if parsed == Self::Unknown && value.trim() != Self::Unknown.as_str() {
|
||||
#[cfg(feature = "kind-observability")]
|
||||
tracing::warn!(
|
||||
raw_kind = value,
|
||||
source_context = context,
|
||||
"未知的 GameCreationApp 资源 kind,已收口为 unknown"
|
||||
);
|
||||
}
|
||||
parsed
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GameCreationAppAssetKind {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from_str_lossy(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GameCreationAppAssetKind {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for GameCreationAppAssetKind {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GameCreationAppAssetKind {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct AssetKindVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for AssetKindVisitor {
|
||||
type Value = GameCreationAppAssetKind;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a GameCreationApp asset kind string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(GameCreationAppAssetKind::parse_with_context(value, "serde"))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(AssetKindVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GameCreationAppAssetKind;
|
||||
|
||||
#[test]
|
||||
fn serde_round_trip_uses_kebab_case() {
|
||||
let values = [
|
||||
(GameCreationAppAssetKind::Image, "image"),
|
||||
(
|
||||
GameCreationAppAssetKind::CharacterAnimation,
|
||||
"character-animation",
|
||||
),
|
||||
(GameCreationAppAssetKind::UiDesignDoc, "ui-design-doc"),
|
||||
(GameCreationAppAssetKind::SoundEffect, "sound-effect"),
|
||||
(
|
||||
GameCreationAppAssetKind::BackgroundMusic,
|
||||
"background-music",
|
||||
),
|
||||
(GameCreationAppAssetKind::Font, "font"),
|
||||
(GameCreationAppAssetKind::Unknown, "unknown"),
|
||||
];
|
||||
|
||||
for (value, encoded) in values {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&value).unwrap(),
|
||||
format!("\"{encoded}\"")
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<GameCreationAppAssetKind>(&format!("\"{encoded}\""))
|
||||
.unwrap(),
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_wire_values_are_closed_over_unknown() {
|
||||
for raw in ["asset", "ui", "game-background", "future-kind"] {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<GameCreationAppAssetKind>(&format!("\"{raw}\"")).unwrap(),
|
||||
GameCreationAppAssetKind::Unknown
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user