Skip to content

A2: Extend StructuredConfigVariablesStep to YAML + XML

Placeholder ppxd requested to merge feat/calamari-yaml-xml-config-formats into main

Summary

Format-dispatch refactor of G1.3. Same operator-facing toggle (Squid.Action.JsonConfigVariables.Enabled + Targets glob) now handles .json/.json5, .yaml/.yml, .xml — dispatched per file by extension.

Zero new NuGet dependencies — YamlDotNet 16.3.0 already in csproj; XML via .NET native System.Xml.Linq.

Format dispatch matrix

Extension Format Engine
.json, .json5 JsonConfigFormat Existing JsonPathReplacer (G1.3 + H1) — unchanged
.yaml, .yml YamlConfigFormat YamlDotNet.RepresentationModel.YamlStream
.xml XmlConfigFormat System.Xml.Linq.XDocument
.config (none) XDT territory (G1.2) — deliberately NOT handled here

Shared semantic contract (single source of truth)

All formats route through ConfigVariableLookup.TryFind for variable resolution:

  • Dot-form path (canonical) + colon-form (ASP.NET-Core IConfiguration idiom)
  • Squid.* self-namespace skip on dot form (H1-#1)
  • Colon-form escape hatch (Squid:X:Y) for deliberate operator override

If JSON / YAML / XML ever drift apart on namespace handling, that's a regression — the shared helper makes drift mechanically impossible.

What's in the box

Layer File
Interface + shared result src/Squid.Calamari/Commands/StructuredConfig/IStructuredConfigFormat.cs (new)
Variable lookup (Squid.* guard + dot/colon) src/Squid.Calamari/Commands/StructuredConfig/ConfigVariableLookup.cs (new)
JSON adapter JsonConfigFormat.cs (new)
YAML YamlConfigFormat.cs (new)
XML XmlConfigFormat.cs (new)
Dispatcher StructuredConfigFormatRegistry.cs (new)
Step uses registry StructuredConfigVariablesStep.cs
Tests (37 new) YamlConfigFormatTests.cs (13), XmlConfigFormatTests.cs (16), StructuredConfigFormatRegistryTests.cs (8)

Why wire literal stays JsonConfigVariables

The toggle is still Squid.Action.JsonConfigVariables.Enabled — name is technically misleading (YAML+XML also fire now) but renaming would break the canonical surface shipped in #367 (A1) just three PRs ago. The behavioural change is "files matching Targets glob are dispatched by extension". Operators who set Targets=*.yaml already see YAML support — no UI / wire-literal change needed.

If you want the canonical name to evolve, that's a future PR with the same dual-name pattern A1 used (canonical + legacy).

Test plan

  • Squid.Calamari.Tests: 422/422 (was 374; +48)
  • Squid.UnitTests: 5625/5625 (unchanged)
  • Solution build: 0 errors
  • Pre-merge audit GREEN on 7/7 invariants
  • All G1.3 + H1 JsonPathReplacer tests pass unchanged — back-compat preserved
  • Squid.* guard verified per format (regression catch: format-specific drift would only show up if the guard wasn't shared)
  • .config files NOT handled by XmlConfigFormat — pinned by XmlConfigFormatTests.CanHandle theory
  • xmlns:* declarations not addressable — pinned
  • Staging: real .yaml + .xml deploy with multi-document YAML

Non-breaking guarantee

Surface Status
Existing wire literals Unchanged (canonical + legacy)
Existing .json deploys Byte-identical behaviour (JsonConfigFormat is pure adapter)
.config files Untouched by this step (XDT G1.2 still owns them)
JsonPathReplacer public API Unchanged
Squid.UnitTests cross-project drift detectors All pass
SquidWeb Untouched

Merge request reports

Loading