First, we’ll implement our models, which are simple and straightforward. Let’s create a class to store a single translation.
1
final case class Translation(lang: String, name: String)
Translation model
This method works, but we can definitely do better.
Using Option[String] is of no use because both fields have to be set. But a String can always be null and contain a lot of unexpected things (literally anything).
This is where refined types come to the rescue!
Refine types
Let’s define some refined types, which we can use later on. We need two things: a language code that obeys the restrictions of ISO-639-1, and a stronger definition for a product name. For the former, we use a regular expression, and for the latter, we simply expect a non-empty String.
12
type LanguageCode = String Refined MatchesRegex[W.`"^[a-z]{2}$"`.T]
type ProductName = String Refined NonEmpty
Refined types for models
Now, we can give our translation model another try.
1
final case class Translation(lang: LanguageCode, name: ProductName)
Translation model using refined types
Much better! While we’re at it, we can also write the JSON codecs using the refined module of the Circe library. We put them into the companion object of the model.