DEV Community

Toni
Toni

Posted on • Originally published at blog.tvaisanen.com on

Data Validation in Clojure

Malli is a data-driven schema Clojure library for defining types and validating data. It can be used for example to:

  • type check data when received from untrusted sources i.e. validate that HTTP request bodies before writing to the database

  • define domain entities as code and generate UML diagrams

  • generate clj-kondo types for static type checking in editors.

Malli uses hiccup-inspired vector syntax for defining types. There's also a map syntax variant but the use case for that is to be used as an internal library representation. The vector syntax looks like this.

  (def Todo
    [:map
     [:id :int]
     [:author :string]
     [:created inst?]
     [:status [:enum :todo :doing :done]]])

Enter fullscreen mode Exit fullscreen mode

Here we have a "Todo" schema that represents a map with properties: id, author, created, and status. Each child of the map vector defines a property, meaning the property :id is a type of :int and :created is a type of instant and so on. All of these properties can be validated separately.

(malli.core/validate :int 1)
;; => true
(malli.core/validate :int "1")
;; => false
(malli.core/validate inst? (java.time.Instant/now))
;; => true
(malli.core/validate [:enum :todo :doing :done] :done)
;; => true

Enter fullscreen mode Exit fullscreen mode

Or as a whole.

(malli.core/validate Todo
                    {:id 1 
                     :author "Toni" 
                     :created #inst "2023-09-30"
                     :status :todo})
;; => true

Enter fullscreen mode Exit fullscreen mode

In the case that the data is not valid we probably want to know the details on why so. For this, we can use malli.core/explain and malli.error/humanize.

(def invalid-todo
    {:id 1
     :author "Toni" 
     :created "not-an-instant"
     :status :todo})

(malli.core/validate Todo invalid-todo)
;; => false
(:errors (malli.core/explain Todo invalid-todo))
;; =>
({:path [:created],
  :in [:created],
  :schema inst?,
  :value "not-an-instant"})

(malli.error/humanize
  (malli.core/explain Todo invalid-todo))
;; => 
{:created ["should be an inst"]}

Enter fullscreen mode Exit fullscreen mode

The error messages can be customized and internationalized to fit your needs. Malli has a lot more features to offer, so I encourage you to go through the documentation to learn more.

Thank you for reading, I hope you found this useful. Please let me know if there's some specific Malli feature that you would like to know more about.

Top comments (0)