Prototype of an experimental validations library

Hi all, I’ve been playing with PureScript to build a data validation library. Specifically, I wanted a library that gave me all the ergonomics of a dynamic language, but type-safe (of course). After brainstorming my ideal API design, I couldn’t think of a language that it would work in besides PureScript!

Suppose our schema looks like this:

type Student = { studentId :: Int, name :: String, age :: Int }
type School  = { name :: String
               , age :: Int
               , students :: Array Student
               }

Suppose each student needs to be at least 10 years old, there needs to be at least 5 students, and both the school and the students’ names must be non-empty. Then I want to be able to create a validation object that looks like this:

studentValidator = { name: [ notEmpty ], age: [ atLeast 10 ] }
schoolValidator = { name: [ notEmpty ]
                  , students: [ lengthAtLeast 5
                              , each studentValidator 
                              ]
                  }

where each validation rule is a simple function, like

notEmpty s = if s == "" then FieldError "can't be empty" else Ok

and then run validate schoolValidator mySchool to get an error object back out that mirrors the schema of the validator.

I’ve spent the last few days trying to figure out the row type machinery well enough to make this work. I have a really, really bad and ugly prototype currently at here. There’s still some big warts with the ergonomics related to type specialization, and I’m sure my code is really redundant and inelegant since I basically got it to work by adding more and more class constraints until the typechecker stopped complaining. Also, I need to do way more testing, so there may be horrible bugs still. Nevertheless, I thought you guys might be interested - I don’t think any other static language has a flexible enough type system to do this without using macros or upcasting everything to Objects.

2 Likes

The data that you’re validating is JSON, is that right?

Your idea is very good, and there have been other attempts to solve this same problem.

It seems to me like you might be able accomplish a lot of this same stuff by making special newtypes and writing DecodeJson class instances for your newtypes? In that scenario, the decodeJson function would be your validate function.

You might also be able to accomplish something similar by writing parsers for the F monad. In that scenario your validate function would be runExcept. The classic essay on this technique is Parse, don’t validate.

2 Likes