Declarative decodable object validation.
struct Client: Verifiable {
@Should(.notBeEmptyString)
var name: String?
init() {}
}
struct Basket: Verifiable {
@Must(.bePositiveInteger)
var amount: Int = 1
var client = Client()
init() {}
}
This library allows you to perform some simple validation on Decodable
objects as you decode them.
It is only suitable for simple, independant assertions, not for complex business rules.
Implement the Verifiable
protocol on your Decodable
object. Its only requirement is an empty initializer.
struct Client: Verifiable {
var name: String = "Name"
init() {}
}
Then decode it from JSON using decode(verify:from:)
:
let clientJson = #"{"name": "Toto"}"#.data(using: .utf8)!
let client = try? JSONDecoder().decode(verify: Client.self, from: clientJson)
If it works so far, you can start adding constraints ;)
Must
and Should
are property wrappers used to add validation rules to properties.
Wrapped fields marked with @Should(rule)
are optionals. They cannot contain values that do not abide by the associated rule.
Attempts to set an invalid value will set nil
instead.
Therefore, with if let
and similar constructs, you are assured to work with verified values.
Wraped fields marked with @Must(rule)
are implictly unwrapped, allowing you to directly work with values.
Attempts to set an invalid value will fail silently.
Unlike with Should
, decoding an invalid value will throw a VerificationError
.
A rule is a simple closure:
let bePositiveInteger: Rule<Int> = Rule { (int, test) in
int >= 0 ? test.pass() : test.fail("Expected positive integer")
}
Rules can be combined:
@Must(.beEmptyString | .beOfLength(3))
Or negated (in that case, the failure reason will be less explicit):
@Must(.not(.beEmptyString)) // use .notBeEmptyString instead
More may be added in the future.
Rules on integers:
bePositiveInteger
Rules on strings:
notBeEmptyString
notBeBlankString
(whitespace is blank space)beEmptyString
beBlankString
beOfLength(Int)
matchRegex(String)
To benefit from a nice, short syntax similar to that of the default set of rules, you should either:
- make your rules global functions
- make a static extension to
Rule
The library adds an extension to JSONDecoder
which sets a context in userInfo
.
The following methods are added:
decode(verify:from)
to decode a verifiable objectdecode(verifyStrict:from)
to decode and throw errors withShould
as well
It is possible to verify assignments in a block, using verify
and verifyStrict
(both Should
and Must
will throw with the latter):
try verify(myObject, myOtherObject) {
myObject.validatedField = "invalid value"
myOtherObject.validatedField = "some value"
}
This is needed because computed properties cannot throw.
A VerificationError.rulesBroken
is thrown when an invalid assignement is attempted.
Its associated values rules
is a list of Failure
s (stuct with a reason string and a path to the error).