Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 11 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,19 @@ on:
pull_request:
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- uses: purescript-contrib/setup-purescript@main
with:
purescript: "unstable"

- uses: actions/setup-node@v2
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "14.x"

- name: Install dependencies
run: |
npm install -g bower
npm install
bower install --production

- name: Build source
run: npm run-script build

- name: Run tests
run: |
bower install
npm run-script test --if-present
node-version: "24"
- run: npm install
- run: npm run build
- run: npm run build:example
- run: npm run example
- run: npm run format:check
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/.*
!/.gitignore
!/.github/
package-lock.json
!/.tidyrc.json
/package-lock.json
/spago.lock
/bower_components/
/node_modules/
/output/
10 changes: 10 additions & 0 deletions .tidyrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"importSort": "ide",
"importWrap": "source",
"indent": 2,
"operatorsFile": null,
"ribbon": 1,
"typeArrowPlacement": "first",
"unicode": "never",
"width": null
}
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [Unreleased](https://github.com/purescript/purescript-validation/compare/v6.0.1...HEAD)

Breaking changes:

Expand All @@ -12,9 +12,17 @@ Bugfixes:

Other improvements:

## [v6.0.1](https://github.com/purescript/purescript-validation/releases/tag/v6.0.1) - 2026-10-17

Other improvements:

- Migrate from Bower/Pulp to Spago, update CI and formatting, and restore the runnable example (#41 by @thomashoneyman).
- Use explicit case expressions for validation branches (#41 by @thomashoneyman).

## [v6.0.0](https://github.com/purescript/purescript-validation/releases/tag/v6.0.0) - 2022-04-27

Breaking changes:

- Update project and deps to PureScript v0.15.0 (#37 by @JordanMartinez)
- Drop deprecated `unV`; use `validation` instead (#38 by @JordanMartinez)

Expand All @@ -27,15 +35,17 @@ Other improvements:
## [v5.0.0](https://github.com/purescript/purescript-validation/releases/tag/v5.0.0) - 2021-02-26

Breaking changes:

- Added support for PureScript 0.14 and dropped support for all previous versions (#28)

New features:

Bugfixes:

Other improvements:

- Added detailed `Semigroup` validation examples (#17)
- Added link to to Applicative Validation chapter in the PureScript book (#29)
- Added link to to Applicative Validation chapter in the PureScript book (#29)
- Migrated CI to GitHub Actions and updated installation instructions to use Spago (#30)
- Renamed `unV` to `validation` and deprecated `unV` (#33)
- Added a changelog and pull request template (#34, #35)
Expand Down
32 changes: 0 additions & 32 deletions bower.json

This file was deleted.

17 changes: 17 additions & 0 deletions examples/spago.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package:
name: validation-examples
build:
strict: true
pedanticPackages: true
run:
main: Semigroup
dependencies:
- arrays: ">=7.0.0 <8.0.0"
- bifunctors: ">=6.0.0 <7.0.0"
- console: ">=6.0.0 <7.0.0"
- effect: ">=4.0.0 <5.0.0"
- newtype: ">=5.0.0 <6.0.0"
- ordered-collections: ">=3.0.0 <4.0.0"
- prelude: ">=6.0.0 <7.0.0"
- strings: ">=6.0.0 <7.0.0"
- validation: "*"
44 changes: 22 additions & 22 deletions examples/Semigroup.purs → examples/src/Semigroup.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ module Semigroup where

import Prelude

import Effect (Effect)
import Effect.Console (logShow)
import Data.Array.NonEmpty (NonEmptyArray)
import Data.Array.NonEmpty as NonEmpty
import Data.Bifunctor (bimap)
import Data.Eq.Generic (genericEq)
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Eq (genericEq)
import Data.Generic.Rep.Ord (genericCompare)
import Data.Generic.Rep.Show (genericShow)
import Data.Map (Map)
import Data.Map as Map
import Data.Newtype (class Newtype, over2)
import Data.Ord.Generic (genericCompare)
import Data.Show.Generic (genericShow)
import Data.String (length, null, toLower, toUpper)
import Data.Validation.Semigroup (V, invalid)
import Effect (Effect)
import Effect.Console (logShow)

-- | `UnvalidatedFormData` represents the raw data we might receive from a form
-- | before any validation has been performed.
Expand Down Expand Up @@ -46,7 +46,7 @@ type ValidatedFormData =
}

toString :: ValidatedFormData -> String
toString { username: (Username u)} = "{ username: '" <> u <> "', password: <not shown> }"
toString { username: (Username u) } = "{ username: '" <> u <> "', password: <not shown> }"

-- | `ValidationError` represents the potential errors we might encounter during
-- | the validation process.
Expand Down Expand Up @@ -166,10 +166,10 @@ instance showFormValidationErrors :: Show FormValidationErrors where
-- | Otherwise, it returns the input wrapped in the `Username` newtype to
-- | distinguish it from a normal, unvalidated `String`.
validateUsername :: String -> V FormValidationErrors Username
validateUsername input =
validateUsername input =
bimap (FormValidationErrors <<< Map.singleton InvalidUsername) Username
$ validateNonEmpty input
*> validateMinimumLength 4 input
$ validateNonEmpty input
*> validateMinimumLength 4 input

-- | This function validates that an input string conforms to our requirements
-- | for a valid password. Namely, we require that the input be non-empty, at
Expand All @@ -185,11 +185,11 @@ validateUsername input =
-- | Otherwise, it returns the input wrapped in the `Password` newtype to
-- | distinguish it from a normal, unvalidated `String`.
validatePassword :: String -> V FormValidationErrors Password
validatePassword input =
validatePassword input =
bimap (FormValidationErrors <<< Map.singleton InvalidPassword) Password
$ validateNonEmpty input
*> validateMinimumLength 6 input
*> validateMixedCase input
$ validateNonEmpty input
*> validateMinimumLength 6 input
*> validateMixedCase input

-- | This function validates that an `UnvalidatedFormData` record contains both
-- | a valid username and a valid password, per the requirements specified in
Expand All @@ -201,34 +201,34 @@ validatePassword input =
-- |
-- | Otherwise, it returns the validated fields in the `ValidatedFormData`
-- | record specified above.
validateForm
:: UnvalidatedFormData
validateForm
:: UnvalidatedFormData
-> V FormValidationErrors ValidatedFormData
validateForm {username, password} = {username: _, password: _}
validateForm { username, password } = { username: _, password: _ }
<$> validateUsername username
<*> validatePassword password

-- | This is a form that will fail validation, since both fields are empty
-- | strings.
emptyUsernameAndPassword :: UnvalidatedFormData
emptyUsernameAndPassword = {username: "", password: ""}
emptyUsernameAndPassword = { username: "", password: "" }

-- | This is a form that will fail validation, since both fields are too short.
shortUsernameAndPassword :: UnvalidatedFormData
shortUsernameAndPassword = {username: "foo", password: "bar"}
shortUsernameAndPassword = { username: "foo", password: "bar" }

-- | This is a form that will fail validation, since the password lowercase.
lowerCasePassword :: UnvalidatedFormData
lowerCasePassword = {username: "alice", password: "foobarbaz"}
lowerCasePassword = { username: "alice", password: "foobarbaz" }

-- | This is a form that will fail validation, since the password uppercase.
upperCasePassword :: UnvalidatedFormData
upperCasePassword = {username: "alice", password: "FOOBARBAZ"}
upperCasePassword = { username: "alice", password: "FOOBARBAZ" }

-- | This is a form that will pass validation, as it conforms to all the
-- | requirements outlined in the validation functions above.
goodForm :: UnvalidatedFormData
goodForm = {username: "alice", password: "FooBarBaz"}
goodForm = { username: "alice", password: "FooBarBaz" }

-- | Run through all of the example forms and print the validation results to
-- | the console.
Expand All @@ -253,4 +253,4 @@ main = do
-- > pure ("{\"username\":\"alice\",\"password\":\"FooBarBaz\"}")

where
printValidation = logShow <<< (map toString)
printValidation = logShow <<< (map toString)
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"private": true,
"scripts": {
"clean": "rimraf output && rimraf .pulp-cache",
"build": "pulp build -- --censor-lib --strict",
"build:example": "pulp build --include examples"
"build": "spago build",
"build:example": "spago build -p validation-examples",
"example": "spago run -p validation-examples",
"format": "purs-tidy format-in-place src examples/src && oxfmt --write .",
"format:check": "purs-tidy check src examples/src && oxfmt --check ."
},
"devDependencies": {
"pulp": "16.0.0-0",
"purescript-psa": "^0.8.2",
"rimraf": "^3.0.2"
"oxfmt": "^0.73.0",
"purescript": "^0.15.16",
"purs-tidy": "^0.11.1",
"spago": "^1.0.5"
}
}
20 changes: 20 additions & 0 deletions spago.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package:
name: validation
description: Applicative validation
build:
strict: true
pedanticPackages: true
publish:
version: 6.0.1
license: BSD-3-Clause
location:
githubOwner: purescript
githubRepo: purescript-validation
dependencies:
- bifunctors: ">=6.0.0 <7.0.0"
- control: ">=6.0.0 <7.0.0"
- either: ">=6.0.0 <7.0.0"
- foldable-traversable: ">=6.0.0 <7.0.0"
- newtype: ">=5.0.0 <6.0.0"
- prelude: ">=6.0.0 <7.0.0"
workspace: {}
21 changes: 12 additions & 9 deletions src/Data/Validation/Semigroup.purs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import Data.Bifunctor (class Bifunctor)
import Data.Either (Either(..))
import Data.Eq (class Eq1)
import Data.Foldable (class Foldable)
import Data.Newtype (class Newtype)
import Data.Ord (class Ord1)
import Data.Traversable (class Traversable)
import Data.Newtype (class Newtype)

-- | The `V` functor, used for applicative validation
-- |
Expand All @@ -46,17 +46,19 @@ derive instance newtypeV :: Newtype (V err result) _
-- | applied to the first function, if the validation succeeded the inner value
-- | is applied to the second function.
validation :: forall err result r. (err -> r) -> (result -> r) -> V err result -> r
validation f _ (V (Left err)) = f err
validation _ g (V (Right result)) = g result
validation f g (V value) = case value of
Left err -> f err
Right result -> g result

-- | Fail with a validation error.
invalid :: forall err result. err -> V err result
invalid = V <<< Left

-- | Test whether validation was successful or not.
isValid :: forall err result. V err result -> Boolean
isValid (V (Right _)) = true
isValid _ = false
isValid = case _ of
V (Right _) -> true
_ -> false

toEither :: forall err result. V err result -> Either err result
toEither (V e) = e
Expand Down Expand Up @@ -87,10 +89,11 @@ derive newtype instance functorV :: Functor (V err)
derive newtype instance bifunctorV :: Bifunctor V

instance applyV :: Semigroup err => Apply (V err) where
apply (V (Left err1)) (V (Left err2)) = V (Left (err1 <> err2))
apply (V (Left err)) _ = V (Left err)
apply _ (V (Left err)) = V (Left err)
apply (V (Right f)) (V (Right x)) = V (Right (f x))
apply left right = case left, right of
V (Left err1), V (Left err2) -> V (Left (err1 <> err2))
V (Left err), _ -> V (Left err)
_, V (Left err) -> V (Left err)
V (Right f), V (Right x) -> V (Right (f x))

instance applicativeV :: Semigroup err => Applicative (V err) where
pure = V <<< Right
Expand Down
Loading
Loading