Giter VIP home page Giter VIP logo

heckle's Introduction

heckle

The Clojure(Script) validation library you deserve.

Heckle is a flexible, function-oriented, and composable validations library for Clojure and ClojureScript.

Build Status Clojars Project

Why another validation library?

Clojure has several existing validation libraries, but as I looked around I was stunned to see how inflexibly they were designed. Many only support working with hash-maps. Many only support validating one piece of data in isolation (making it hard to validate things like a "Password confirmation" field). I couldn't find a validations library that elegantly addressed all of my validation needs, so I built one.

Usage

Let's dive right in with a quick example:

(require '[heckle.core :refer [validate]]
         '[heckle.validations :as v])

(def login-validations
  [(v/matches #".@." :email)
   (v/matches #"[a-z]" :password "must include a lower-case letter")
   (v/matches #"[A-Z]" :password "must include a capital letter")
   (v/matches #"\d" :password "must include a number")
   (v/length-is-at-least 8 :password)])

(validate login-validations {:email "[email protected]" :password "MyPa55word"})
  ; => {}

(validate login-validations {:email "me" :password "passwd"})
  ; => {:email #{"is invalid"}
  ;     :password #{"must include a capital letter"
  ;                 "must include a number"
  ;                 "must be at least 8 characters"}}

As you can see, heckle.core/validate take a list of "validations" and some data, and returns a hash-map of errors. The errors is a hash-map where the key is the invalid field and the value is a list of error messages. When everything is okay, the errors hash-map is empty.

All the hard work is handled by the validation functions you provide. The validation functions have a very simple interface: they take the data to validate as their only argument, and they return error information in the form of [error-key error-message] if the data fails the validation, or nil if the data passes.

The simplicity of the validation functions is the key difference between Heckle and other validation libraries. Since they receive the entire input data, they can check one field or many fields at will. The error key they return is independent from the data its validating.

Heckle ships with many standard validation functions built-in, and it's easy to define your own.

Built-in validation function builders

Heckle comes with functions to build your own validation functions. The heckle.validations namespace contains builder functions for validationg hash-map input data. Example usage:

(heckle.core/validate
  [(heckle.validations/is-present :email)
   (heckle.validations/is-confirmed :email)
   (heckle.validations/is-at-least 18 :age "is too young")]
  {:email "[email protected]" :age 30}) ; => {}
Fn name Description Arguments Default error message
is-present If value is a string, ensures it is not blank. If value is not a string, ensures it is not nil. key - the key whose value we will check
error-msg - (optional) custom error message when invalid
"is required"
matches Ensure a string matches against a regular expression regex - the regular expression
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
"is invalid"
is-one-of Ensure a value is one of a list of values collection - the set of permissible values
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be either A, B or C"
is-confirmed Ensure a value has been confirmed accurately (the value and its confirmation are equal) key - the key whose value we will check is confirmed
confirmation-key - (optional) the name of the key of the confirmation value
error-msg - (optional) custom error message when invalid
"does not match"
length-is-at-least Ensure a string or other sequence is at least the given length min-length - the minimum permissible length
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be at least 8 characters"
length-is-no-more-than Ensure a string or other sequence is at most the given length max-length - the maximum permissible length
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be less than 25 characters"
is-at-least Ensure a value is greater than or equal to a minimum value bound - the minimum permissible value
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be at least 3"
is-greater-than Ensure a value is strictly greater than a given value bound - the value above which valid values must be
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be greater than 2"
is-less-than Ensure a value is strictly less than a given value bound - the value below which valid values must be
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be less than 10"
is-no-more-than-than Ensure a value is less than or equal to a maximum value bound - the maximum permissible value
key - the key whose value we will check
error-msg - (optional) custom error message when invalid
e.g. "must be no more than 9"

Building your own validation functions

If none of the built-in validation functions work for you, it's easy to write your own validation functions. You can just write a your own validation from scratch (it's very easy!), or you can use Heckle's helper functions make-claim and make-denial.

Completely custom validations

A validation is just a function that accepts the input data as an argument and returns nil if everything is okay, or error information in the form [error-key error-message] if there's an error.

Here are some completely custom validation functions you can use with validate:

(def sign-up-validations
  [(fn [data] (when (empty? (:email data)) [:email "is required"]))
   (fn [data] (when-not (re-find #"[a-z]" (:password data)) [:password "must contain at least one lowercase letter"]))
   (fn [data] (when-not (re-find #"[A-Z]" (:password data)) [:password "must contain at least one capital letter"]))
   (fn [data] (when-not (re-find #"\d" (:password data)) [:password "must contain at least one number"]))])

(heckle.core/validate sign-up-validations {:email "" :password "pass"})
  ; => {:email #{"is required"}
  ;     :password #{"must conatin at least one capital letter"
  ;                 "must contain at least one number"}}

Using make-claim and make-denial

It's even easier to write custom validation functions using two helper functions make-claim and make-denial. These functions both take a predicate function, the error key and the error message. make-claim expects the predicate to return a truthy value when there is no error, and make-denial expects the predicate to return a falsey value when there is no error.

Let's see the same example as above, but using these helper functions:

(def sign-up-validations
  [(heckle.core/make-denial #(empty? (:email %1)) :email "is required")
   (heckle.core/make-claim #(re-find #"[a-z]" (:password %1)) :password "must contain at least one lowercase letter")
   (heckle.core/make-claim #(re-find #"[A-Z]" (:password %1)) :password "must contain at least one capital letter")
   (heckle.core/make-claim #(re-find #"\d" (:password %1)) :password "must contain at least one number")])

(heckle.core/validate sign-up-validations {:email "" :password "pass"})
  ; => {:email #{"is required"}
  ;     :password #{"must conatin at least one capital letter"
  ;                 "must contain at least one number"}}

Short-circuiting validations with validation groups

Normally, Heckle will run every validation you give it. This is good if you want your user to know everything they need to fix. But sometimes it's preferable to stop early if you encounter an error and skip subsequent validations.

For this, Heckle provides the function heck.core/group. This function takes a list of validation functions and returns a validation function that lazily evaluates the given validations until one returns an error or all have passed.

(def sign-up-validations
  [(heckle.core/group
     (heckle.validations/is-present :email)
     (heckle.validations/matches #".@." :email))
   (heckle.core/group
     (heckle.validations/is-present :password)
     (heckle.validations/length-is-at-least 8 :password)
     (heckle.core/group
       (heckle.validations/matches #"[A-Z]" :password "must include a capital letter")
       (heckle.validations/matches #"[a-z]" :password "must include a lower-case letter")
       (heckle.validations/matches #"\d" :password "must include a number")))])

(heckle.core/validate sign-up-validations {:email "" :password ""})
  ; => {:email #{"is required"}
  ;     :password #{"is required"}}
(heckle.core/validate sign-up-validations {:email "[email protected]" :password "pass"})
  ; => {:password #{"must be at least 8 characters" "must include a capital letter"}}

As you can see, groups can be nested as much as you want. That's because groups are just validation functions themselves that execute other validation functions. Functional composition ftw!

This is useful for a number of use cases:

  • You want at most only 1 error message per field
  • You want at most only 1 error message entirely
  • Certain validations are dependent on other validations having passed (like if a validation function will raise an error if given nil or a datetime check will raise if the input string doesn't parse)
  • Certain validations are expensive to compute and you'd rather skip them if something else is wrong anyway (e.g. they involve a network call)

Roadmap

  • Add collection validations
  • Add a syntax shortcut for setting up multiple validations for the same field
  • Allow customization of the default error messages
  • Support records in addition to hash-maps for built-in validation functions
  • Add data type validations?

License

Copyright © 2018 Nathan Wallace

Distributed under the MIT License.

heckle's People

Contributors

nwallace avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.