keuringsdienst: Data validation in Haskell made easy.

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Data validation in Haskell made easy and clean, with user rules that can be combined.


[Skip to Readme]

Properties

Versions 0.1.0.2, 0.1.0.3, 0.1.0.3, 0.1.0.4, 0.1.0.5, 0.1.1.0, 1.0.0.5
Change log ChangeLog.md
Dependencies aeson (<=2.2.0.0), base (>=4.5 && <5), containers (<=0.6.7), text (<=2.0.2) [details]
License GPL-3.0-only
Author Josep Bigorra
Maintainer Josep Bigorra
Category Validation, Data
Source repo head: git clone https://git.sr.ht/~teutonicjoe/keuringsdienst
Uploaded by jjba at 2023-08-11T09:38:45Z

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for keuringsdienst-0.1.0.3

[back to package description]

Keuringsdienst (van Waarde)

img img

If you know, you know. Data validation in Haskell that is composable, made easy and clean.

Licensed under the GNU General Public License v3 or later.

Data validation rules that are easy to write and serve as documentation of your data as well. Therefore, data validations SHOULD live next to your data models.

Context and motivation

There exist many data validation packages, in Haskell and other languages, but so far I have never found something that was flexible but powerful enough for my needs.

I based myself on Semigroup and Monoid operations, and attempted to make validation rules that are easy to write, read, compose and maintain.

See an example, from my music website WikiMusic.

validateEntity x =
  (identifier x) |?| isTextOfLength 36
  <> (displayName x) |?| (isNonEmptyText <> isTextSmallerThanOrEqual 120)

Imagine a simple data model (record) for a music artist:

data Artist = Artist
{ identifier :: Text,
  displayName :: Text,
  createdBy :: Text,
  visibilityStatus :: Int,
  approvedBy :: Maybe Text,
  createdAt :: UTCTime,
  lastEditedAt :: Maybe UTCTime,
  artworks :: Map Text ArtistArtwork,
  comments :: Map Text ArtistComment,
  opinions :: Map Text ArtistOpinion,
  spotifyUrl :: Maybe Text
}
deriving (Eq, Show, Generic)

Defining validations

Of course for all this to work, you need some imports:

import Keuringsdienst
import Keuringsdienst.Helpers

You can define a validation function for Artist by composing validation results and rules. Validation results are the result of applying rules to certain data and can be composed with <> since they are Monoid. Rules can also be composed (AND) with <> since they are also Monoid and can be OR'ed with the *||* operator, a.k.a ofDitOfDat.

validateArtist :: Artist -> ValidationResult
validateArtist x =
  (identifier x) |?| isTextOfLength 36
    <> (displayName x) |?| (isNonEmptyText <> isTextSmallerThanOrEqual 120)
    <> (createdBy x) |?| isTextOfLength 36
    <> (visibilityStatus x) |?| isPositiveOrZero
    <> (approvedBy x) |??| isTextOfLength 36
    <> (spotifyUrl x) |??| isNonEmptyText

What is a ValidationResult

type ErrMsg = Text

data Validation err
  = Success
  | Failure err
  deriving (Eq, Show, Generic, FromJSON, ToJSON)

type ValidationResult = Validation [ErrMsg]

Optics / Lenses

If you like Optics and lenses as I do, you are fully free to use it:

validateArtist :: Artist -> ValidationResult
validateArtist x =
  (x ^. #identifier) |?| isTextOfLength 36
    <> (x ^. #displayName) |?| (isNonEmptyText <> isTextSmallerThanOrEqual 120)
    <> (x ^. #createdBy) |?| isTextOfLength 36
    <> (x ^. #visibilityStatus) |?| isPositiveOrZero
    <> (x ^. #approvedBy) |??| isTextOfLength 36
    <> (x ^. #spotifyUrl) |??| isNonEmptyText