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
use super::sample_url::{DocsSampleBaseUrl, InvalidSampleBaseUrl};
use crate::core::config::warning_ack::WarningAcknowledgement;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SnippetConfig {
pub output: String,
#[serde(default)]
pub languages: Vec<String>,
#[serde(default)]
pub capabilities: SnippetCapabilities,
/// Public base URL the generated *documentation* snippets bind for a fixture's
/// `mock_url` / `mock_url_list` arguments, e.g. `"https://samples.example.org"`.
///
/// Documentation-only: the executable e2e suite keeps binding the per-fixture mock
/// server, so configuring this can never send a generated test to the network. Unset
/// falls back to `https://example.com`, the reserved documentation domain, and the
/// snippet run reports every fixture whose published snippet carries it.
#[serde(default)]
pub sample_base_url: Option<String>,
/// Glob patterns naming hand-authored snippet files that are curated on purpose, rather
/// than generated by alef.
///
/// Patterns are relative to the PROJECT ROOT -- the directory holding `alef.toml`, the
/// same base [`Self::output`] itself is written in -- and not relative to `output`.
/// Hand-authored snippets characteristically sit beside the generated tree rather than
/// inside it (`docs/snippets/cli/*.md` next to `output = "docs/snippets/generated"`), so
/// an `output`-relative pattern could not name them at all.
///
/// Coverage tracks fixture/language cells alef itself renders; a curated file has no
/// fixture behind it at all (a Docker recipe, an API-server walkthrough, anything alef's
/// current backends cannot express). Before this field existed, alef had no way to say
/// so: every curated path either sat outside coverage entirely (invisible) or was
/// misreported by a migration comparison as a gap alef "should" close. Declaring a path
/// here resolves it into `SnippetGenerationReport::curated_paths` as CURATED -- distinct
/// from both `generated` and `missing` -- and into
/// `migration::MigrationEntry::curated` for the migration-comparison path, so a report
/// can state "N curated, M generated" instead of leaving "all snippets are generated" an
/// unverifiable claim.
///
/// Mirrors `docs.coverage_exceptions`, which retires one fixture/language coverage
/// CELL with a reason; this retires a PATH that never had a cell to begin with, because
/// no fixture stands behind it. Every declared pattern must match at least one real file
/// under the project root at generation time -- a pattern matching zero files fails the run
/// rather than silently marking nothing as curated, which would recreate the exact
/// "coverage reports curated files as missing" gap this field exists to close. A pattern
/// must never match a path alef itself generates.
#[serde(default)]
pub curated_snippets: Vec<String>,
/// Narrow, per-fixture/target acknowledgements for the reserved-placeholder-domain warning
/// (task #540). Each entry silences exactly one fixture id publishing the placeholder for
/// exactly one target language -- never every fixture, never every language. An entry that
/// stops matching (because the fixture no longer publishes the placeholder for that target)
/// fails the run rather than lingering as a no-op; see
/// `crate::core::warning_ack::AcknowledgementLedger`. Configuring `sample_base_url` above
/// remains the actual fix; this exists for fixtures whose sample input is genuinely
/// reserved/non-routable documentation content on purpose.
#[serde(default)]
pub acknowledged_warnings: Vec<WarningAcknowledgement>,
}
impl SnippetConfig {
/// Resolve [`Self::sample_base_url`] into the address documentation snippets bind,
/// falling back to the reserved-domain placeholder when the project configures none.
pub fn docs_sample_base_url(&self) -> Result<DocsSampleBaseUrl<'_>, InvalidSampleBaseUrl> {
DocsSampleBaseUrl::resolve(self.sample_base_url.as_deref())
}
pub fn languages_or<'a>(&'a self, fallback: &'a [String]) -> &'a [String] {
if self.languages.is_empty() {
fallback
} else {
&self.languages
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct SnippetCapabilities {
#[serde(default)]
pub all: BTreeSet<String>,
#[serde(flatten)]
pub languages: BTreeMap<String, BTreeSet<String>>,
}
impl SnippetCapabilities {
pub fn for_language(&self, language: &str) -> BTreeSet<String> {
let mut values = self.all.clone();
if let Some(language_values) = self.languages.get(language) {
values.extend(language_values.iter().cloned());
}
values
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_snippet_languages_override_e2e_targets() {
let fallback = vec!["python".to_string(), "java".to_string()];
let mut config = SnippetConfig {
output: "docs/snippets-generated".into(),
..SnippetConfig::default()
};
assert_eq!(config.languages_or(&fallback), fallback);
config.languages = vec!["python".into()];
assert_eq!(config.languages_or(&fallback), ["python"]);
}
/// Regression test for the misplaced-key defect: a consumer that writes
/// `[e2e].fields_optional` / `fields_array` / `fields_enum` / `result_fields` /
/// `fields_method_calls` one level too deep, under `[crates.e2e.snippets]`,
/// used to have all five keys silently discarded by serde because
/// `SnippetConfig` had no `deny_unknown_fields`. `FieldResolver::is_optional()`
/// (and friends) then returned `false`/empty unconditionally for every field,
/// in every fixture, in every language — with no error anywhere.
///
/// Without `#[serde(deny_unknown_fields)]` on `SnippetConfig` this test fails:
/// `toml::from_str` returns `Ok(..)` and the misplaced keys vanish silently.
#[test]
fn an_unconfigured_snippet_config_reports_a_placeholder_sample_base_url() {
let config = SnippetConfig::default();
let resolved = config.docs_sample_base_url().expect("no configuration resolves");
assert_eq!(resolved.base(), "https://example.com");
assert!(resolved.is_placeholder());
}
#[test]
fn a_configured_sample_base_url_reaches_the_docs_path() {
let config: SnippetConfig = toml::from_str(
r#"
output = "docs/snippets-generated"
sample_base_url = "https://samples.example.org/"
"#,
)
.expect("sample_base_url is an accepted key");
let resolved = config.docs_sample_base_url().expect("a valid base resolves");
assert_eq!(resolved.base(), "https://samples.example.org");
assert!(!resolved.is_placeholder());
}
#[test]
fn misplaced_e2e_fields_under_snippets_table_is_rejected_not_silently_dropped() {
// Carry exactly one misplaced key. toml reports only the first unknown
// field it reaches, so a fixture with several would pin the assertion to
// whichever the deserializer happens to visit first.
let toml_str = r#"
output = "docs/snippets-generated"
fields_optional = ["metadata", "content"]
"#;
let err = toml::from_str::<SnippetConfig>(toml_str).expect_err(
"a SnippetConfig table carrying e2e-level field-classification keys must be \
rejected, not silently accepted with those keys discarded",
);
let message = err.to_string();
assert!(
message.contains("fields_optional"),
"error must name the offending key so the misplacement is discoverable: {message}"
);
assert!(
message.contains("unknown field"),
"error must be serde's unknown-field diagnostic, not an unrelated parse failure: {message}"
);
}
/// Companion happy-path check: a `SnippetConfig` table containing only its
/// real fields (`output`, `languages`, `capabilities`) still deserializes
/// cleanly under `deny_unknown_fields` — the fix must not reject legitimate
/// configs.
#[test]
fn well_formed_snippet_config_still_deserializes_under_deny_unknown_fields() {
let toml_str = r#"
output = "docs/snippets-generated"
languages = ["python", "java"]
[capabilities]
all = ["run"]
python = ["compile"]
"#;
let config: SnippetConfig = toml::from_str(toml_str).expect("well-formed config must still parse");
assert_eq!(config.output, "docs/snippets-generated");
assert_eq!(config.languages, vec!["python", "java"]);
assert_eq!(config.capabilities.for_language("python"), {
let mut expected = BTreeSet::new();
expected.insert("run".to_string());
expected.insert("compile".to_string());
expected
});
}
}