Home

Awesome

Validate

codecov GitHub Actions Workflow Status GitHub Release GitHub License

Overview

validate is an OCaml library designed to streamline the process of validating records, lists, or values. It primarily operates through a PPX deriver that automatically generates validators using annotations, utilizing an underlying library of helper validation functions.

Prerequisites

Installation

Installing validate

To install the validate library, use OPAM with the following command:

opam install validate

After installation, you need to add validate as a library in your project's dune file and specify validate.ppx_derive_validate as a preprocessor. Here is an example of how to set up the dune file:

(library
  (name something)
  (preprocess (pps validate.ppx_derive_validate))
  (libraries validate))

Annotations and Usage

The validate library in OCaml allows for precise validation of data through a variety of annotations. For each type, the library generates a function named validate_[type_name] which can be used to perform the validation.

Example Usage

Validating record

type test_record = {
  min : string; [@min_length 2]
  max : string; [@max_length 5]
  numeric_list : (int [@less_than 10]) list; [@min_length 2] 
  ...
}
[@@deriving validate, show, eq]

let example_record = { min = "ab"; max = "hello"; numeric_list = [1, 2, 3] }
let validation_result = validate_test_record example_record

In this example:

Validating simple types

type list_type = ((string[@min_length 1]) list[@min_length 2]) [@@deriving validate]

let example_list = ["a"; "bc"]
let validation_result = validate_list_type example_list

In this example, validate_list_type function will validate that each string in the list has a minimum length of 1 and that the list itself has a minimum length of 2.

Validating tuples

type tuple_type = (string[@email]) * (int[@greater_than 1]) [@@deriving validate]

let example_tuple = ("example@email.com", 2)
let validation_result = validate_tuple_type example_tuple

Here, the validate_tuple_type function ensures the first element of the tuple is a valid email address and the second element is an integer greater than 1.

Validating variants

Variants in OCaml can also be validated using validate. Here's an example of how to use annotations with variants:

type tuple_variant =
  | EmailToId of (string[@email]) * (int[@greater_than_or_equal 0])
  | Email of (string[@email])
  | Profile of {
    username : string [@min_length 3];
    email : string [@email];
  }
[@@deriving validate]

(* Example usage *)
let email_to_id_variant = EmailToId ("example@email.com", 0)
let validation_result = validate_tuple_variant email_to_id_variant

In this example:

Validating Recursive Types

validate also supports recursive types, allowing for the validation of nested, self-referential data structures. Here is an example demonstrating the validation of a recursive type representing a binary tree:

type tree =
  | Leaf of (int[@greater_than 0])
  | Node of { left : tree; [@dive] right : (tree[@dive]) }
[@@deriving validate, show, eq]

(* Example usage *)
let my_tree = Node { left = Leaf 1; right = Leaf 2 }
let validation_result = validate_tree my_tree

In this example:

Validating Circular Recursive Types

validate also handles circular recursive types, which are useful for defining structures where two types refer to each other recursively. This feature is particularly useful for complex data models. Here's an example:

type a = { 
  a_id : int; [@greater_than 0] 
  b : (b[@dive]) option 
}
[@@deriving validate, show, eq]

and b = { 
  b_id : int; [@greater_than 0] 
  a : (a[@dive]) option 
}
[@@deriving validate, show, eq]

(* Example usage *)
let rec a_instance = { a_id = 1; b = Some { b_id = 2; a = Some a_instance } }
let validation_result_a = validate_a a_instance

let rec b_instance = { b_id = 1; a = Some { a_id = 2; b = Some b_instance } }
let validation_result_b = validate_b b_instance

In this example:

Categorized Annotations

String/List Annotations

String Annotations

Integer/Float Annotations

Annotations for Other Types

Option Type Annotations

Advanced Annotations

let custom_validator str =
  if String.length str > 1 then Ok ()
  else Error (Validate.BaseError { code = "custom_validator"; params = [] })

type custom_validator_record = {
  custom_validator : string; [@custom custom_validator]
  custom_inline_validator : int;
      [@custom
        fun i ->
          if i > 1 then Ok ()
          else
            Error
              (Validate.BaseError { code = "custom_validator"; params = [] })]
}
[@@deriving validate]
type cond_record = {
  unit : string;
  temperature : int; [@greater_than_or_equal 0] [@ignore_if fun r -> r.unit <> "K"]
}
[@@deriving validate]
type username_or_email = {
  username : string option; [@some_if fun r -> r.email = None]
  email : string option; [@none_if fun r -> Option.is_some r.username]
}
[@@deriving validate]

Error Handling

In validate, the validation function returns a result type, which includes an Ok value equal to the input parameter, or an error parameter. The error types are defined as follows:

type base_validation_error = { 
  code : string; 
  params : (string * string) list 
}
type keyed_validation_errors = string * validation_error list
type index_validation_error = int * validation_error list
type validation_error =
  | BaseError of base_validation_error
  | KeyedError of keyed_validation_errors list
  | IterableError of index_validation_error list
  | GroupError of validation_error list

Contributing

Contributions to validate are warmly welcomed and appreciated. Whether it's opening issues for bug reports, suggesting new features, or submitting pull requests, all forms of contribution help in making validate better.