Get started / Swift
docuconf for Swift
Values are read through Apple's swift-configuration, with its key names and parsing. docuconf adds the declaration it lacks: @Env and @FileInput property wrappers with descriptions, secrets and constraints.
- Builds on
- swift-configuration
- Package
- Docuconf (SwiftPM)
- Requires
- Swift 6.2
- Status
- v0.1 alpha, not yet released · conformance 132 of 134 (skips json-schema)
- Source
- docuconf-swift · the orders example
Not published yet
main branch. After the first release it will be .package(url: "https://github.com/Docuconf/docuconf-swift", from: "0.1.0").The code below is the orders service, the example every SDK ships: six variables, one of them a secret, checked the same way in every language. Compare it across languages.
1.Install
There is no tagged release yet, so depend on the main branch (Swift 6.2+, Linux or macOS 15+):
swift builddependencies: [
// Until the first release, from the main branch:
.package(url: "https://github.com/Docuconf/docuconf-swift", branch: "main"),
],2.Declare your configuration
A struct conforming to DocuconfConfig, with one @Env per variable: the swift-configuration key, a description and the rules. The environment variable is the key in upper case (log.level is LOG_LEVEL). A property with no initial value is required. A multi-line description is a doc comment: its first paragraph is the description and the rest the details.
enum LogLevel: String, ConfigEnum {
case debug, info, warn, error
}struct OrdersConfig: DocuconfConfig {
@Env("port", "HTTP listen port", .range(1...65535))
var port = 8080
@Env("log.level", "Minimum log level")
var logLevel = LogLevel.info
@Env("database.url", "Postgres connection string for the orders database", .secret, .schemes("postgres"), .maxLength(2048))
var databaseURL: URL
@Env("allowed.origins", "Origins allowed to call the API (CORS)", .minItems(1))
var allowedOrigins = ["http://localhost:3000"]
// Durations are read as a number of seconds (REQUEST_TIMEOUT=30); the contract holds "30s".
@Env("request.timeout", "Timeout for one request", .range(.seconds(1) ... .seconds(300)))
var requestTimeout: Duration = .seconds(30)
// A description may be a whole doc comment: its first paragraph is the description, the rest the details.
@Env("worker.count", """
Number of background order workers
Each worker takes one order at a time from the queue and holds one database connection, so keep this
at or below the pool size:
- one connection per worker;
- plus one for the HTTP handlers.
""", .range(1...64))
var workerCount = 4
}3.Load it at boot
Docuconf.loadOrExit reads every variable through swift-configuration and checks every file, then returns the struct, or prints every problem and exits 1 without a backtrace. Docuconf.load throws ConfigurationError instead. exportIfRequested turns the executable into its own exporter.
Docuconf.exportIfRequested(OrdersConfig.self, name: "orders")
// Reads the environment. On a problem it prints every violation at once (also to /dev/termination-log) and exits 1.
let config = await Docuconf.loadOrExit(OrdersConfig.self)4.Run it, and see an error
Run the example with a valid environment:
DATABASE_URL=postgres://orders:pw@localhost:5432/orders swift run OrdersWith PORT=0 and no DATABASE_URL, the service does not start: it lists every problem with its error code and exits 1. The same text goes to /dev/termination-log.
$ PORT=0 swift run Orders
...
docuconf: 2 configuration problems:
- PORT [out_of_range]: is below min 1 (got "0")
- DATABASE_URL [missing_required]: is required but not set (Postgres connection string for the orders database)5.Test your configuration
Docuconf.load(_:environment:) reads only the dictionary it is given, never the process environment, and writes no termination log, so tests can run in parallel. This uses Swift Testing, in a test target that depends on the executable.
import Docuconf
import Testing
@testable import Orders
// load(_:environment:) reads only this dictionary: never the process environment, and no termination log.
func load(_ environment: [String: String]) async throws -> OrdersConfig {
try await Docuconf.load(OrdersConfig.self, environment: environment)
}
@Test func defaults() async throws {
let config = try await load(["DATABASE_URL": "postgres://orders@db/orders"])
#expect(config.port == 8080)
#expect(config.workerCount == 4)
}
@Test func rejectsBadValues() async throws {
let error = await #expect(throws: ConfigurationError.self) { try await load(["PORT": "70000"]) }
let found = error?.violations.map { "\($0.input) \($0.code.rawValue)" } ?? []
#expect(found.contains("PORT out_of_range"))
#expect(found.contains("DATABASE_URL missing_required"))
}swift test6.Export the contract
In export mode the executable reads no environment and checks no files, so it runs in CI without production values.
swift run Orders docuconf-export --out contract.cue// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {language: "swift", sdk: "docuconf-swift", version: "0.1.0"}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
configKey: "allowed.origins"
items: "string"
encoding: "csv"
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
configKey: "database.url"
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
configKey: "log.level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
configKey: "port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for one request"
configKey: "request.timeout"
encoding: "seconds"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background order workers"
details: "Each worker takes one order at a time from the queue and holds one database connection, so keep this\nat or below the pool size:\n\n- one connection per worker;\n- plus one for the HTTP handlers."
configKey: "worker.count"
min: 1
max: 64
default: 4
}
}
}The platform then checks each environment's values against contract.cue before it deploys, with docuconf vet, the Helm chart or CUE. See How it works.
7.Vapor and Hummingbird
Load the config in your entry point before you build the Application, then pass it to your routes. The keys are swift-configuration keys, so reader.int(forKey: "port") elsewhere reads the same value, and a TLSKeyPair file input gives PEM ready for swift-nio-ssl. To add config files, pass providers as Docuconf.load(_:files:); see the README.
Next
- The docuconf-swift README: every type, file input and option.
- The orders example, with its smoke test.
- The same service in every language, side by side.
- What every SDK supports, and this one's known gaps: Built and tested on Linux; macOS and iOS builds are untested. No TOML, no profiles.
Every snippet on this page is compiled or run against docuconf-swift's main branch in CI. Facts checked 2026-10-08.