Omit empty fields from bootstrap config output

probod-bootstrap was writing empty strings and stub blocks such as
`esign: {}` into generated YAML. The post-marshal prune pass caused
part of that by stripping empty leaf strings while leaving empty
parent maps behind.

Drop the prune round-trip in WriteConfig and rely on struct-level
omitzero/omitempty tags plus custom IsZero() helpers on probodconfig.
Only include LLM providers when an API key is set, use a nil map for
extra API headers, and extend the dev-config Makefile recipe with the
local dev defaults already documented in .env.example.

Config loading is unchanged: omitted keys still decode to Go zero
values.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-07-01 10:15:28 +02:00
parent 2be3e18437
commit e22aaa8b67
17 changed files with 380 additions and 195 deletions

View File

@@ -37,28 +37,21 @@ func WriteConfig(cfg *probodconfig.FullConfig, path string, format Format) error
return fmt.Errorf("create directory %s: %w", dir, err)
}
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("marshal config: %w", err)
}
var tree any
if err := yaml.Unmarshal(data, &tree); err != nil {
return fmt.Errorf("unmarshal config: %w", err)
}
pruned := pruneEmptyStrings(tree)
var (
data []byte
err error
)
switch format {
case FormatJSON:
data, err = json.MarshalIndent(pruned, "", " ")
data, err = json.MarshalIndent(cfg, "", " ")
if err != nil {
return fmt.Errorf("marshal pruned config as json: %w", err)
return fmt.Errorf("marshal config as json: %w", err)
}
case FormatYAML:
data, err = yaml.Marshal(pruned)
data, err = yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("marshal pruned config as yaml: %w", err)
return fmt.Errorf("marshal config as yaml: %w", err)
}
default:
return fmt.Errorf("unsupported config format: %q", format)
@@ -70,28 +63,3 @@ func WriteConfig(cfg *probodconfig.FullConfig, path string, format Format) error
return nil
}
func pruneEmptyStrings(value any) any {
switch v := value.(type) {
case map[string]any:
for key, child := range v {
if s, ok := child.(string); ok && s == "" {
delete(v, key)
continue
}
v[key] = pruneEmptyStrings(child)
}
return v
case []any:
for i, child := range v {
v[i] = pruneEmptyStrings(child)
}
return v
default:
return v
}
}