Buildable
Production-grade Builders for Swift, without the boilerplate. Powered by Swift Macros.
Buildable generates fluent, type-safe builders for your models so you can focus on behavior, not repetitive setup code.
This is especially useful in tests, fixtures, prototyping, and anywhere model construction starts to drown out intent.
In real projects, builders are everywhere:
- Test data setup
- Complex nested models
- Enums with associated values
- Avoiding 12-parameter initializers no one enjoys reading
And yet they are:
- Repetitive
- Error-prone
- Painfully boring to maintain
Buildable turns builders into generated infrastructure, the way they always should have been.
- Zero manual builder boilerplate
- Immutable fluent builders or mutable builders
- Nested builders via
@Built - Default value handling
- Enum support (associated values, labels, tuples)
- Explicit, predictable failure modes
- Swift 5.9+
- Xcode 15+
Macros are non-negotiable. Blame Apple.
Add the package via Xcode (File → Add Packages…), then:
import Buildable@Buildable
struct User {
let id: UUID
let email: String
var isAdmin: Bool = false
}let user = UserBuilder()
.id(UUID())
.email("user@example.com")
.build()
XCTAssertEqual(user?.isAdmin, false)You get readable intent without noise. Defaults behave like defaults.
@Buildable
struct Address {
let city: String
let country: String
}
@Buildable
struct Profile {
let name: String
@Built let address: Address
}let profile = ProfileBuilder()
.name("Gábor")
.address {
$0.city("Budapest")
.country("Hungary")
.build()
}
.build()Nested builders read top-down, exactly like the object graph.
Some test setups are easier with mutation. That is allowed.
@Buildable(isMutable: true)
struct Order {
let id: String
let amount: Int
var isPaid: Bool = false
}let builder = OrderBuilder()
builder.id = "order-42"
builder.amount = 3
builder.isPaid = true
let order = try builder.build()Missing required values throw:
BuilderError.missingParameter("amount")No guessing. No silent corruption.
@Buildable
enum AppAction {
case login(email: String, password: String)
case logout
case deepLink(URL)
}let login = AppActionBuilder()
.login(email: "test@site.com", password: "123456")
.build()
let logout = AppActionBuilder().logout().build()Labels are preserved. Intention survives refactors.
@Buildable
enum Event {
@Built case purchase(Order)
case cancel(reason: String)
}let event = EventBuilder()
.purchase { builder in
builder.id("order-1")
.amount(5)
.build()
}
.build()Note: @Built enum cases currently support a single associated value only.
let value: T→ requiredlet value: T?→ optionalvar value: T = default→ optional in builder, default used on build- Computed properties are ignored
Immutable builders return nil if required values are missing.
Mutable builders throw explicit errors.
-
Immutable
- Short fixtures
- Declarative tests
- Snapshot-style setup
-
Mutable
- Large object graphs
- Step-by-step configuration
- Conditional setup logic
You can mix both in the same codebase.
- Builders are infrastructure, not handwritten art projects
- Generated code must be predictable and boring
- Errors should surface immediately
- Readability beats cleverness
PRs are welcome if they:
- Add tests
- Preserve API stability
- Don’t introduce magical behavior
If it makes builders mysterious, it probably won’t be merged.
MIT
