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
//! #839 gate: every TOML example in the docs parses as the file it says it is.
//!
//! `docs/architecture/overview.md` shipped a production `fraiseql.toml` whose keys sat
//! in `[server]`/`[database]` grouping tables that `ServerConfig` does not have; serde
//! silently discarded every documented key and the example "worked" while configuring
//! nothing. The config structs now deny unknown fields, and this test closes the loop
//! on the docs side.
//!
//! Every toml-fenced block in `README.md` and `docs/` (history excepted) names its file
//! in its first non-empty line, a comment such as `# fraiseql.toml`, and parses as it:
//!
//! * `server.toml` deserializes into the server's [`ServerConfig`];
//! * `fraiseql.toml` deserializes into one of the compiler's two readers of that file:
//! [`TomlSchema`] (the file as `fraiseql compile`'s input, TOML-first authoring) or
//! [`TomlProjectConfig`] (the project config beside a `schema.json`). Two structs for one file
//! name is the dialect split #1641 removes; until then either counts, and both deny unknown keys,
//! so an invented key still fails;
//! * any other `*.toml` (`Cargo.toml`, `pyproject.toml`, ...) is valid TOML.
//!
//! A block that names no file fails: an unnamed example is one a reader cannot check,
//! and the two config dialects (D-9) make "which file is this?" a real question.
//!
//! Wired in `.dagger/main.go`'s test leg next to `config_coverage_manifest_test`,
//! with the full server feature set so every feature-gated section exists. The
//! `#![cfg]` guard below keeps a bare feature-less `cargo test` from false-failing —
//! the dagger line is the gate of record.
#![cfg(all(feature = "auth", feature = "observers", feature = "rest"))]
#![allow(clippy::unwrap_used, clippy::expect_used)] // Reason: test code
#![allow(missing_docs)] // Reason: test code
use std::path::{Path, PathBuf};
use fraiseql_cli::config::{TomlProjectConfig, TomlSchema};
use fraiseql_server::ServerConfig;
/// Repo root, resolved from this crate's manifest dir.
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
}
/// Extract every toml-fenced code block from a markdown file, with its starting line.
fn toml_blocks(markdown: &str) -> Vec<(usize, String)> {
let mut blocks = Vec::new();
let mut current: Option<(usize, Vec<&str>)> = None;
for (idx, line) in markdown.lines().enumerate() {
let trimmed = line.trim_start();
match &mut current {
None if trimmed.starts_with("```toml") => current = Some((idx + 1, Vec::new())),
None => {},
Some(_) if trimmed.starts_with("```") => {
let (start, lines) = current.take().unwrap();
blocks.push((start, lines.join("\n")));
},
Some((_, lines)) => lines.push(line),
}
}
blocks
}
/// The file a block names in its first non-empty line (`# fraiseql.toml — ...`), if any.
fn named_file(block: &str) -> Option<&str> {
let first = block.lines().find(|l| !l.trim().is_empty())?.trim_start();
let rest = first.strip_prefix('#')?.trim_start();
let end = rest.find(".toml")? + ".toml".len();
let name = &rest[..end];
name.chars()
.all(|c| c.is_ascii_alphanumeric() || "._-/".contains(c))
.then_some(name)
}
/// Markdown files under `dir`, recursively, skipping the directories that record history.
fn markdown_files(dir: &Path, out: &mut Vec<PathBuf>) {
const HISTORY: [&str; 3] = ["history", "audits", "migration"];
for entry in std::fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
if !HISTORY.iter().any(|h| path.ends_with(h)) {
markdown_files(&path, out);
}
} else if path.extension().and_then(|e| e.to_str()) == Some("md") {
out.push(path);
}
}
}
#[test]
fn every_documented_toml_example_parses_as_the_file_it_names() {
let root = repo_root();
let mut files = vec![root.join("README.md")];
markdown_files(&root.join("docs"), &mut files);
files.sort();
let (mut server, mut compiler, mut other) = (0usize, 0usize, 0usize);
let mut failures = Vec::new();
for path in &files {
let content = std::fs::read_to_string(path).unwrap();
let shown = path.strip_prefix(&root).unwrap().display();
for (line, block) in toml_blocks(&content) {
let result = match named_file(&block) {
None => Err("names no file: start the block with a comment such as \
`# fraiseql.toml` or `# server.toml`"
.to_string()),
Some(name) if name.ends_with("server.toml") => {
server += 1;
toml::from_str::<ServerConfig>(&block).map(drop).map_err(|e| e.to_string())
},
Some(name) if name.ends_with("fraiseql.toml") => {
compiler += 1;
toml::from_str::<TomlSchema>(&block).map(drop).or_else(|schema| {
toml::from_str::<TomlProjectConfig>(&block).map(drop).map_err(|project| {
format!("as TomlSchema: {schema}; as TomlProjectConfig: {project}")
})
})
},
Some(_) => {
other += 1;
toml::from_str::<toml::Value>(&block).map(drop).map_err(|e| e.to_string())
},
};
if let Err(e) = result {
failures.push(format!("{shown}:{line}: {e}"));
}
}
}
assert!(
failures.is_empty(),
"documented TOML examples that do not parse as the file they name \
(fix the doc or the struct — never let them drift):\n{}",
failures.join("\n")
);
// A silently shrinking corpus is how the gate stops gating: if a floor trips because
// examples were removed, lower it consciously.
assert!(server >= 6, "expected at least 6 server.toml examples, found {server}");
assert!(compiler >= 6, "expected at least 6 fraiseql.toml examples, found {compiler}");
assert!(other >= 1, "expected at least 1 example of another TOML file, found {other}");
}