Bidirectional coding primitives — Coder.Protocol unifies a parser and a serializer into one codec type, so decode and encode of a format live on a single conformer with unified Input / Output / Buffer / Failure. Writing the two directions as separate parser and serializer types forces two names, two failure types, and a round-trip contract nobody checks; a coder is the format-times-value unit that carries both directions and the contract in one place.
The two directions are deliberately asymmetric: decode reads from a cursor Input (which may be non-copyable or non-escapable, such as a borrowed span), while encode appends to a mutable Buffer. Codecs whose two directions genuinely differ in type and failure mode fit here; text round-trips that share one input type and compose declaratively belong to Parser.Bidirectional in swift-parser-primitives instead.
- One conformer, both directions —
Coder.Protocolrefines the parser and serializer protocols; conforming once yieldsparse(_:)andserialize(_:into:)with a single unified failure type. - Typed throws end-to-end —
Failureis a type parameter on every surface; noany Errorappears in the API. Codecs with distinct decode/encode failures populateFailurewithEither<DecodeFailure, EncodeFailure>from swift-either-primitives. - Closure-backed witnesses —
Coder.Witnessbuilds an ad-hoc codec from a parse closure and a serialize closure;Coder.Pureis theFailure == Nevershorthand for infallible codecs. - Canonical-coder attachment — the
Codableprotocol lets a value type declare its canonical coder, giving the type itselfinit(decoding:)andencoded(). - Leaf by design — coders do not compose through a
bodybuilder; combinators belong on parsers and serializers, and a coder is the terminal format-times-value unit.
Note: the package's Codable protocol intentionally shadows Swift.Codable. Code that needs Swift's built-in protocol alongside this package writes Swift.Codable explicitly.
import Coder_Primitives
struct TruncatedInput: Error {}
// One value for both directions: a big-endian UInt16 codec.
let uint16BE = Coder.Witness<ArraySlice<UInt8>, UInt16, [UInt8], TruncatedInput>(
parse: { (input) throws(TruncatedInput) in
guard let high = input.popFirst(), let low = input.popFirst() else {
throw TruncatedInput()
}
return UInt16(high) << 8 | UInt16(low)
},
serialize: { value, buffer in
buffer.append(UInt8(value >> 8))
buffer.append(UInt8(truncatingIfNeeded: value))
}
)
var wire: ArraySlice<UInt8> = [0x01, 0x02]
let port = try uint16BE.parse(&wire) // 258, wire fully consumed
var encoded: [UInt8] = []
try uint16BE.serialize(port, into: &encoded) // [0x01, 0x02] — round-trips by constructionA type can adopt its codec as canonical via Codable, so decode and encode become members of the type itself:
struct Port { var number: UInt16 }
extension Port: Coder_Primitives.Codable {
static var coder: Coder_Primitives.Coder.Witness<ArraySlice<UInt8>, Port, [UInt8], TruncatedInput> {
.init(
parse: { (input) throws(TruncatedInput) in
guard let high = input.popFirst(), let low = input.popFirst() else {
throw TruncatedInput()
}
return Port(number: UInt16(high) << 8 | UInt16(low))
},
serialize: { port, buffer in
buffer.append(UInt8(port.number >> 8))
buffer.append(UInt8(truncatingIfNeeded: port.number))
}
)
}
}
var input: ArraySlice<UInt8> = [0x01, 0x02]
let https = try Port(decoding: &input) // Port(number: 258)
let bytes = try https.encoded() // [0x01, 0x02]Add the dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/swift-primitives/swift-coder-primitives.git", branch: "main")
]Add the product to your target:
.target(
name: "App",
dependencies: [
.product(name: "Coder Primitives", package: "swift-coder-primitives")
]
)The package is pre-1.0 — depend on branch: "main" until 0.1.0 is tagged. Requires Swift 6.3 and macOS 26 / iOS 26 / tvOS 26 / watchOS 26 / visionOS 26 (or the corresponding Linux / Windows toolchain).
| Product | Contents | When to import |
|---|---|---|
Coder Primitives |
Coder.Protocol, Coder.Witness, Coder.Pure, and the Codable attachment protocol |
Most consumers |
Coder Primitives Test Support |
Re-export of Coder Primitives for test targets |
Test targets that exercise codecs |
The surface is small and early: the protocol, one closure-backed witness, and the canonical-coder attachment. Combinator vocabulary lives in the parser and serializer packages this one refines; no coder-side combinators exist yet.
| Platform | CI | Status |
|---|---|---|
| macOS 26 | Yes | Full support |
| Linux | Yes | Full support |
| Windows | Yes | Full support |
| iOS/tvOS/watchOS | — | Supported |
swift-parser-primitives— the parser vocabularyCoder.Protocolrefines; useParser.Bidirectionalthere when both directions share one input type and compose declaratively.swift-serializer-primitives— the serializer vocabularyCoder.Protocolrefines.swift-either-primitives—Either<DecodeFailure, EncodeFailure>for codecs with asymmetric failure modes.
Discussion thread will be created at first public release.
Apache 2.0. See LICENSE.md.