From 63e22e85c69aca47e41b155932464d25b71d89c2 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Thu, 15 Oct 2026 18:04:06 +0000 Subject: [PATCH 1/7] Migrate package configuration and CI to Spago --- .github/workflows/ci.yml | 33 +++++++++++---------------------- .gitignore | 4 +++- .tidyrc.json | 10 ++++++++++ CHANGELOG.md | 10 +++------- bower.json | 32 -------------------------------- examples/spago.yaml | 17 +++++++++++++++++ package.json | 15 +++++++++------ spago.yaml | 20 ++++++++++++++++++++ 8 files changed, 73 insertions(+), 68 deletions(-) create mode 100644 .tidyrc.json delete mode 100644 bower.json create mode 100644 examples/spago.yaml create mode 100644 spago.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c69237a..0f3a6c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index 2c02232..afb2a45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ /.* !/.gitignore !/.github/ -package-lock.json +!/.tidyrc.json +/package-lock.json +/spago.lock /bower_components/ /node_modules/ /output/ diff --git a/.tidyrc.json b/.tidyrc.json new file mode 100644 index 0000000..9e39cf1 --- /dev/null +++ b/.tidyrc.json @@ -0,0 +1,10 @@ +{ + "importSort": "ide", + "importWrap": "source", + "indent": 2, + "operatorsFile": null, + "ribbon": 1, + "typeArrowPlacement": "first", + "unicode": "never", + "width": null +} diff --git a/CHANGELOG.md b/CHANGELOG.md index de3ecb5..53caec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,12 @@ 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] - -Breaking changes: - -New features: - -Bugfixes: +## [v6.0.1](https://github.com/purescript/purescript-validation/releases/tag/v6.0.1) Other improvements: +- Migrate from Bower/Pulp to Spago, update CI and formatting, and restore the runnable example. + ## [v6.0.0](https://github.com/purescript/purescript-validation/releases/tag/v6.0.0) - 2022-04-27 Breaking changes: diff --git a/bower.json b/bower.json deleted file mode 100644 index e90a82a..0000000 --- a/bower.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "purescript-validation", - "homepage": "https://github.com/purescript/purescript-validation", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-validation.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "dependencies": { - "purescript-bifunctors": "^6.0.0", - "purescript-control": "^6.0.0", - "purescript-either": "^6.0.0", - "purescript-foldable-traversable": "^6.0.0", - "purescript-newtype": "^5.0.0", - "purescript-prelude": "^6.0.0" - }, - "devDependencies": { - "purescript-console": "^6.0.0", - "purescript-ordered-collections": "^3.0.0", - "purescript-psci-support": "^6.0.0", - "purescript-strings": "^6.0.0" - } -} diff --git a/examples/spago.yaml b/examples/spago.yaml new file mode 100644 index 0000000..b7cb6dd --- /dev/null +++ b/examples/spago.yaml @@ -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: "*" diff --git a/package.json b/package.json index e8e8435..c8cac5f 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/spago.yaml b/spago.yaml new file mode 100644 index 0000000..15a83d7 --- /dev/null +++ b/spago.yaml @@ -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: {} From 4e512914268b0066ccbe33b0c3501e454d337890 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Thu, 15 Oct 2026 18:05:35 +0000 Subject: [PATCH 2/7] Restore the example in a nested Spago package --- examples/{ => src}/Semigroup.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename examples/{ => src}/Semigroup.purs (98%) diff --git a/examples/Semigroup.purs b/examples/src/Semigroup.purs similarity index 98% rename from examples/Semigroup.purs rename to examples/src/Semigroup.purs index b080eaa..403c653 100644 --- a/examples/Semigroup.purs +++ b/examples/src/Semigroup.purs @@ -8,9 +8,9 @@ import Data.Array.NonEmpty (NonEmptyArray) import Data.Array.NonEmpty as NonEmpty import Data.Bifunctor (bimap) 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.Eq.Generic (genericEq) +import Data.Ord.Generic (genericCompare) +import Data.Show.Generic (genericShow) import Data.Map (Map) import Data.Map as Map import Data.Newtype (class Newtype, over2) From dd6ff4a5212df08c3a407390c7969112537c7973 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Thu, 15 Oct 2026 18:05:46 +0000 Subject: [PATCH 3/7] Format PureScript and repository files --- CHANGELOG.md | 5 +++- examples/src/Semigroup.purs | 44 +++++++++++++++--------------- src/Data/Validation/Semigroup.purs | 2 +- src/Data/Validation/Semiring.purs | 4 +-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53caec3..61fede9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Other improvements: ## [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) @@ -23,6 +24,7 @@ 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: @@ -30,8 +32,9 @@ 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) diff --git a/examples/src/Semigroup.purs b/examples/src/Semigroup.purs index 403c653..cff6bc8 100644 --- a/examples/src/Semigroup.purs +++ b/examples/src/Semigroup.purs @@ -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.Generic.Rep (class Generic) import Data.Eq.Generic (genericEq) -import Data.Ord.Generic (genericCompare) -import Data.Show.Generic (genericShow) +import Data.Generic.Rep (class Generic) 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. @@ -46,7 +46,7 @@ type ValidatedFormData = } toString :: ValidatedFormData -> String -toString { username: (Username u)} = "{ username: '" <> u <> "', password: }" +toString { username: (Username u) } = "{ username: '" <> u <> "', password: }" -- | `ValidationError` represents the potential errors we might encounter during -- | the validation process. @@ -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 @@ -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 @@ -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. @@ -253,4 +253,4 @@ main = do -- > pure ("{\"username\":\"alice\",\"password\":\"FooBarBaz\"}") where - printValidation = logShow <<< (map toString) + printValidation = logShow <<< (map toString) diff --git a/src/Data/Validation/Semigroup.purs b/src/Data/Validation/Semigroup.purs index 9e46aaa..2546851 100644 --- a/src/Data/Validation/Semigroup.purs +++ b/src/Data/Validation/Semigroup.purs @@ -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 -- | diff --git a/src/Data/Validation/Semiring.purs b/src/Data/Validation/Semiring.purs index 84c0b4e..1d01c46 100644 --- a/src/Data/Validation/Semiring.purs +++ b/src/Data/Validation/Semiring.purs @@ -19,9 +19,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 alternative validation -- | @@ -87,7 +87,7 @@ derive newtype instance functorV :: Functor (V err) derive newtype instance bifunctorV :: Bifunctor V -instance applyV :: Semiring err => Apply (V err) where +instance applyV :: Semiring 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) From 395df2f906a1d85166f0eb0c12b8cfc0ef98fc3b Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Thu, 15 Oct 2026 18:10:44 +0000 Subject: [PATCH 4/7] Credit the migration pull request --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61fede9..c55b5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Notable changes to this project are documented in this file. The format is based Other improvements: -- Migrate from Bower/Pulp to Spago, update CI and formatting, and restore the runnable example. +- Migrate from Bower/Pulp to Spago, update CI and formatting, and restore the runnable example (#41 by @thomashoneyman). ## [v6.0.0](https://github.com/purescript/purescript-validation/releases/tag/v6.0.0) - 2022-04-27 From 8f6ff2092f2dbc6785673c15e8c9cdac0f89f9c7 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Thu, 15 Oct 2026 21:26:22 +0000 Subject: [PATCH 5/7] Prepare changelog release date and unreleased section --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c55b5d7..f3be4df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,17 @@ 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). -## [v6.0.1](https://github.com/purescript/purescript-validation/releases/tag/v6.0.1) +## [Unreleased] + +Breaking changes: + +New features: + +Bugfixes: + +Other improvements: + +## [v6.0.1](https://github.com/purescript/purescript-validation/releases/tag/v6.0.1) - 2026-10-17 Other improvements: From a858046da3f136fe9f0cd5a73cee5f602ae7489e Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Sat, 17 Oct 2026 13:05:06 +0000 Subject: [PATCH 6/7] Use explicit case expressions for validation branches --- src/Data/Validation/Semigroup.purs | 19 +++++++++++-------- src/Data/Validation/Semiring.purs | 26 +++++++++++++++----------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Data/Validation/Semigroup.purs b/src/Data/Validation/Semigroup.purs index 2546851..a2cb149 100644 --- a/src/Data/Validation/Semigroup.purs +++ b/src/Data/Validation/Semigroup.purs @@ -46,8 +46,9 @@ 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 @@ -55,8 +56,9 @@ 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 @@ -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 diff --git a/src/Data/Validation/Semiring.purs b/src/Data/Validation/Semiring.purs index 1d01c46..366bbbf 100644 --- a/src/Data/Validation/Semiring.purs +++ b/src/Data/Validation/Semiring.purs @@ -47,8 +47,9 @@ 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 @@ -56,8 +57,9 @@ 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 @@ -88,10 +90,11 @@ derive newtype instance functorV :: Functor (V err) derive newtype instance bifunctorV :: Bifunctor V instance applyV :: Semiring 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 :: Semiring err => Applicative (V err) where pure = V <<< Right @@ -103,9 +106,10 @@ instance monoidV :: (Semiring err, Monoid a) => Monoid (V err a) where mempty = pure mempty instance altV :: Semiring err => Alt (V err) where - alt (V (Left err1)) (V (Left err2)) = V (Left (err1 + err2)) - alt (V (Left _)) a = a - alt (V (Right a)) _ = V (Right a) + alt left right = case left, right of + V (Left err1), V (Left err2) -> V (Left (err1 + err2)) + V (Left _), a -> a + V (Right a), _ -> V (Right a) instance plusV :: Semiring err => Plus (V err) where empty = V (Left zero) From aadb7d74b77210ba9abb9a19e7be055a2af45f38 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Sat, 17 Oct 2026 16:29:24 +0000 Subject: [PATCH 7/7] Correct release changelog coverage and Unreleased link --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3be4df..5853660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: @@ -17,6 +17,7 @@ Other improvements: 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