refactor(core): M126 加载器稳定 id 词表收敛到 game::LoaderKind::as_str
M122 把「id → 加载器」的解析收到 core::game::loader::InstanceLoader::parse,但 「种类 → 对外稳定 id」的命名词表还散在多处,其中 install 侧那一份只靠一句「不要改名」 的注释约束,MARKERS 更是第四张无守卫字面量表。 - core::game::LoaderKind 新增 as_str(**全仓唯一一份 id 字面量**)、has_marker (穷尽 match:新种类必须表态认不认 id 标记)、parse(as_str 的反查)与 ALL(清单, 顺序即标记优先级);MARKERS 的标记串改为取自 as_str,不再是独立字面量。 - addon::loader::LoaderKind::as_str 改成委派(结构映射,无字面量):安装侧拼出的 <mc>-<kind>-<loader> 与解析侧从此同源。 - presenter::dto::instances::LoaderKind 的 as_str/parse 委派给 core(to_core/from_core 是结构映射),删掉第四份词表;CLI 删掉 loader_kind_id 翻译层,直接调 core。 - 测试:core 新增 markers_cover_every_kind_that_has_one(MARKERS↔ALL↔has_marker↔as_str 逐格对齐 + 按标记串 round-trip + neoforge 先于 forge);presenter 新增 loader_kind_ids_are_the_released_contract(七个 id 逐字钉住 + 反查 + 别名隔离)。 - 对外输出未变:真二进制对同一组 7 类实例的 `instances list --json` 输出逐字节相同 (md5 0341752658ca28e020ba48c31a51a124),人读输出同样逐字节相同。 file 行数:core/game/loader.rs 337→399(≤400 硬约束,文档按密度重排、事实未丢)。
This commit is contained in:
1 parent
84f85bbc79
commit
be37c17962
4 files changed
+253
-134
No files matched your search
@@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use unlucky_core::game::LoaderKind as CoreLoaderKind;
|
||||
|
||||
/// 实例标识:core::game 生成的稳定 id,app 视为不透明字符串。
|
||||
pub type InstanceId = String;
|
||||
|
||||
@@ -40,31 +42,58 @@ impl LoaderKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// 稳定 id(`vanilla` / `fabric` / …;GUI 与 CLI 的 `--loader` 同词表)。
|
||||
pub fn as_str(self) -> &'static str {
|
||||
/// 本 DTO 种类 → core 的宽种类。
|
||||
///
|
||||
/// **结构映射,一个 id 字面量都没有**:词表只有一个真相源
|
||||
/// ([`CoreLoaderKind::as_str`]),本 crate 写不进第二份。加一支 DTO 种类这里编不过。
|
||||
const fn to_core(self) -> CoreLoaderKind {
|
||||
match self {
|
||||
Self::Vanilla => "vanilla",
|
||||
Self::Fabric => "fabric",
|
||||
Self::Quilt => "quilt",
|
||||
Self::Forge => "forge",
|
||||
Self::NeoForge => "neoforge",
|
||||
Self::OptiFine => "optifine",
|
||||
Self::LiteLoader => "liteloader",
|
||||
Self::Vanilla => CoreLoaderKind::Vanilla,
|
||||
Self::Fabric => CoreLoaderKind::Fabric,
|
||||
Self::Quilt => CoreLoaderKind::Quilt,
|
||||
Self::Forge => CoreLoaderKind::Forge,
|
||||
Self::NeoForge => CoreLoaderKind::NeoForge,
|
||||
Self::OptiFine => CoreLoaderKind::OptiFine,
|
||||
Self::LiteLoader => CoreLoaderKind::LiteLoader,
|
||||
}
|
||||
}
|
||||
|
||||
/// core 的宽种类 → 本 DTO 种类([`Self::to_core`] 的反向,同样不含字面量)。
|
||||
///
|
||||
/// core 给 `game::LoaderKind` 加一支、而 DTO 还没跟上时,这里编不过——这正是要的:
|
||||
/// UI 不许「新加载器默默显示成原版」。
|
||||
const fn from_core(kind: CoreLoaderKind) -> Self {
|
||||
match kind {
|
||||
CoreLoaderKind::Vanilla => Self::Vanilla,
|
||||
CoreLoaderKind::Fabric => Self::Fabric,
|
||||
CoreLoaderKind::Quilt => Self::Quilt,
|
||||
CoreLoaderKind::Forge => Self::Forge,
|
||||
CoreLoaderKind::NeoForge => Self::NeoForge,
|
||||
CoreLoaderKind::OptiFine => Self::OptiFine,
|
||||
CoreLoaderKind::LiteLoader => Self::LiteLoader,
|
||||
}
|
||||
}
|
||||
|
||||
/// 稳定 id(`vanilla` / `fabric` / …;GUI 与 `--json` 同词表)。
|
||||
///
|
||||
/// **委派**给 [`CoreLoaderKind::as_str`](M126):整张词表在 core 只有一份字面量
|
||||
/// (0.2.0 已发布契约),这里只剩一次结构映射。
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
self.to_core().as_str()
|
||||
}
|
||||
|
||||
/// 从稳定 id 解析(未知给 `None`,由调用方决定报错文案)。
|
||||
///
|
||||
/// 七个 id 的匹配委派给 [`CoreLoaderKind::parse`];本方法只额外收 `none` / 空串这两个
|
||||
/// **GUI 输入别名**(表单里「不装加载器」的写法)——它们不是对外输出的 id,也绝不参与
|
||||
/// [`Self::as_str`]。
|
||||
pub fn parse(raw: &str) -> Option<Self> {
|
||||
Some(match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"vanilla" | "none" | "" => Self::Vanilla,
|
||||
"fabric" => Self::Fabric,
|
||||
"quilt" => Self::Quilt,
|
||||
"forge" => Self::Forge,
|
||||
"neoforge" => Self::NeoForge,
|
||||
"optifine" => Self::OptiFine,
|
||||
"liteloader" => Self::LiteLoader,
|
||||
_ => return None,
|
||||
})
|
||||
let wanted = raw.trim();
|
||||
if wanted.is_empty() || wanted.eq_ignore_ascii_case("none") {
|
||||
return Some(Self::Vanilla);
|
||||
}
|
||||
Some(Self::from_core(CoreLoaderKind::parse(wanted)?))
|
||||
}
|
||||
|
||||
/// 向导里的可选加载器(顺序即展示顺序)。
|
||||
@@ -252,6 +281,43 @@ mod tests {
|
||||
assert_eq!(LoaderKind::parse("unknown"), None);
|
||||
}
|
||||
|
||||
/// DTO 的 `as_str` / `parse` 是 core 词表的**委派**(M126),而词表本身是**对外契约**:
|
||||
/// 七个 id 在这里独立抄一遍(0.2.0 已发布形状,改名等于违约)。
|
||||
///
|
||||
/// 抓得住:core 的 `as_str` 或委派接线改错(`OptiFine` 映到 `LiteLoader`、映射漏了哪支);
|
||||
/// 词表改字/改大小写(`neoforge` → `neo_forge`);`parse` 收进了不该有的别名(`neo-forge`)。
|
||||
/// 抓不住:core 的 `ALL` / `MARKERS` 之间对不对得上(那是 core 的
|
||||
/// `markers_cover_every_kind_that_has_one` 的职责),以及真二进制输出面上的 JSON
|
||||
/// (那是 CLI 的 `json_rows_expose_loader_kind_and_version`)。
|
||||
#[test]
|
||||
fn loader_kind_ids_are_the_released_contract() {
|
||||
const CONTRACT: [(LoaderKind, &str); 7] = [
|
||||
(LoaderKind::Vanilla, "vanilla"),
|
||||
(LoaderKind::Fabric, "fabric"),
|
||||
(LoaderKind::Quilt, "quilt"),
|
||||
(LoaderKind::Forge, "forge"),
|
||||
(LoaderKind::NeoForge, "neoforge"),
|
||||
(LoaderKind::OptiFine, "optifine"),
|
||||
(LoaderKind::LiteLoader, "liteloader"),
|
||||
];
|
||||
for (kind, id) in CONTRACT {
|
||||
assert_eq!(kind.as_str(), id, "{kind:?} 的稳定 id 变了");
|
||||
assert_eq!(LoaderKind::parse(id), Some(kind), "`{id}` 认不回来");
|
||||
let upper = id.to_ascii_uppercase();
|
||||
assert_eq!(LoaderKind::parse(&upper), Some(kind), "大写 `{upper}`");
|
||||
}
|
||||
// GUI 输入别名(不是对外 id):不装加载器有三种写法;别名与拼法都不许混进词表。
|
||||
for alias in ["none", "", " "] {
|
||||
assert_eq!(
|
||||
LoaderKind::parse(alias),
|
||||
Some(LoaderKind::Vanilla),
|
||||
"`{alias}`"
|
||||
);
|
||||
}
|
||||
assert_eq!(LoaderKind::parse("neo-forge"), None);
|
||||
assert_eq!(LoaderKind::parse("rift"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn progress_ratio_handles_unknown_total() {
|
||||
assert_eq!(Progress { done: 1, total: 0 }.ratio(), 0.0);
|
||||
|
||||
Reference in new issue
Block a user