From 558455275a194af29a090e58c11c8a0b72ca52b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Nikolaus?= Date: Tue, 1 Sep 2026 11:18:39 +0200 Subject: [PATCH] feat(config): default the schema version when omitted `version` is now optional in phora.toml and phora.local.toml; 1 is the only schema version and serves as the default. Explicit `version = 1` keeps parsing unchanged. The README self-contained-fence invariant selected blocks by their leading `version = 1` line; it now selects any fence declaring both a [sources.] and a [targets.] table, which is what "self-contained" actually means. --- README.md | 2 +- phora.example.toml | 2 +- src/config/mod.rs | 8 ++++++++ src/config/tests.rs | 8 ++++++++ tests/doc_invariants.rs | 9 +++++++-- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9942756..b5cdadb 100644 --- a/README.md +++ b/README.md @@ -399,7 +399,7 @@ Phora reads `phora.toml` from the working directory, optionally overlaid by [`phora.example.toml`](phora.example.toml) for a complete example. ```toml -version = 1 +# version = 1 # optional; 1 is the only schema version and the default # protocol = "ssh" # global default for forge sources (default https) # [defaults] diff --git a/phora.example.toml b/phora.example.toml index 9b861bc..6142f5e 100644 --- a/phora.example.toml +++ b/phora.example.toml @@ -1,4 +1,4 @@ -version = 1 +# version = 1 # optional; 1 is the only schema version and the default # protocol selects https vs ssh for host-aliased sources; default https. # Uncomment to flip the global default (still overridable per source): diff --git a/src/config/mod.rs b/src/config/mod.rs index ccbd5b5..4ef9472 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -57,9 +57,17 @@ impl Defaults { } } +/// The only schema version phora speaks; omitting `version` selects it. +pub const SCHEMA_VERSION: u32 = 1; + +const fn default_schema_version() -> u32 { + SCHEMA_VERSION +} + #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] pub struct Config { + #[serde(default = "default_schema_version")] pub version: u32, #[serde(default)] pub protocol: Option, diff --git a/src/config/tests.rs b/src/config/tests.rs index 827dacb..7e7e81f 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -323,6 +323,14 @@ fn parses_version_and_all_sections_from_example() { assert_eq!(cfg.targets.len(), 3); } +#[test] +fn omitted_version_defaults_to_the_schema_version() { + let cfg = Config::parse("[sources.dots]\ngit = \"https://example.com/dots.git\"\n") + .expect("a config without a version key should parse"); + assert_eq!(cfg.version, crate::config::SCHEMA_VERSION); + assert_eq!(cfg.sources.len(), 1); +} + #[test] fn parses_source_fields_from_example() { let cfg = Config::parse(EXAMPLE_TOML).expect("example toml should parse"); diff --git a/tests/doc_invariants.rs b/tests/doc_invariants.rs index 4b37316..cd00367 100644 --- a/tests/doc_invariants.rs +++ b/tests/doc_invariants.rs @@ -153,11 +153,16 @@ fn local_example_toml_drops_legacy_binding_forms() { fn readme_self_contained_fences_parse_and_validate() { let complete: Vec = fenced_blocks(README) .into_iter() - .filter(|block| block.trim_start().starts_with("version = 1")) + .filter(|block| { + let lines: Vec<&str> = block.lines().collect(); + lines.iter().any(|line| line.starts_with("[sources.")) + && lines.iter().any(|line| line.starts_with("[targets.")) + }) .collect(); assert!( !complete.is_empty(), - "README.md: expected at least one self-contained `version = 1` config fence" + "README.md: expected at least one self-contained config fence declaring both \ + a [sources.] and a [targets.] table" ); for block in &complete { let config = Config::parse(block).unwrap_or_else(|err| {