Module Conformist
Conformist is a library for creating and validating schemas. It provides run-time types without the use of ppx. It can be used to validate incoming data and to translate it to static types for safe usage.
Example
Let's start with an example. We have a static type that represents a user.
type occupation =
| Mathematician
| Engineer
type user =
{ occupation : occupation
; email : string
; birthday : Ptime.t
; nr_of_siblings : int
; comment : string option
; wants_premium : bool
}In order to create a conformist schema, we need a constructor that takes all the record fields and create a user.
let user occupation email birthday nr_of_siblings comment wants_premium =
{ occupation; email; birthday; nr_of_siblings; comment; wants_premium }
;;Now we can create a schema.
let occupation_decoder = function
| "mathematician" -> Ok Mathematician
| "engineer" -> Ok Engineer
| _ -> Error "Unknown occupation provided"
;;
let occupation_encoder = function
| Mathematician -> "mathematician"
| Engineer -> "engineer"
;;
let user_schema =
Conformist.(
make
Field.
[ custom
occupation_decoder
occupation_encoder
"occupation"
~meta:()
; string "email"
; date "birthday"
; int ~default:0 "nr_of_siblings"
; optional (string "comment")
; bool "wants_premium"
]
user)
;;Try to delete/swap lines of that list, to change the constructor or the user type. The code doesn't compile anymore!
user_schema showcases the creation of a custom type and optional types.
This is how you can decode a user given some input:
let user =
let input =
[ "occupation", [ "engineer" ]
; "email", [ "test@example.com" ]
; "birthday", [ "2020-12-01T00:00:00.00Z" ]
; "nr_of_siblings", [ "3" ]
; "comment", [ "hello" ]
; "wants_premium", [ "true" ]
]
in
Conformist.decode Schema.user_schema input
;;Decoding doesn't validate the data, it just makes sure that the types are correct and translates strings to the correct static types.
We can validate data based on each field's validators.
let validation_errors =
let input =
[ "occupation", [ "engineer" ]
; "email", [ "test@example.com" ]
; "birthday", [ "2020-12-01T00:00:00.00Z" ]
; "nr_of_siblings", [ "3" ]
; "comment", [ "hello" ]
; "wants_premium", [ "true" ]
]
in
Conformist.validate Schema.user_schema input
;;Note that if decoding of a field fails, validation fails as well since before a field is validated it gets decoded.
Use decode_and_validate to do both steps.
Fields
Every member of the list in the example is a field. Use the provided fold_left to traverse the list of fiels. Helper functions are provided that operate on fields.
module Field : sig ... endtype 'a decoder= string -> ('a, string) Stdlib.resultA
'a decodertries to turn a string into a value of type'a. It returns a descriptive errors message upon failure.
type 'a validator= 'a -> string optionA
'a validatortakes something of type'aand returns an error string if validation fails,Noneif everything is ok
val custom : 'a decoder -> 'a encoder -> ?default:'a -> ?type_:string -> ?meta:'b -> ?validator:'a validator -> string -> ('b, 'a) Field.tUse
custom decoder encoder ?default ?type_ ?meta ?validator field_nameto create a field with a custom type that is not supported out-of-the box. Provide a customdecoderwith a descriptive error message so conformist knows how to turn a string into your custom value.A string representation of the static
type_can also be provided, by default thefield_nameis taken.A
defaultvalue can be provided.
val optional : ?meta:'a -> ('b, 'c) Field.t -> ('a, 'c option) Field.toptional ?meta fieldturns afieldinto an optional field. If the field does not exist in the input data or if the associated value in the input data is an empty list, the value isNone. If the data is not provided in the input at all, no validation logic is executed.Example:
let make name address = { name; address } in let schema = Conformist.(make [ string "name"; optional (string "address") ] make) in (* Decoding fails *) let decoded = Conformist.decode schema [] in (* Validation fails *) let validated = Conformist.validate [] in (* Decoding succeeds, address is [None] *) let decoded = Conformist.decode schema [ "name", [ "Walter" ] ] in let decoded = Conformist.decode schema [ "name", [ "Walter" ]; "address", [] ] in (* Validation succeeds *) let validated = Conformist.validate [ "name", [ "Walter" ] ] in ()
val bool : ?default:bool -> ?meta:'a -> ?msg:string -> string -> ('a, bool) Field.tbool ?default ?meta ?msg field_namereturns a field with namefield_namethat decodes to abool.defaultis an optional default value for the field.metais optional meta data that is attached to the field. This is useful when implementing features on top of conformist.msgis the decode error message that is returned ifdecodefails.
val float : ?default:float -> ?meta:'a -> ?msg:string -> ?validator:float validator -> string -> ('a, float) Field.tfloat ?default ?meta ?msg ?validator field_namereturns a field with namefield_namethat decodes tofloat.defaultis an optional default value for the field.metais optional meta data that is attached to the field. This is useful when implementing features on top of conformist.msgis the decode error message that is returned ifdecodefails.validatoris an optional validator that is run when callingvalidate. By default, no validation logic is executed. This means that if a value decodes, it is valid.
val int : ?default:int -> ?meta:'a -> ?msg:string -> ?validator:int validator -> string -> ('a, int) Field.tint ?meta ?msg ?validator field_namereturns a field with namefield_namethat decodes toint.defaultis an optional default value for the field.metais optional meta data that is attached to the field. This is useful when implementing features on top of conformist.msgis the decode error message that is returned ifdecodefails.validatoris an optional validator that is run when callingvalidate. By default, no validation logic is executed. This means that if a value decodes, it is valid.
val string : ?default:string -> ?meta:'a -> ?validator:string validator -> string -> ('a, string) Field.tstring ?meta ?validator field_namereturn a field with namefield_namethat decodes tostring.defaultis an optional default value for the field.metais optional meta data that is attached to the field. This is useful when implementing features on top of conformist.msgis the decode error message that is returned ifdecodefails.validatoris an optional validator that is run when callingvalidate. By default, no validation logic is executed. This means that if a value decodes, it is valid.
val date : ?default:Ptime.date -> ?meta:'a -> ?msg:string -> ?validator:(int * int * int) validator -> string -> ('a, Ptime.date) Field.tDon't use
date, usedatetimeinstead.
val datetime : ?default:Ptime.t -> ?meta:'a -> ?msg:string -> ?validator:Ptime.t validator -> string -> ('a, Ptime.t) Field.tdatetime ?default ?meta ?validator field_namereturns a field with namefield_namethat decodes tostring.defaultis an optional default value for the field.metais optional meta data that is attached to the field. This is useful when implementing features on top of conformist.msgis the decode error message that is returned ifdecodefails.validatoris an optional validator that is run when callingvalidate. By default, no validation logic is executed. This means that if a value decodes, it is valid.
Schema
A schema is a list of fields. Input data can be decoded and validated using a schema.
val empty : ('a, unit, unit) temptycreates an empty schema.
val make : ('a, 'b, 'c) Field.list -> 'b -> ('a, 'b, 'c) tmake fields constructorcreate a schema.
val fold_left : f:('res -> 'meta Field.any_field -> 'res) -> init:'res -> ('meta, 'args, 'ty) t -> 'resfold_left ~f ~init schematraverses the list of fields ofschema. Use the functions inFieldto work with a generic field.
type error= string * string option * stringAn error
(field, value, error_message)is used to for decoding errors and validation errors.fieldis the field name of the input that failed to decode or validate,valueis the input value (if one was provided) anderror_messageis the decoding or validation error message.An empty list of
errormeans that the schema is valid.
type input= (string * string list) listThe
inputrepresents unsafe data that needs to be decoded and validated. This is typically some user input.
val decode : ('meta, 'ctor, 'ty) t -> input -> ('ty, error) Stdlib.resultdecode schema inputreturns the decoded value of type'tyby decoding theinputusing theschema.No validation logic is executed in this step.
val validate : ('meta, 'ctor, 'ty) t -> input -> error listvalidate schema inputreturns a list of validation errors by running the validators defined inschemaon theinputdata. An empty list implies that there are no validation errors and that the input is valid according to the schema.Note that
inputthat has no validation errors might still fail to decode, depending on the validation functions specified inschema.
val decode_and_validate : ('meta, 'ctor, 'ty) t -> input -> ('ty, error list) Result.tdecode_and_validate schema inputreturns the decoded and validated value of type'tyby decoding theinputusing theschemaand running its validators.Use
decode_and_validateto combine the functionsdecodeandvalidateand to either end up with the decoded value or all errors that happened during the decoding and validation steps.