// Package consumers contains small, maintained application shapes that exercise // ConfigKit without making consumer domain policy part of the library API. package consumers import ( "math" "path/filepath" "strconv" "strings" "time" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) // Artifact is a Narratio-shaped generated output declaration. type Artifact struct { Output string `yaml:"output"` Timeout time.Duration `yaml:"timeout"` } // NarratioConfig demonstrates application defaults, partial imports, and // application-owned model-profile and artifact semantics. type NarratioConfig struct { PollInterval time.Duration `yaml:"poll_interval"` Timeout time.Duration `yaml:"timeout"` ModelProfile string `yaml:"model_profile"` Artifacts map[string]Artifact `yaml:"artifacts"` } // LoadNarratio loads the maintained import fixture with Narratio-shaped rules. func LoadNarratio(path string) (*configkit.Result[NarratioConfig], error) { return LoadNarratioOverlay(path, "") } // LoadNarratioOverlay selects a ConfigKit configuration overlay independently // from the application's ModelProfile field. func LoadNarratioOverlay(path, overlay string) (*configkit.Result[NarratioConfig], error) { return configkit.Load(configkit.File(path), configkit.Options[NarratioConfig]{ Imports: true, Overlay: overlay, Definition: configkit.Definition[NarratioConfig]{ Defaults: func() NarratioConfig { return NarratioConfig{PollInterval: time.Minute} }, Resolve: resolveNarratio, Validate: validateNarratio, }, }) } func resolveNarratio(configuration *NarratioConfig, resolution *configkit.Resolution) error { configkit.Default(resolution, configkit.Keys("timeout"), &configuration.Timeout, configuration.PollInterval/10, "derived from poll interval", configkit.Keys("poll_interval")) for name, artifact := range configuration.Artifacts { path := configkit.Keys("artifacts", name) configkit.Default(resolution, path.Key("timeout"), &artifact.Timeout, configuration.Timeout, "inherited pipeline timeout", configkit.Keys("timeout")) configkit.Normalize(resolution, path.Key("output"), &artifact.Output, strings.TrimSpace(artifact.Output), "trimmed artifact output") configuration.Artifacts[name] = artifact } return nil } func validateNarratio(configuration *NarratioConfig, validation *configkit.Validation) { if configuration.PollInterval <= 0 { validation.Error(configkit.Keys("poll_interval"), "must be positive") } if configuration.Timeout <= 0 { validation.Error(configkit.Keys("timeout"), "must be positive") } for name, artifact := range configuration.Artifacts { path := configkit.Keys("artifacts", name) if artifact.Output == "" { validation.Error(path.Key("output"), "must be nonempty") } if artifact.Timeout <= 0 { validation.Error(path.Key("timeout"), "must be positive") } } } // ObjectBinding is a Notarius-shaped canonical object binding. Options remain // dynamic because the application catalog selects their schema at runtime. type ObjectBinding struct { Object string `yaml:"object"` Options map[string]any `yaml:"options"` } // NotariusConfig demonstrates string-backed versions and dynamic catalog options. type NotariusConfig struct { Version string `yaml:"version"` Bindings []ObjectBinding `yaml:"bindings"` } // LoadNotarius loads a Notarius-shaped configuration. func LoadNotarius(path string) (*configkit.Result[NotariusConfig], error) { return configkit.Load(configkit.File(path), configkit.Options[NotariusConfig]{ Definition: configkit.Definition[NotariusConfig]{ Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: requireVersion("4")}, Validate: validateNotarius, }, }) } func requireVersion(expected string) func(configkit.Version, *configkit.Validation) error { return func(version configkit.Version, validation *configkit.Validation) error { if !version.Present || version.Null || version.Text != expected { validation.Error(configkit.Keys("version"), "unsupported configuration version") } return nil } } func validateNotarius(configuration *NotariusConfig, validation *configkit.Validation) { for index, binding := range configuration.Bindings { path := configkit.Keys("bindings").Index(index) if binding.Object != "summary" && binding.Object != "registry" { validation.Error(path.Key("object"), "unsupported object binding") } for key, value := range binding.Options { optionPath := path.Key("options").Key(key) switch key { case "window_units": units, ok := value.(int64) if !ok || units < 1 { validation.Error(optionPath, "must be a positive integer") } case "format": if _, ok := value.(string); !ok { validation.Error(optionPath, "must be a string") } default: validation.Error(optionPath, "unsupported object option") } } } } // NotariusWindow converts ConfigKit's portable dynamic integer only at the // application runtime boundary. func NotariusWindow(configuration NotariusConfig, binding int) (int, bool) { if binding < 0 || binding >= len(configuration.Bindings) { return 0, false } value, ok := configuration.Bindings[binding].Options["window_units"].(int64) if !ok || value < 1 || (strconv.IntSize == 32 && value > math.MaxInt32) { return 0, false } return int(value), true } // NotariusPathConfig demonstrates application-owned interpretation of a // path-valued configuration field. ConfigKit supplies provenance; this example // selects its own relative-path bases. type NotariusPathConfig struct { Reference string `yaml:"reference"` Sensitive string `yaml:"sensitive"` } // LoadNotariusPaths loads the path-bearing Notarius-shaped example. InvocationDir // is the application-supplied base for relative CLI overrides; it is never read // from the process working directory. func LoadNotariusPaths(path, invocationDir string, overrides ...configkit.Override) (*configkit.Result[NotariusPathConfig], error) { return configkit.Load(configkit.File(path), configkit.Options[NotariusPathConfig]{ Imports: true, Overrides: overrides, Definition: configkit.Definition[NotariusPathConfig]{ Defaults: func() NotariusPathConfig { return NotariusPathConfig{Reference: "defaults/reference.txt"} }, Resolve: func(configuration *NotariusPathConfig, resolution *configkit.Resolution) error { resolveNotariusReference(configuration, resolution, invocationDir) return nil }, Secret: func(path configkit.Path) bool { return path.String() == "/sensitive" }, }, }) } func resolveNotariusReference(configuration *NotariusPathConfig, resolution *configkit.Resolution, invocationDir string) { path := configkit.Keys("reference") if configuration.Reference == "" || filepath.IsAbs(configuration.Reference) { return } info, found := resolution.InputInfo(path) if !found { resolution.Warning(path, "relative reference has no entry provenance") return } base := "" if info.Origin.Layer == configkit.LayerCLI { base = invocationDir if base == "" { resolution.Warning(path, "relative CLI reference has no invocation directory") return } } else { sourceID := "source-1" // ConfigKit's primary-source identity convention. if info.Origin.Layer != configkit.LayerDefault { if len(info.Origin.Locations) != 1 { resolution.Warning(path, "relative reference has no unambiguous declaring source") return } sourceID = info.Origin.Locations[0].SourceID } source, exists := resolution.Source(sourceID) if !exists || source.Path == "" { resolution.Warning(path, "relative reference has no declaring file") return } base = filepath.Dir(source.Path) } configkit.Normalize(resolution, path, &configuration.Reference, filepath.Join(base, configuration.Reference), "resolved relative reference from entry source") } // ModuleOptions is a WeatherReporter-shaped union of known module option fields. type ModuleOptions struct { WindowUnits *int `yaml:"window_units"` Sections []string `yaml:"sections"` } // ModuleSpec uses the canonical object form instead of a string shorthand. type ModuleSpec struct { Module string `yaml:"module"` Options ModuleOptions `yaml:"options"` } // ReportConfig keeps report-specific lists application-owned. type ReportConfig struct { Modules []ModuleSpec `yaml:"modules"` PathTemplates []string `yaml:"path_templates"` } // WeatherConfig demonstrates simple defaults, reports, module options, and an // optional typed secret reference. type WeatherConfig struct { Units string `yaml:"units"` Reports map[string]ReportConfig `yaml:"reports"` Token *configkit.Secret `yaml:"token"` } // LoadWeather loads a WeatherReporter-shaped source and explicit CLI records. func LoadWeather(source configkit.Source, overrides ...configkit.Override) (*configkit.Result[WeatherConfig], error) { return configkit.Load(source, configkit.Options[WeatherConfig]{ Overrides: overrides, Definition: weatherDefinition(func() WeatherConfig { return WeatherConfig{Units: "metric"} }), }) } // LoadConstructedWeather runs the same rules over a detached constructed value. func LoadConstructedWeather(configuration WeatherConfig) (*configkit.Result[WeatherConfig], error) { return configkit.Load(configkit.NoSource(), configkit.Options[WeatherConfig]{ Definition: weatherDefinition(func() WeatherConfig { return configuration }), }) } func weatherDefinition(defaults func() WeatherConfig) configkit.Definition[WeatherConfig] { return configkit.Definition[WeatherConfig]{ Defaults: defaults, Resolve: func(configuration *WeatherConfig, resolution *configkit.Resolution) error { for reportName, report := range configuration.Reports { var modules []ModuleSpec if report.Modules != nil { modules = append(make([]ModuleSpec, 0, len(report.Modules)), report.Modules...) } for index, module := range modules { if module.Options.Sections != nil { sections := append([]string(nil), module.Options.Sections...) for position := range sections { sections[position] = strings.TrimSpace(sections[position]) } module.Options.Sections = sections } modules[index] = module } configkit.Normalize(resolution, configkit.Keys("reports", reportName, "modules"), &report.Modules, modules, "normalized module options") configuration.Reports[reportName] = report } return nil }, Validate: validateWeather, } } func validateWeather(configuration *WeatherConfig, validation *configkit.Validation) { if configuration.Units != "metric" && configuration.Units != "us" { validation.Error(configkit.Keys("units"), "must be metric or us") } for reportName, report := range configuration.Reports { path := configkit.Keys("reports", reportName) if report.Modules != nil && len(report.Modules) == 0 { validation.Error(path.Key("modules"), "must contain at least one module") } if report.PathTemplates != nil && len(report.PathTemplates) == 0 { validation.Error(path.Key("path_templates"), "must contain at least one template") } for index, module := range report.Modules { modulePath := path.Key("modules").Index(index) switch module.Module { case "chunker": if module.Options.WindowUnits == nil || *module.Options.WindowUnits < 1 { validation.Error(modulePath.Key("options").Key("window_units"), "must be a positive integer") } if module.Options.Sections != nil { validation.Error(modulePath.Key("options").Key("sections"), "not supported by this module") } case "discussion": if module.Options.WindowUnits != nil { validation.Error(modulePath.Key("options").Key("window_units"), "not supported by this module") } if len(module.Options.Sections) == 0 { validation.Error(modulePath.Key("options").Key("sections"), "must contain at least one section") } default: validation.Error(modulePath.Key("module"), "unsupported module") } } } } // SpeakerRule and SpeakerRules preserve declaration order for application matching. type SpeakerRule struct { Prefix string `yaml:"prefix"` Name string `yaml:"name"` } type SpeakerRules struct { Rules []SpeakerRule `yaml:"rules"` } // AutocorrectRule and AutocorrectRules preserve replacement order. type AutocorrectRule struct { Find string `yaml:"find"` Replace string `yaml:"replace"` } type AutocorrectRules struct { Rules []AutocorrectRule `yaml:"rules"` } // LoadSpeakerRules loads one independent Seriatim-shaped rule file. func LoadSpeakerRules(path string) (*configkit.Result[SpeakerRules], error) { return configkit.Load(configkit.File(path), configkit.Options[SpeakerRules]{ Definition: configkit.Definition[SpeakerRules]{Validate: validateSpeakerRules}, }) } // LoadAutocorrectRules loads another independent ordered rule file. func LoadAutocorrectRules(path string) (*configkit.Result[AutocorrectRules], error) { return configkit.Load(configkit.File(path), configkit.Options[AutocorrectRules]{ Definition: configkit.Definition[AutocorrectRules]{Validate: validateAutocorrectRules}, }) } func validateSpeakerRules(configuration *SpeakerRules, validation *configkit.Validation) { seen := make(map[string]int) for index, rule := range configuration.Rules { path := configkit.Keys("rules").Index(index) if rule.Prefix == "" { validation.Error(path.Key("prefix"), "must be nonempty") } if previous, ok := seen[rule.Prefix]; ok { validation.Error(path.Key("prefix"), "prefix must be unique", configkit.Keys("rules").Index(previous).Key("prefix")) } seen[rule.Prefix] = index } } func validateAutocorrectRules(configuration *AutocorrectRules, validation *configkit.Validation) { for index, rule := range configuration.Rules { path := configkit.Keys("rules").Index(index) if rule.Find == "" { validation.Error(path.Key("find"), "must be nonempty") } } } // SpeakerName is application runtime matching, not configuration infrastructure. func SpeakerName(rules SpeakerRules, input string) (string, bool) { for _, rule := range rules.Rules { if strings.HasPrefix(input, rule.Prefix) { return rule.Name, true } } return "", false } // ApplyAutocorrect is application runtime replacement in declared order. func ApplyAutocorrect(rules AutocorrectRules, input string) string { for _, rule := range rules.Rules { input = strings.ReplaceAll(input, rule.Find, rule.Replace) } return input }