nichlink-core 0.1.3

Registry, transaction, contracts, grafts, and diagnostics for NichLink
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
//! The persisted `graft.plan` document.
//! 持久化的 `graft.plan` 文档。
//!
//! A plan file records one external overlay declaration: which logical slot a
//! host hands over, which external implementation is meant to take it, and
//! whether the whole subtree or only the node is replaced. It is a declaration,
//! not an application: the runtime record resolver
//! ([`Registry::overlay_recorded`](crate::Registry::overlay_recorded)) is what
//! turns it into an overlay cut, and this document never touches the base tree
//! or the external tree.
//! 计划文件记录一条外部覆盖声明:宿主交出哪个逻辑槽位、打算由哪个外部实现接管、
//! 是整棵子树替换还是只替换节点。它是声明而不是应用:把它变成覆盖切口的是运行期记录
//! 解析器([`Registry::overlay_recorded`](crate::Registry::overlay_recorded)),本文档
//! 永不改动原树或外部树。
//!
//! The format is versioned so a reader can refuse a document it does not
//! understand instead of guessing at it.
//! 格式带版本,读取方因此可以拒绝读不懂的文档,而不是猜。

use std::fmt;

use crate::registry_core::identity::NodeId;

/// The only `graft.plan` layout this build understands.
/// 本版本唯一能读懂的 `graft.plan` 版式。
pub const GRAFT_PLAN_VERSION: u32 = 1;

/// One external overlay declaration as written to `graft.plan`.
/// 写进 `graft.plan` 的一条外部覆盖声明。
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraftPlanDocument {
    /// Layout version; must equal `GRAFT_PLAN_VERSION`.
    /// 版式版本;必须等于 `GRAFT_PLAN_VERSION`。
    pub version: u32,
    /// Compile-time identity of the target face in the base tree.
    /// 原树中目标注册面的编译期身份。
    pub target: NodeId,
    /// The target's logical path, for humans and for string-selector overlays.
    /// 目标的逻辑路径,供人阅读,也供字符串选择器的 overlay 使用。
    pub target_path: String,
    /// Name, path, or identity of the external implementation's face.
    /// 外部实现注册面的名称、路径或身份。
    pub graft: String,
    /// Whether the whole subtree rooted at the target is replaced.
    /// 是否替换目标根节点下的整棵子树。
    pub full: bool,
}

impl GraftPlanDocument {
    /// The declaration a freshly created plan carries.
    /// 新建计划携带的声明。
    pub fn new(
        target: NodeId,
        target_path: impl Into<String>,
        graft: impl Into<String>,
        full: bool,
    ) -> Self {
        Self {
            version: GRAFT_PLAN_VERSION,
            target,
            target_path: target_path.into(),
            graft: graft.into(),
            full,
        }
    }

    /// Parse a plan document, refusing anything this version cannot read.
    /// 解析计划文档;读不懂的内容一律拒绝,而不是猜。
    pub fn parse(source: &str) -> Result<Self, GraftPlanDocumentError> {
        let mut version = None;
        let mut target = None;
        let mut target_path = None;
        let mut graft = None;
        let mut full = None;
        let mut seen: Vec<&str> = Vec::new();
        for (index, raw) in source.lines().enumerate() {
            let line = raw.trim();
            if line.is_empty() || line.starts_with('#') {
                continue;
            }
            let (key, value) =
                line.split_once('=')
                    .ok_or_else(|| GraftPlanDocumentError::Malformed {
                        line: index + 1,
                        message: "expected `key=value`".to_owned(),
                    })?;
            let key = key.trim();
            let value = value.trim();
            // Every other reader in this workspace refuses a repeated field
            // (`from_jsonl` and the face parser both do), and a plan is written by
            // a program: a second value silently winning would make the file mean
            // something its text does not say.
            // 本工作区其他每个读取者都拒绝重复字段(`from_jsonl` 与注册面解析器都是),而计划
            // 是程序写出来的:让第二个取值静默获胜,会让文件表达出它的文本没有说的意思。
            if seen.contains(&key) {
                return Err(GraftPlanDocumentError::Malformed {
                    line: index + 1,
                    message: format!("duplicate key `{key}`"),
                });
            }
            seen.push(key);
            match key {
                "version" => {
                    let parsed =
                        value
                            .parse::<u32>()
                            .map_err(|_| GraftPlanDocumentError::Malformed {
                                line: index + 1,
                                message: format!("version `{value}` is not a number"),
                            })?;
                    if parsed != GRAFT_PLAN_VERSION {
                        return Err(GraftPlanDocumentError::UnsupportedVersion(parsed));
                    }
                    version = Some(parsed);
                }
                "target" => {
                    let parsed =
                        value
                            .parse::<NodeId>()
                            .map_err(|_| GraftPlanDocumentError::Malformed {
                                line: index + 1,
                                message: format!("target `{value}` is not a node identity"),
                            })?;
                    target = Some(parsed);
                }
                "target_path" => target_path = Some(validate_path(value, index + 1)?),
                "graft" => graft = Some(validate_selector(value, index + 1)?),
                "full" => {
                    let parsed = match value {
                        "true" => true,
                        "false" => false,
                        _ => {
                            return Err(GraftPlanDocumentError::Malformed {
                                line: index + 1,
                                message: format!("full `{value}` must be true or false"),
                            });
                        }
                    };
                    full = Some(parsed);
                }
                other => return Err(GraftPlanDocumentError::UnknownKey(other.to_owned())),
            }
        }
        Ok(Self {
            version: version.ok_or(GraftPlanDocumentError::MissingKey("version"))?,
            target: target.ok_or(GraftPlanDocumentError::MissingKey("target"))?,
            target_path: target_path.ok_or(GraftPlanDocumentError::MissingKey("target_path"))?,
            graft: graft.ok_or(GraftPlanDocumentError::MissingKey("graft"))?,
            full: full.ok_or(GraftPlanDocumentError::MissingKey("full"))?,
        })
    }

    /// Render the canonical document, one `key=value` per line.
    /// 渲染规范文档,每行一个 `key=value`。
    pub fn render(&self) -> String {
        format!(
            "version={}\ntarget={}\ntarget_path={}\ngraft={}\nfull={}\n",
            self.version,
            self.target,
            self.target_path,
            self.graft,
            if self.full { "true" } else { "false" }
        )
    }

    /// The `cut … graft …` clause a host entry writes inside
    /// `static_graft_plan!`.
    /// 宿主入口写在 `static_graft_plan!` 里的 `cut … graft …` 子句。
    ///
    /// The string form needs no linked external implementation, which is why it
    /// is the form tooling can generate: the implementation may arrive later as
    /// a plugin, and the compiler is not asked to resolve a crate the author has
    /// not written yet.
    /// 字符串写法不需要链接外部实现,这正是工具能生成它的原因:实现可以稍后作为插件
    /// 到位,也不必要求编译器解析作者还没写的 crate。
    pub fn declaration(&self) -> String {
        format!(
            "cut \"{}\"{} graft \"{}\",",
            self.target_path,
            if self.full { " full" } else { "" },
            self.graft
        )
    }
}

fn validate_path(value: &str, line: usize) -> Result<String, GraftPlanDocumentError> {
    if value.is_empty() || value.split('/').any(str::is_empty) || value.contains(['\\', '"']) {
        return Err(GraftPlanDocumentError::Malformed {
            line,
            message: format!("target_path `{value}` is not a `/`-separated logical path"),
        });
    }
    Ok(value.to_owned())
}

/// Check one external implementation selector against the plan format.
/// 按计划格式校验一个外部实现选择器。
///
/// One rule, shared by the file writer and the parser, so a plan this crate
/// writes is always a plan this crate can read back.
/// 写入方与解析方共用同一条规则,因此本 crate 写出的计划永远读得回来。
pub fn validate_graft_selector(selector: &str) -> Result<(), GraftPlanDocumentError> {
    if selector.trim().is_empty() {
        return Err(GraftPlanDocumentError::InvalidSelector(
            "external graft name must be a non-empty selector".to_owned(),
        ));
    }
    if selector.contains(['/', '\\']) {
        return Err(GraftPlanDocumentError::InvalidSelector(
            "external graft name must not contain path separators".to_owned(),
        ));
    }
    if selector.chars().any(char::is_whitespace) || selector.contains('"') {
        return Err(GraftPlanDocumentError::InvalidSelector(
            "external graft name must be one word without quotes".to_owned(),
        ));
    }
    // A selector names one directory inside the record root, so `.` and `..` are
    // not names at all: joining them walked the writer out of the record root
    // (`<pkg>/.nichlink/external-grafts/../graft.plan`), the write reported
    // success, and `list_external_grafts` then never listed it — a record the
    // runtime never applied while the author saw a created plan. A leading dot is
    // refused with them, because a hidden record directory is the same surprise in
    // a quieter form.
    // 选择器命名记录根目录里的**一个**目录,因此 `.` 与 `..` 根本不是名字:join 它们会把写入方
    // 带出记录根(`<pkg>/.nichlink/external-grafts/../graft.plan`),写入还报成功,随后
    // `list_external_grafts` 永远列不出它——一份运行期从不应用、作者却看到"计划已创建"的记录。
    // 以点开头的名字一并拒绝,因为隐藏的记录目录是同一个意外更安静的形式。
    if selector.starts_with('.') {
        return Err(GraftPlanDocumentError::InvalidSelector(
            "external graft name must not start with `.`: it names a directory inside the record root"
                .to_owned(),
        ));
    }
    Ok(())
}

fn validate_selector(value: &str, line: usize) -> Result<String, GraftPlanDocumentError> {
    validate_graft_selector(value).map_err(|message| GraftPlanDocumentError::Malformed {
        line,
        message: format!("graft `{value}` is invalid: {message}"),
    })?;
    Ok(value.to_owned())
}

/// Why a `graft.plan` document was refused.
/// `graft.plan` 文档被拒绝的原因。
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GraftPlanDocumentError {
    /// Document declares a layout this build cannot read.
    /// 文档声明的版式是本构建读不懂的。
    UnsupportedVersion(u32),
    /// The selector names something the plan format cannot store.
    /// 选择器命名了计划格式存不下的东西。
    InvalidSelector(String),
    /// Document omitted a required key.
    /// 文档缺少一个必需的键。
    MissingKey(&'static str),
    /// Document carried a key this format does not define.
    /// 文档带有本格式未定义的键。
    UnknownKey(String),
    /// Document had a line the format cannot read.
    /// 文档中存在本格式读不懂的行。
    Malformed {
        /// 1-based line the failure was found on.
        /// 发现失败的行号,从 1 起。
        line: usize,
        /// What was wrong with that line.
        /// 该行错在哪里。
        message: String,
    },
}

impl fmt::Display for GraftPlanDocumentError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnsupportedVersion(version) => write!(
                formatter,
                "graft plan version `{version}` is not supported (expected {GRAFT_PLAN_VERSION})"
            ),
            Self::InvalidSelector(message) => write!(formatter, "{message}"),
            Self::MissingKey(key) => write!(formatter, "graft plan is missing `{key}`"),
            Self::UnknownKey(key) => write!(formatter, "graft plan has an unknown key `{key}`"),
            Self::Malformed { line, message } => {
                write!(formatter, "graft plan line {line}: {message}")
            }
        }
    }
}

impl std::error::Error for GraftPlanDocumentError {}

impl From<GraftPlanDocumentError> for String {
    fn from(error: GraftPlanDocumentError) -> Self {
        error.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// A repeated key is refused, like every other reader in this workspace
    /// refuses a repeated field: a plan is written by a program, and a second
    /// value silently winning would make the file mean something its text does
    /// not say.
    /// 重复的键会被拒绝,正如本工作区其他每个读取者都拒绝重复字段:计划是程序写出来的,让
    /// 第二个取值静默获胜会让文件表达出它的文本没有说的意思。
    #[test]
    fn a_repeated_key_is_refused() {
        let target = NodeId::from_path("control/object/button/button.rs", "Button");
        let text = format!(
            "version=1\ntarget={target}\ntarget_path=root/control/button\ngraft=first\ngraft=second\n"
        );
        let error = GraftPlanDocument::parse(&text).expect_err("a repeated key must be refused");
        let message = error.to_string();
        assert!(message.contains("duplicate"), "{message}");
        assert!(message.contains("graft"), "{message}");
    }

    /// A selector names one directory, so `.` and `..` — and any name that starts
    /// with a dot — are refused by the same rule the writer and the parser share.
    /// 选择器命名的是一个目录,因此 `.`、`..` 以及任何以点开头的名字都被写入方与解析方共用的
    /// 同一条规则拒绝。
    #[test]
    fn selectors_that_could_leave_the_record_directory_are_refused() {
        for refused in [".", "..", ".hidden"] {
            assert!(
                validate_graft_selector(refused).is_err(),
                "`{refused}` must not name a directory"
            );
        }
        // The control: an ordinary selector still passes, so the rule did not
        // simply refuse everything.
        // 对照:普通选择器仍然通过,因此这条规则不是一律拒绝。
        validate_graft_selector("button_graft").expect("an ordinary selector is valid");
    }

    fn document() -> GraftPlanDocument {
        GraftPlanDocument::new(
            NodeId::from_path("control/object/button/button.rs", "Button"),
            "root/control/button",
            "button_fast",
            false,
        )
    }

    #[test]
    fn a_document_round_trips_through_its_text_form() {
        let document = document();
        let text = document.render();
        assert_eq!(
            text,
            format!(
                "version=1\ntarget={}\ntarget_path=root/control/button\ngraft=button_fast\nfull=false\n",
                document.target
            )
        );
        assert_eq!(GraftPlanDocument::parse(&text).expect("parses"), document);
    }

    #[test]
    fn a_subtree_document_round_trips_and_renders_the_full_clause() {
        let mut document = document();
        document.full = true;
        assert_eq!(
            GraftPlanDocument::parse(&document.render()).expect("parses"),
            document
        );
        assert_eq!(
            document.declaration(),
            "cut \"root/control/button\" full graft \"button_fast\","
        );
        assert!(document.full);
    }

    /// The partial form renders without the `full` clause; the granularity now
    /// lives only in `full`, because the cut builder moved to the record
    /// resolver.
    /// 普通形式渲染时不带 `full` 子句;粒度现在只存在于 `full` 字段,因为切口构造器
    /// 已移入记录解析器。
    #[test]
    fn a_partial_document_omits_the_full_clause() {
        let document = document();
        assert_eq!(
            document.declaration(),
            "cut \"root/control/button\" graft \"button_fast\","
        );
        assert_eq!(document.target_path, "root/control/button");
        assert_eq!(document.graft, "button_fast");
        assert!(!document.full);
    }

    #[test]
    fn key_order_and_comments_do_not_matter() {
        let document = document();
        let reordered = format!(
            "# written by hand\ngraft=button_fast\nfull=false\n\ntarget_path=root/control/button\nversion=1\ntarget={}\n",
            document.target
        );
        assert_eq!(
            GraftPlanDocument::parse(&reordered).expect("parses"),
            document
        );
    }

    #[test]
    fn an_unknown_version_is_refused_instead_of_guessed() {
        let text = document().render().replace("version=1", "version=2");
        assert_eq!(
            GraftPlanDocument::parse(&text),
            Err(GraftPlanDocumentError::UnsupportedVersion(2))
        );
    }

    #[test]
    fn missing_unknown_and_malformed_keys_are_reported() {
        let missing = document().render().replace("full=false\n", "");
        assert_eq!(
            GraftPlanDocument::parse(&missing),
            Err(GraftPlanDocumentError::MissingKey("full"))
        );
        let unknown = format!("{}editor=vscode\n", document().render());
        assert_eq!(
            GraftPlanDocument::parse(&unknown),
            Err(GraftPlanDocumentError::UnknownKey("editor".to_owned()))
        );
        let malformed = document().render().replace("full=false", "full=yes");
        assert!(matches!(
            GraftPlanDocument::parse(&malformed),
            Err(GraftPlanDocumentError::Malformed { line: 5, .. })
        ));
        let selector = document()
            .render()
            .replace("graft=button_fast", "graft=a/b");
        assert!(matches!(
            GraftPlanDocument::parse(&selector),
            Err(GraftPlanDocumentError::Malformed { line: 4, .. })
        ));
        let path = document().render().replace(
            "target_path=root/control/button",
            "target_path=control//button",
        );
        assert!(matches!(
            GraftPlanDocument::parse(&path),
            Err(GraftPlanDocumentError::Malformed { line: 3, .. })
        ));
    }
}