Coder Social home page Coder Social logo

oxidizing / conformist Goto Github PK

View Code? Open in Web Editor NEW
36.0 5.0 5.0 164 KB

Schema definition and validation with support for decoding to bridge the gap between runtime types and static types.

Home Page: https://v3.ocaml.org/p/conformist

License: MIT License

Makefile 1.85% OCaml 97.67% Nix 0.49%
ocaml schema-validation schema-definitions schema decoder

conformist's Introduction

Conformist

Schema definition and validation with support for decoding to bridge the gap between runtime types and static types.
Explore the docs »

Table of Contents

About

Conformist allows you to define schemas to decode, validate and sanitize input data declaratively. It comes with runtime types for primitive OCaml types such as int, string, bool, float and Ptime.t, option and custom types. Re-use business rules in validators and run it on the client side with js_of_ocaml. Arbitrary meta data can be stored in schemas which is useful to build functionality on top of conformist.

Typical use cases are enforcing invariants of models or user input sanitization.

In essence, conformist helps you to keep your runtime types/contracts in sync with your static types.

Installation

opam install conformist

In your dune file:

(executable
  (name app)
  (libraries
   ...
   conformist))

Usage

Let's look at an example.

type occupation =
  | Mathematician
  | Engineer

type user =
  { occupation : occupation
  ; email : string
  ; birthday : Ptime.t
  ; nr_of_siblings : int
  ; comment : string option
  ; favorite_shows : string list
  ; wants_premium : bool
  }

let user
    occupation
    email
    birthday
    nr_of_siblings
    comment
    favorite_shows
    wants_premium
  =
  { occupation
  ; email
  ; birthday
  ; nr_of_siblings
  ; comment
  ; favorite_shows
  ; wants_premium
  }
;;

let occupation_decoder = function
  | [ "mathematician" ] -> Ok Mathematician
  | [ "engineer" ] -> Ok Engineer
  | _ -> Error "Unknown occupation provided"
;;

let occupation_encoder = function
  | Mathematician -> [ "mathematician" ]
  | Engineer -> [ "engineer" ]
;;

(* This is for example purpose only. 
   Please use emile (https://github.com/dinosaure/emile) instead *)
let validate_email value =
  if String.index value '@' > 0 then None
  else Some "This doesn't look like an email"
  
let user_schema =
  Conformist.(
    make
      [ custom occupation_decoder occupation_encoder "occupation" ~meta:()
      ; string "email" ~validator: validate_email
      ; datetime "birthday"
      ; int ~default:0 "nr_of_siblings"
      ; optional (string "comment")
      ; list (string "favorite_shows")
      ; bool "wants_premium"
      ]
      user)
;;

let input =
  [ "occupation", [ "engineer" ]
  ; "email", [ "[email protected]" ]
  ; "birthday", [ "2020-12-01T00:00:00.00Z" ]
  ; "nr_of_siblings", [ "3" ]
  ; "comment", [ "hello" ]
  ; "favorite_shows", [ "Iron Man"; "Avengers" ]
  ; "wants_premium", [ "true" ]
  ]
;;

let user = Conformist.decode user_schema input
let validation_errors = Conformist.validate user_schema input

Try to delete/swap some lines of the list of fields, to change the constructor or the user type. The compiler forces you to keep these three things in sync.

Decoding doesn't validate the data, it just makes sure that the types are correct and translates strings to the correct static types.

Note that if decoding of a field fails, validation fails as well. Before a field is validated, it gets decoded.

Since we are shadowing the list [], dune warnings might fail compilation depending on the configuration. Suppress warning -40 can help.

Documentation

The documentation for the latest released version can be found here.

License

Copyright (c) 2020 Oxidizing Systems

Distributed under the MIT License. See LICENSE for more information.

Acknowledgements

The implementation of this project was inspired by archi and re-web.

conformist's People

Contributors

aronerben avatar joseferben avatar thangngoc89 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

conformist's Issues

Allow validation of tuples

Some values should be validated together, for example, for SMTP validation, either both a username and a password are given or neither. The situation where one is given but not the other should not happen. I propose the following construct:

let smtp_schema =
  let open Conformist in
  make
    [ optional (string "SMTP_USERNAME")
    ; optional (string "SMTP_PASSWORD")
    ]
    smtp_config
;;

becomes

let smtp_schema =
  let open Conformist in
  make
    [ optional (pair (string "SMTP_USERNAME") (string "SMTP_PASSWORD"))    ]
    smtp_config
;;

Now Conformist should make sure that either both SMTP_USERNAME and SMTP_PASSWORD are given or neither.

decode gets called multiple times (custom fields)

Running conformist returns an error but the data gets still validated as expected.

The example record has three fields, a key, a language and a translation. The following lines are showing the output of the decoder function with additionally added loggers:

[INFO] [application]: confirmation_subject
[INFO] [application]: Something: Key
[INFO] [application]: Something: Language
[INFO] [application]: Something: Translation
[INFO] [application]: Empty list: Key
[INFO] [application]: Undefined key
[INFO] [application]: Something: Key
[INFO] [application]: Empty list: Language
[INFO] [application]: Undefined language
[INFO] [application]: Something: Language
[INFO] [application]: Empty list: Translation
[INFO] [application]: Undefined translation
[INFO] [application]: Something: Translation

Here is the custom decoder function (the empty list results in the "Undefined ..." message):

let decoder create_fcn field l =
  let open CCResult in
  match l with
  | x :: _ ->
    Logs.info (fun m -> m "Something: %s" (Entity_message.show_field field));
    create_fcn x
  | [] ->
    Logs.info (fun m -> m "Empty list: %s" (Entity_message.show_field field));
    Error (Entity_message.Undefined field |> with_log_error ~level:Logs.Info)
;;

It seems that conformist runs as expected in the first attempt and then tries to look for each field again (some recursiveness issue?).

Supporting Lists

Conformist currently does not support lists.

Example:

let[@warning "-45"] pizza_schema
    : (unit, string -> string list -> t, t) Conformist.t
  =
  Conformist.(
    make
      Field.
        [ string
            ~validator:(fun name ->
              if String.length name > 12
              then Some "The name is too long, it has to be less than 20"
              else if String.equal "" name
              then Some "The name can not be empty"
              else None)
            "name"
        ; string list
            ~validator:(fun ingredients ->
              if List.length ingredients > 0
              then None
              else Some "Ingredients cannot be empty")
            "ingredients"
        ]
      create_pizza)
;;

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.