///| Describes a typed configuration key.
pub struct SchemaField {
path : String
kind : String
required : Bool
default_value : String?
description : String
rules : Array[Rule]
} derive(@debug.Debug, Eq)
///| A named group of related fields.
pub struct SchemaSection {
name : String
description : String
fields : Array[SchemaField]
} derive(@debug.Debug, Eq)
///| A complete configuration contract.
pub struct ConfigSchema {
name : String
version : String
sections : Array[SchemaSection]
} derive(@debug.Debug, Eq)
pub fn field(
path : String,
kind : String,
required? : Bool = false,
default_value? : String? = None,
description? : String = "",
rules? : Array[Rule] = [],
) -> SchemaField {
{ path, kind, required, default_value, description, rules }
}
pub fn section(name : String, description : String, fields : Array[SchemaField]) -> SchemaSection {
{ name, description, fields }
}
pub fn schema(name : String, version : String, sections : Array[SchemaSection]) -> ConfigSchema {
{ name, version, sections }
}
///| Converts schema fields into validation rules.
pub fn ConfigSchema::rules(self : ConfigSchema) -> Array[Rule] {
let result : Array[Rule] = []
for schema_section in self.sections {
for schema_field in schema_section.fields {
if schema_field.required {
result.push(required(schema_field.path))
}
for rule in schema_field.rules {
result.push(rule)
}
}
}
result
}
///| Validates a merged view against a schema.
pub fn ConfigSchema::validate(self : ConfigSchema, view : ConfigView) -> Array[Diagnostic] {
validate(view, self.rules())
}
///| Returns all known field paths in declaration order.
pub fn ConfigSchema::paths(self : ConfigSchema) -> Array[String] {
let result : Array[String] = []
for schema_section in self.sections {
for schema_field in schema_section.fields {
result.push(schema_field.path)
}
}
result
}
///| Finds schema metadata for one path.
pub fn ConfigSchema::find_field(self : ConfigSchema, path : String) -> SchemaField? {
for schema_section in self.sections {
for schema_field in schema_section.fields {
if schema_field.path == path {
return Some(schema_field)
}
}
}
None
}
///| Reports values present in the config but unknown to the schema.
pub fn ConfigSchema::unknown_key_diagnostics(self : ConfigSchema, view : ConfigView) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
let known = self.paths()
for item in view.values {
let path = entry_path(item.section, item.key)
if !array_contains(known, path) {
diagnostics.push(warning("unknown-key", "key '" + path + "' is not declared in schema '" + self.name + "'", span=Some(item.span)))
}
}
diagnostics
}
///| Produces a Markdown contract table.
pub fn ConfigSchema::render_markdown(self : ConfigSchema) -> String {
let builder = StringBuilder()
builder.write_string("# " + self.name + " Configuration Schema\n\n")
builder.write_string("Version: " + self.version + "\n\n")
for schema_section in self.sections {
builder.write_string("## " + schema_section.name + "\n\n")
if schema_section.description != "" {
builder.write_string(schema_section.description + "\n\n")
}
builder.write_string("| Path | Type | Required | Default | Description |\n")
builder.write_string("| --- | --- | --- | --- | --- |\n")
for schema_field in schema_section.fields {
builder.write_string("| " + schema_field.path + " | " + schema_field.kind + " | " + bool_label(schema_field.required) + " | " + option_or_empty(schema_field.default_value) + " | " + schema_field.description + " |\n")
}
builder.write_string("\n")
}
builder.to_string()
}
fn bool_label(value : Bool) -> String {
if value { "yes" } else { "no" }
}
fn option_or_empty(value : String?) -> String {
match value {
Some(text) => text
None => ""
}
}
fn array_contains(values : Array[String], target : String) -> Bool {
for value in values {
if value == target {
return true
}
}
false
}
///| A reusable schema for small web services.
pub fn web_service_schema() -> ConfigSchema {
schema("web-service", "1.0", [
section("server", "HTTP listener and runtime behavior.", [
field("server.host", "string", required=true, description="Bind address or host name."),
field("server.port", "int", required=true, description="TCP port.", rules=[int_range("server.port", Some(1), Some(65535))]),
field("server.mode", "enum", required=false, default_value=Some("development"), description="Runtime mode.", rules=[enum_value("server.mode", ["development", "test", "production"])]),
field("server.workers", "int", required=false, default_value=Some("1"), description="Worker count.", rules=[int_range("server.workers", Some(1), Some(256))]),
field("server.request_timeout_ms", "int", required=false, default_value=Some("30000"), description="Request timeout in milliseconds.", rules=[int_range("server.request_timeout_ms", Some(1), Some(600000))]),
]),
section("database", "Primary database connection settings.", [
field("database.url", "string", required=true, description="Database connection URL."),
field("database.pool_min", "int", required=false, default_value=Some("1"), description="Minimum pool size.", rules=[int_range("database.pool_min", Some(0), Some(1024))]),
field("database.pool_max", "int", required=false, default_value=Some("16"), description="Maximum pool size.", rules=[int_range("database.pool_max", Some(1), Some(4096))]),
field("database.password", "secret", required=false, description="Database password.", rules=[secret_like("database.password")]),
]),
section("auth", "Authentication and authorization settings.", [
field("auth.enabled", "bool", required=false, default_value=Some("false"), description="Enable authentication."),
field("auth.token", "secret", required=false, description="Static service token.", rules=[secret_like("auth.token")]),
field("auth.oauth", "bool", required=false, description="Enable OAuth."),
field("auth.client_id", "string", required=false, description="OAuth client id.", rules=[requires("auth.oauth", "auth.client_id")]),
field("auth.basic", "bool", required=false, description="Enable basic auth.", rules=[conflicts("auth.oauth", "auth.basic")]),
]),
])
}
///| A reusable schema for CLI tools.
pub fn cli_tool_schema() -> ConfigSchema {
schema("cli-tool", "1.0", [
section("output", "Command output behavior.", [
field("output.format", "enum", required=false, default_value=Some("text"), description="Output format.", rules=[enum_value("output.format", ["text", "json", "markdown"])]),
field("output.color", "enum", required=false, default_value=Some("auto"), description="Color mode.", rules=[enum_value("output.color", ["auto", "always", "never"])]),
field("output.quiet", "bool", required=false, default_value=Some("false"), description="Suppress non-error output."),
]),
section("runtime", "Runtime limits and cache settings.", [
field("runtime.threads", "int", required=false, default_value=Some("1"), description="Worker thread count.", rules=[int_range("runtime.threads", Some(1), Some(128))]),
field("runtime.cache_dir", "path", required=false, description="Cache directory path."),
field("runtime.timeout_ms", "int", required=false, default_value=Some("30000"), description="Global timeout.", rules=[int_range("runtime.timeout_ms", Some(1), Some(86400000))]),
]),
])
}
///| A reusable schema for test environments.
pub fn test_environment_schema() -> ConfigSchema {
schema("test-environment", "1.0", [
section("test", "Test selection and failure policy.", [
field("test.pattern", "string", required=false, default_value=Some("*"), description="Test name filter."),
field("test.retries", "int", required=false, default_value=Some("0"), description="Retry count.", rules=[int_range("test.retries", Some(0), Some(20))]),
field("test.fail_fast", "bool", required=false, default_value=Some("false"), description="Stop after the first failure."),
]),
section("fixture", "Fixture and sandbox options.", [
field("fixture.root", "path", required=true, description="Fixture root directory."),
field("fixture.reset", "bool", required=false, default_value=Some("true"), description="Reset fixtures before running."),
field("fixture.seed", "int", required=false, default_value=Some("1"), description="Deterministic seed.", rules=[int_range("fixture.seed", Some(0), None)]),
]),
])
}