binder

package
v0.32.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package binder builds Cobra flags (and, in form.go, an interactive huh form) from a Go struct by reflection. In V1 it ships for exactly one consumer: the jenticctl installer's generated BackendConfig (impl/6.0 §3) — the API-surface binder described in impl/2.2 stays a post-V1 exploration (00_openapi_gen.md §3).

The installer config is NESTED and not uniformly one level deep (databases.registry.host is depth 2; security.jwt_verification.* nests further), so the binder walks the struct recursively and binds every SCALAR leaf under its full dotted path with dots/underscores rendered as dashes: server.public_base_url → --server-public-base-url, databases.registry.host → --databases-registry-host. That is deliberately the same naming rule as the backend's JENTIC__SECTION__KEY env overrides and the YAML keys — one mental model across flags, env, and file. Non-scalar leaves (slices, maps) are skipped: they stay reachable only via YAML/--out, never a flag (impl/6.0 §3, "flatten scalars, never collections").

Two lessons from impl/2.2 §1a carry over: unmapped scalar-ish kinds FAIL LOUD (panic at construction, caught in CI, never a silently-undsettable field), and pointer-ness is used only for allocation during hydration, never as a semantic signal.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindFlags

func BindFlags(cmd *cobra.Command, target interface{})

BindFlags registers one Cobra flag per scalar leaf of the nested config struct, named by its dotted path flattened to kebab-case (impl/6.0 §3). It is idempotent-safe only on a fresh command; re-binding the same struct onto a command that already has a colliding flag will panic via pflag, which is the desired loud failure.

func BindFlagsWithOptions

func BindFlagsWithOptions(cmd *cobra.Command, target interface{}, opts BindOptions)

BindFlagsWithOptions is BindFlags with the installer's Exclude/Hidden controls.

func BuildDynamicForm

func BuildDynamicForm(target interface{}, opts FormOptions) *huh.Form

BuildDynamicForm inspects the nested config struct and generates a grouped interactive huh.Form: one huh.Group per top-level section, one huh.Field per scalar leaf inside it (impl/6.1 §3). Grouping is STRUCTURAL — AppConfig's section models already partition every leaf into the logical clusters the wizard wants, so there are no group tags to inject into the read-only generated struct. Deeper scalar leaves surface under their dotted path within their section's screen, matching the flag flatten rule. Non-scalar leaves (slices/maps) and excluded (sensitive) leaves are skipped — reachable only via flags/YAML.

The returned form's fields bind directly to the struct's leaves, so on form.Run() completion the config is populated in place. Pointer sections/leaves are allocated first (via the shared walk) so Bubble Tea never nil-derefs.

func ChangedOverrides

func ChangedOverrides(cmd *cobra.Command, target interface{}) (map[string]any, error)

ChangedOverrides returns a nested map of ONLY the flags the operator explicitly set, keyed by the config's dotted path split back into a section tree (databases-registry-host, set, → {"databases":{"registry":{"host":...}}}). This is the flag layer of the installer's precedence ladder: it feeds straight into the settings merge (ctl.ResolveSettings) so unset flags never contribute a zero-value that would clobber a schema default or preset (impl/6.0 §3.5). The target is used only to enumerate the leaves and their kinds; its field values are not read.

func HydrateStruct

func HydrateStruct(cmd *cobra.Command, target interface{}) error

HydrateStruct writes every EXPLICITLY-SET flag back onto its leaf, leaving unset leaves untouched so the defaults<preset<flags precedence ladder (impl/6.0 §3.5) holds: applyDefaults/applyPreset populate cfg first, then this overrides only what the operator actually passed. Flags the user did not set are skipped via pflag's Changed(), never clobbering a preset/default value with a flag zero-value.

func LeafPaths

func LeafPaths(target interface{}) []string

LeafPaths returns the dotted path of every scalar leaf of the nested config struct, in walk order. Callers use it to build Exclude sets for a whole section (e.g. everything under `telemetry.`) without duplicating the leaf walk that BindFlags and the form generator share.

func NonZeroOverrides

func NonZeroOverrides(target interface{}, exclude map[string]bool) map[string]any

NonZeroOverrides returns a nested map of the struct's scalar leaves that hold a NON-ZERO value, skipping any path in exclude. It is the read-back for the interactive config form (impl/6.1): the form binds a fresh struct, the operator fills in only what they want to change, and this collects exactly those leaves as overrides ("" / 0 / false == "leave the wizard's value alone"). exclude carries the sensitive paths so a secret typed into a (non-excluded-by-accident) field can never leak into the overlay.

Types

type BindOptions

type BindOptions struct {
	Exclude map[string]bool
	Hidden  bool
}

BindOptions tune BindFlags for the installer's needs (impl/6.0): Exclude drops leaves entirely (sensitive secret-bearing paths never become flags), and Hidden registers the flags but marks them hidden so they work yet stay out of --help and the public cli-reference noise. Paths are dotted (server.public_base_url).

type FormOptions

type FormOptions struct {
	Exclude map[string]bool
}

FormOptions tune the generated interactive form. Exclude drops leaves (the installer passes the sensitive-path set so secrets are never prompted as plain form fields — they belong in the secret-generation flow, impl/6.0).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL