Loading
feat(config): runtime DatabaseConfig + DSN assembly (S04 Step 1)
Summary
Step 1 of the database composition-root plan.
Converts the DatabaseConfig proto into a runtime config.DatabaseConfig, assembles the LabKit postgres.Client DSN from the split parameters, and rejects database.tls.server_name at config-load because the current LabKit release exposes no DSN equivalent (sslservername does not exist).
This MR ships parsing and assembly only. Step 2 (migration runner) and Step 3 (composition root wiring) follow in sibling MRs.
What's in this MR
internal/config/database.go(new):DatabaseConfig,DatabasePoolConfig,TLSConfigruntime types;databaseFromProtoconverter wired intoLoad;AssembleDSNbuilds apostgres://URL vianet/url(password percent-encoded, TLS file paths encoded assslrootcert/sslcert/sslkeyonly when populated);*UnsupportedTLSFieldErrorfor theserver_namerejection.internal/config/config.go: addsDatabase DatabaseConfigtoConfigand callsdatabaseFromProtoinLoad.internal/config/database_test.go(new): nine targeted tests covering the converter happy path, both pool-duration error paths, missing-stanza zero-value contract,server_namerejection, and fourAssembleDSNshape tests.proto/.../config.proto+gen/.../config.pb.go: doc-comment-only update onTLSConfig.server_nameflagging the database-path limitation. No field changes; the regenerated.pb.godiff is limited to the leading comment.- Fixtures: extends
happy_path.yamlwith adatabase:stanza, addsinvalid_database_pool_duration.yamlas a typed-error witness, renames the prior server-only fixture tono_database.yamlfor the missing-stanza test.
Acceptance (plan Step 1)
| Criterion | Pinned by |
|---|---|
Populated database: yields full Config.Database |
TestDatabaseFromProto_HappyPath, TestLoad_HappyPath |
Invalid pool duration returns *DurationParseError |
TestDatabaseFromProto_InvalidMaxLifetime, TestDatabaseFromProto_InvalidMaxIdleTime, TestLoad_DatabaseInvalidPoolDuration |
Missing database: yields zero DatabaseConfig |
TestDatabaseFromProto_AbsentBlockYieldsZeroValue |
AssembleDSN parsable by pgx.ParseConfig, sslmode and TLS paths encoded, password percent-encoded |
TestAssembleDSN_NoTLS, TestAssembleDSN_WithTLSPaths, TestAssembleDSN_PasswordEscaping |
tls.server_name populated returns *UnsupportedTLSFieldError |
TestDatabaseFromProto_RejectsServerNameAsUnsupported |
Empty host short-circuits to preserve downstream ErrDSNRequired |
TestAssembleDSN_EmptyHostReturnsEmpty |
Test plan
-
go build ./... -
go vet ./internal/config/... -
go test -race -short -count=1 ./internal/config/...(passes;database.goat 97.3% coverage) -
golangci-lint run ./internal/config/...at v2.11.0 (skill-pinned) and v2.12.0 (CI-pinned): 0 issues
Plan reference
- Plan:
docs/plans/2026-05-26-database-composition-root.md(Step 1) - Spec:
docs/specs/S04-database.md(Configuration section)