Swiftunwrap

Discover about Swift, iOS and architecture patterns

Self validating models

January 05, 2021

Sometimes in your project you might have objects requiring some validation prior being considered as valid such as IBAN or phone number. In the later we often need to ensure user input is a valid phone number. Failing in doing so (or not doing it) could make your app at best behaving strangely and at worst crashing.

We could at first just work with a simple String:

let iban: String = "Not valid"
````

However by doing so our IBAN might be (and is) invalid. We would also have to check it is actually correct every time we use it. Not doing so could make the app at best behaving strangely and at worst crashing.

To avoid such pitfalls we can use self validating models.

## Building a self validating model

First thing to do is to create an IBAN model. At first we'll consider everything data is valid:

swift struct Iban { let value: String

init(_ string: String) throws { self.value = string } }



Using `throws` we indicate that `init` might fail if input is invalid 💪. It now require us to be careful when trying to get a `Iban` instance:

swift do { let iban = try Iban("Not a valid IBAN") } catch {

}



We can't now work or ask an invalid `Iban` in our app without knowing about it 🎉. Well unless you have bugs in your algorithm that is 🤷‍♂️

## Testing

One great thing about self validating objects is how your tests become more explicit. Let's write some to see:

swift class IbanTests: XCTestCase {

func testinitwithValidIBANFormat__ItReturnAnIban() { let string = "FR1420041010050500013M02606"

XCTAssertNoThrows(try Iban(string)) }

func testinitlengthIsTooShort__IThrow() { let string = "FR142"

XCTAssertThrows(try Iban(string)) } }



Right now our second test will fail so let's just implement our algorithm:

swift struct Iban { let value: String

init(_ string: String) throws { self.value = try Self.validating(string) } }

extension Iban { private static func validating( string: String) throws -> String { // take a look at https://en.wikipedia.org/wiki/InternationalBankAccountNumber // if you ever wanted to implement a real IBAN algorithm let ibanLength = 23

guard string.length == ibanLength else { throw IbanParseError.invalidLength }

return string } }

enum IbanParseError: Error { case empty case invalidLength case invalidAlphaNumeric case unsupportedCountry case invalidChecksum }



Run your tests again and everything should work! One neat thing about self validating objects is Error. See our `IbanParseError`? By using it we make it pretty clear why our object might have failed in building. We can also make our tests even more explicit by checking for specific errors:

func testinitinvalidIbanLength__IThrow() { let string = "FR142"

XCTAssertThrowsSpecific(try Iban(string), IbanParseError.invalidLength) }



## Function validation

You might wonder "Why not testing `Iban.validating` itself?". The answer is simple: because it is not part of our public API. We could make it `public/internal` but there is no reason to. **That's the whole idea behind self validating object: you're asking for an object and just checking you can get it or not**. The validation is... implementation details 🤷‍♂️

## Conclusion

Self validating objects are nothing but simple. It is all about using objects to represent your app data and integrating the validation inside it instead of having it appart.

It might seem like a small change at first but if you try it you'll see that not only does it make your codebase safer but it also improve its readabili