package config import ( "path/filepath" "strings" configkit "gitea.maximumdirect.net/eric/ConfigKit" "gitea.maximumdirect.net/eric/scriptorium/internal/defaults" renderformat "gitea.maximumdirect.net/eric/scriptorium/internal/format" ) // Document is Scriptorium's ConfigKit-native application configuration shape. type Document struct { PromptDir string `yaml:"prompt_dir"` ProfileDir string `yaml:"profile_dir"` SchemaDir string `yaml:"schema_dir"` TransientRetries *int `yaml:"transient_retries"` Server ServerDocument `yaml:"server"` Defaults DefaultsDocument `yaml:"defaults"` Backends map[string]BackendDocument `yaml:"backends"` } type BackendDocument struct { Endpoint string `yaml:"endpoint"` APIKeyEnv string `yaml:"api_key_env"` ExtraParams map[string]any `yaml:"extra_params"` ConcurrencyLimit int `yaml:"concurrency_limit"` QueueCapacity *int `yaml:"queue_capacity"` } type ServerDocument struct { Addr string `yaml:"addr"` ArtifactRoot string `yaml:"artifact_root"` MaxRequestBytes int64 `yaml:"max_request_bytes"` MaxArtifactBytes int64 `yaml:"max_artifact_bytes"` MaxResponseBytes int64 `yaml:"max_response_bytes"` } type DefaultsDocument struct { RenderFormat string `yaml:"render_format"` } func documentDefinition() configkit.Definition[Document] { return configkit.Definition[Document]{ Defaults: documentDefaults, Resolve: resolveDocument, Validate: validateDocument, } } func documentDefaults() Document { return Document{ SchemaDir: defaults.SchemaDirDefault, Server: ServerDocument{ Addr: defaults.HTTPAddrDefault, MaxRequestBytes: defaults.HTTPMaxRequestBytesDefault, MaxArtifactBytes: defaults.HTTPMaxArtifactBytesDefault, MaxResponseBytes: defaults.HTTPMaxResponseBytesDefault, }, Defaults: DefaultsDocument{RenderFormat: string(renderformat.DefaultPreparedRunOutputFormat)}, } } func resolveDocument(document *Document, resolution *configkit.Resolution) error { normalizePath := func(path configkit.Path, value *string) { cleaned := strings.TrimSpace(*value) if cleaned != "" { cleaned = filepath.Clean(cleaned) } if cleaned != *value { configkit.Normalize(resolution, path, value, cleaned, "normalized application path") } } normalizeString := func(path configkit.Path, value *string, normalized, reason string) { if normalized != *value { configkit.Normalize(resolution, path, value, normalized, reason) } } normalizePath(configkit.Keys("prompt_dir"), &document.PromptDir) normalizePath(configkit.Keys("profile_dir"), &document.ProfileDir) normalizePath(configkit.Keys("schema_dir"), &document.SchemaDir) normalizePath(configkit.Keys("server", "artifact_root"), &document.Server.ArtifactRoot) normalizeString(configkit.Keys("server", "addr"), &document.Server.Addr, strings.TrimSpace(document.Server.Addr), "trimmed server address") normalizeString(configkit.Keys("defaults", "render_format"), &document.Defaults.RenderFormat, strings.ToLower(strings.TrimSpace(document.Defaults.RenderFormat)), "normalized render format") return nil } func validateDocument(document *Document, validation *configkit.Validation) { if document.TransientRetries != nil && *document.TransientRetries < 0 { validation.Error(configkit.Keys("transient_retries"), "must be greater than or equal to zero") } if document.Server.MaxRequestBytes < 0 { validation.Error(configkit.Keys("server", "max_request_bytes"), "must be greater than or equal to zero") } if document.Server.MaxArtifactBytes < 0 { validation.Error(configkit.Keys("server", "max_artifact_bytes"), "must be greater than or equal to zero") } if document.Server.MaxResponseBytes < 0 { validation.Error(configkit.Keys("server", "max_response_bytes"), "must be greater than or equal to zero") } switch document.Defaults.RenderFormat { case string(renderformat.OutputFormatText), string(renderformat.OutputFormatJSON): default: validation.Error(configkit.Keys("defaults", "render_format"), "must be text or json") } }