Skip to content
Assay

Assay

Presence

These examples are YAML, because a config file is where “what does absent mean” is a question you actually have. The declaration is the same whatever format you hand it — swap parse(yaml:) for parse(json:) and nothing else about it changes.

@Schema(keys: .snakeCase, formats: .all)
struct Presence: Equatable {
var required: String // absent → an error
var optional: String? // absent → nil
var defaulted: Int = 3 // absent → 3, still validated
@Fallback(0) var salvaged: Int // absent OR invalid → 0, with a warning
@Ignore var notAField: String? = nil // never read from the document
}

Everything present:

required: here
optional: also here
defaulted: 9
salvaged: 5
Presence(required: "here", optional: Optional("also here"), defaulted: 9, salvaged: 5, notAField: nil)

Everything optional absent:

required: here
Presence(required: "here", optional: nil, defaulted: 3, salvaged: 0, notAField: nil)
warnings: fallback_applied

defaulted became 3 and salvaged became 0. Note the warning: a fallback that fires always tells you so, because a silently substituted value is how a data problem hides for a month.

The difference is what happens when the key is present and wrong.

required: here
salvaged: not a number
Presence(required: "here", optional: nil, defaulted: 3, salvaged: 0, notAField: nil)
warnings: fallback_applied

@Fallback took the value and recorded that it had. A = 0 default would have reported an error instead, because a default answers “absent”, not “invalid”.

Choose between them by asking whether a wrong value is survivable. For a score you display, it is. For a price you charge, it is not.

a: null
c: null
n.yaml: error: c must be an array, found null
1 error

null for an optional is nil — the document said so explicitly, and that is the same answer you wanted. null for an array with a default is an error, because null is not a list, and a default answers absence rather than a wrong type.

var x = 3 // no type annotation — the macro cannot see the type
let y: Int = 3 // `let` with a value — nothing could ever assign it

Both are hard errors, and both name the fix. They look like defaults and they are not, which is why you get told at build time rather than finding out from a document.