Skip to content
Merged

Cljs #11

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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,20 @@ jobs:
clojure -X:test:malli-${{ matrix.malli }}
;;
esac
Test-cljs:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install java
# ClojureScript's Closure compiler requires Java 21+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: 21
- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@4c7a6f613e5089821bb3bb2a33a3ee115578580d # 13.6.1
with:
cli: latest
- name: Run tests on ClojureScript (Node)
run: clojure -M:cljs-test
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ jobs:
with:
fetch-depth: 0

- name: Install java
# ClojureScript's Closure compiler (used by test-cljs) requires Java 21+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: 21

- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@4c7a6f613e5089821bb3bb2a33a3ee115578580d # 13.6.1
with:
Expand All @@ -26,4 +33,4 @@ jobs:
env:
CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }}
CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }}
run: clojure -T:build release :build/git-version "$(printf '"%s"' "$(git describe --tags)")" :deploy/only-jar-version-type :full-and-snapshot
run: clojure -T:build release :build/git-version "$(printf '"%s"' "$(git describe --tags)")" :deploy/only-jar-version-type :full-and-pre
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@

/tmp/*
!/tmp/.keep
/plans
/cljs-test-runner-out
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Changelog

## Unreleased

### Added

- ClojureScript support: the library is now `.cljc` and tested on Node
(`clojure -M:cljs-test`) as well as the JVM.
- `:verify-selection` accepts two new values besides `:throw`/`:assert` and
the `:skip`/`nil`/`false` opt-outs: `:log` (print a warning — stderr on
Clojure, `console.warn` on ClojureScript — and continue) and a function
(called with `{:paths ... :available ...}`, selection continues).
- The dynamic var `*verify-selection*` (initially `:throw`) provides the
default for the `:verify-selection` option; an explicitly passed option
still wins.

### Breaking

- An invalid selection now throws an `ExceptionInfo` instead of an `AssertionError`.
This makes it catchable via `(catch ExceptionInfo e ...)` and works in ClojureScript
(where asserts may be elided in release builds). The unknown and available paths are
available as data:
```clojure
(ex-data e)
;; => {:type :malli-select.core/unknown-paths
;; :data {:paths (...), :available (...)}}
```
If you were catching `AssertionError` or matching the assert message, update your code.
The preferred spelling of the `:verify-selection` option is now `:throw` (the default);
`:assert` still works, as do the `:skip`/`nil`/`false` opt-outs.

[Unreleased]: https://github.com/eval/malli-select/compare/v0.7.0...HEAD
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Clojars Project](https://img.shields.io/clojars/v/dk.thinkcreate/malli-select.svg?include_prereleases)](https://clojars.org/dk.thinkcreate/malli-select) [![cljdoc badge](https://cljdoc.org/badge/dk.thinkcreate/malli-select)](https://cljdoc.org/d/dk.thinkcreate/malli-select) [![Tests](https://github.com/eval/malli-select/actions/workflows/ci.yml/badge.svg)](https://github.com/eval/malli-select/actions/workflows/ci.yml)

Create subschemas of [malli](https://github.com/metosin/malli)-schemas using a spec2-inspired select notation.
Create subschemas of [malli](https://github.com/metosin/malli)-schemas using a spec2-inspired select notation. Works on Clojure and ClojureScript.

It's based on Rich Hickey's ideas from his talk ["Maybe Not"](https://youtu.be/YR5WdGrpoug?feature=shared&t=1965) about how [spec-alpha2](https://github.com/clojure/spec-alpha2) might allow for schema reuse.

Expand Down Expand Up @@ -34,7 +34,7 @@ user=> (p (ms/select Person [:name]))
[:vector
[:map
[:street {:optional true} string?]
[:country {:optional true} string?]]]]]
[:zip {:optional true} string?]]]]]

;; *if* any address is provided, it should at least have :street
user=> (p (ms/select Person [{:addresses [:street]}]))
Expand All @@ -44,13 +44,13 @@ user=> (p (ms/select Person [{:addresses [:street]}]))
[:addresses
{:optional true}
[:vector
[:map [:street string?] [:country {:optional true} string?]]]]]
[:map [:street string?] [:zip {:optional true} string?]]]]]

;; example valid data:
;; {}, {:addresses []}, {:addresses [{:street "Main"}]}
;;
;; example invalid data:
;; {:addresses nil}, {:addresses [{}]}, {:addresses [{:street "Foo" :country :se}]}
;; {:addresses nil}, {:addresses [{}]}, {:addresses [{:street "Foo" :zip 1234}]}

;; any address provided should be a full address
user=> (p (ms/select Person [{:addresses ['*]}]))
Expand All @@ -75,18 +75,35 @@ user=> (mg/generate (ms/select Person ^:only [:name]))

;; selecting something not contained in the schema:
user=> (ms/select Person [:a])
Execution error (AssertionError) at dk.thinkcreate.malli-select/select (malli_select.clj:175).
Assert failed: Selection contains unknown paths: ([:a])
Execution error (ExceptionInfo) at malli-select.core/-fail! (core.cljc:7).
:malli-select.core/unknown-paths {:paths ([:a]), :available ([:addresses :street] [:addresses :zip] [:addresses] [:age] [:name])}

Available:
([:addresses] [:age] [:name] [:addresses :street] [:addresses :zip])
;; the unknown and available paths are also in the ex-data:
user=> (ex-data *e)
{:type :malli-select.core/unknown-paths,
:data {:paths ([:a]),
:available ([:addresses :street] [:addresses :zip] [:addresses] [:age] [:name])}}

(empty? invalid-selection-paths)
;; bypass this check:
user=> (ms/select Person [:a] {:verify-selection false})
;; :verify-selection defaults to :throw (`:assert`, the pre-v0.8 spelling, still works);
;; :skip, nil and false disable the check.

;; other options: :log warns (stderr/console.warn) and continues...
user=> (ms/select Person [:a] {:verify-selection :log})
WARNING: :malli-select.core/unknown-paths {:paths ([:a]), :available (...)}
;; ...and a function gets the report, e.g.:
user=> (ms/select Person [:a] {:verify-selection #(log/warn "unknown paths" (:paths %))})

;; change the default via the dynamic var ms/*verify-selection*:
user=> (alter-var-root #'ms/*verify-selection* (constantly :log)) ;; CLJS: (set! ms/*verify-selection* :log)
```

See [the tests](./test/malli_select/core_test.clj) for more.
> [!NOTE]
> Before v0.8 an invalid selection threw an `AssertionError` instead of an
> `ExceptionInfo` — see the [CHANGELOG](CHANGELOG.md) if you were catching it.

See [the tests](./test/malli_select/core_test.cljc) for more.


## LICENSE
Expand Down
30 changes: 16 additions & 14 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ deploys `dk.thinkcreate/malli-select` to [Clojars](https://clojars.org/dk.thinkc
Local tags must be *signed* (`-s`):

```sh
# snapshot release (publishes X.Y.Z-SNAPSHOT)
# prerelease (publishes X.Y.Z-pre.1)
git tag -s vX.Y.Z-pre.1 -m "vX.Y.Z-pre.1"
git push origin vX.Y.Z-pre.1

Expand All @@ -30,29 +30,30 @@ signed but are attributed to your GitHub account.
The workflow runs on every push to `main` and on every tag:

```sh
clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-snapshot
clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-pre
```

`release` (see [build.clj](build.clj)) chains `test` → `build` → `deploy`.
`release` (see [build.clj](build.clj)) chains `test` → `test-cljs` → `build` → `deploy`.
The output of `git describe --tags` determines the version and whether the
built jar is actually deployed:

| `git describe --tags` | jar version | deployed? |
|-----------------------------------|-------------------|-------------------------------|
| `v1.2.3` (exact release tag) | `1.2.3` | yes — full release |
| `v1.2.3-pre.1` (pre-tag, or any commit after one) | `1.2.3-SNAPSHOT` | yes — snapshot |
| `v1.2.3-5-gabc123` (commits after a release tag) | `1.2.3-5-gabc123` | no — build only |
| `v1.2.3-pre.1` (exact pre-tag) | `1.2.3-pre.1` | yes — prerelease |
| any commits after a tag (e.g. `v1.2.3-5-gabc123`) | verbatim minus the `v` | no — build only |

Consequences:

- A full release requires an *exact* `vX.Y.Z` tag on the commit.
- After pushing a `vX.Y.Z-pre.N` tag, every subsequent push to `main`
re-publishes `X.Y.Z-SNAPSHOT` — until the next exact release tag.
- Ordinary pushes to `main` after a release tag act as a dry run:
- A (pre)release requires an *exact* tag on the commit.
- Prereleases are ordinary immutable releases — to publish another, tag
`vX.Y.Z-pre.N+1`. (They're exact versions so consumers — e.g. a
ClojureScript project dogfooding an upcoming release — can pin them.)
- Ordinary pushes to `main` act as a dry run:
tests run and the jar is built, but nothing is deployed.
- Pick pre-tag versions to match the *next* intended release, e.g. after
releasing `v0.7.0` the next pre-tag should be `v0.8.0-pre.1`.
- For a full release the POM's `<scm><tag>` is set to `vX.Y.Z`.
- For both release types the POM's `<scm><tag>` is set to the tag.

## Credentials

Expand All @@ -67,7 +68,7 @@ Consequences:
To rotate: create a new token on Clojars, then
`gh secret set CLOJARS_PASSWORD --repo eval/malli-select`, and delete the old
token. The cheapest end-to-end check of the credentials is publishing a
snapshot via a `-pre` tag (see TL;DR); the token's "last used" date on the
prerelease via a `-pre` tag (see TL;DR); the token's "last used" date on the
Clojars tokens page should update.

## Local release
Expand All @@ -76,12 +77,13 @@ The same can be done locally (e.g. when CI is down):

```sh
CLOJARS_USERNAME=... CLOJARS_PASSWORD=<deploy-token> \
clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-snapshot
clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-pre
```

## Gotchas

- `git describe --tags` needs the full history: the checkout step uses
`fetch-depth: 0` for this — keep it when touching the workflow.
- Tests run against the `:test` alias; a test failure aborts the release
before anything is built or deployed.
- Tests run on both the JVM (`:test` alias) and ClojureScript/Node
(`:cljs-test` alias); a failure in either suite aborts the release before
anything is built or deployed.
69 changes: 42 additions & 27 deletions build.clj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@
(update-keys (filter (comp #(= (name ns) %) namespace key) m)
(comp keyword name)))

(defn- runner-flags
"E.g. `(runner-flags \"test\" {:test/H true}) ;;=> {\"-H\" \"true\"}`"
[ns opts]
(-> (extract-keys-with-ns ns opts)
(update-keys (fn [k]
;; :H => "-H", :help => "--help"
(let [k (name k)]
(cond->> (str "-" k)
(> (count k) 1) (str "-")))))
(update-vals str)))

(defn ^#:fika{:examples [":test/d '\"some-dir\"'"
":test/n '\"some.namespace-test\"'"
"# see all runner options\n:test/H true"]}
Expand All @@ -39,14 +50,7 @@

Passing options to test-runner possible, see examples." [opts]
#_(prn :opts opts)
(let [test-options (extract-keys-with-ns "test" opts)
test-options (-> test-options
(update-keys (fn [k]
;; :H => "-H", :help => "--help"
(let [k (name k)]
(cond->> (str "-" k)
(> (count k) 1) (str "-")))))
(update-vals str))
(let [test-options (runner-flags "test" opts)
basis (b/create-basis {:aliases [:test]})
cmds (doto (b/java-command
{:basis basis
Expand All @@ -58,9 +62,25 @@
(when-not (zero? exit) (throw (ex-info "Tests failed" {}))))
opts)

(defn ^#:fika{:examples [":test-cljs/n '\"some.namespace-test\"'"
"# see all runner options\n:test-cljs/H true"]}
test-cljs
"Run all the tests on ClojureScript (Node).

Passing options to cljs-test-runner possible, see examples." [opts]
(let [test-options (runner-flags "test-cljs" opts)
basis (b/create-basis {:aliases [:cljs-test]})
cmds (b/java-command
{:basis basis
:main 'clojure.main
:main-args (reduce into ["-m" "cljs-test-runner.main"] test-options)})
{:keys [exit]} (b/process cmds)]
(when-not (zero? exit) (throw (ex-info "CLJS tests failed" {}))))
opts)

(b/java-command {:basis (b/create-basis {:aliases [:test]}) :main 'clojure.main})
(defn- pom-template [version version-type]
[[:description "spec2-inspired selection of Malli schemas"]
[[:description "Create subschemas of malli-schemas using a spec2-inspired select notation. Works on Clojure and ClojureScript."]
[:url "https://github.com/eval/malli-select"]
[:licenses
[:license
Expand All @@ -73,7 +93,7 @@
[:url "https://github.com/eval/malli-select"]
[:connection "scm:git:https://github.com/eval/malli-select.git"]
[:developerConnection "scm:git:ssh:git@github.com:eval/malli-select.git"]]
(= :exact version-type) (conj [:tag (str "v" version)]))])
(#{:exact :pre} version-type) (conj [:tag (str "v" version)]))])


(defn- jar-opts [{:keys [version version-type] :as opts}]
Expand All @@ -91,16 +111,11 @@
e.g. `v1.2.3`, `v1.2.3-pre.1` or `v1.2.3-1-g<sha>`.
Yields map with `version` and `type`."
[git-version]
(let [type (condp re-find git-version
#"^v\d+\.\d+\.\d+$" :exact
#"^v\d+\.\d+\.\d+-pre\.\d+" :pre ;; pre-tag and any commit after
:build)
exact-version (second (re-find #"v(\d+\.\d+\.\d+)" git-version))
version (case type
:exact exact-version
:pre (str exact-version "-SNAPSHOT")
:build (subs git-version 1))]
{:version version :version-type type}))
(let [type (condp re-find git-version
#"^v\d+\.\d+\.\d+$" :exact
#"^v\d+\.\d+\.\d+-pre\.\d+$" :pre
:build)]
{:version (subs git-version 1) :version-type type}))

(comment
(git-version->version&type "v1.2.3-123")
Expand Down Expand Up @@ -141,20 +156,20 @@
(defn
^#:fika{:option.only-jar-version-type
{:name "deploy/only-jar-version-type"
:desc "Deploy the built jar based on the type of version it has. One of :full (default, e.g. \"1.2.3\"), :full-and-snapshot (also jar-versions like \"1.2.3-SNAPSHOT\"), :all (any jar that was built)."}}
:desc "Deploy the built jar based on the type of version it has. One of :full (default, e.g. \"1.2.3\"), :full-and-pre (also prerelease versions like \"1.2.3-pre.1\"), :all (any jar that was built)."}}
deploy
"Deploy the built jar."
[{:deploy/keys [only-jar-version-type] :or {only-jar-version-type :full} :as opts}]
{:pre [(#{:full-and-snapshot :full :all} only-jar-version-type)]}
{:pre [(#{:full-and-pre :full :all} only-jar-version-type)]}
(let [{:keys [jar-file] :as opts} (jar-opts opts)
pom-file (b/pom-path (select-keys opts [:lib :class-dir]))
version (pom-path->version pom-file)
[v s] (re-find #"^\d+\.\d+\.\d+(-SNAPSHOT)?$" version)
[v pre] (re-find #"^\d+\.\d+\.\d+(-pre\.\d+)?$" version)
deploy? (or (= :all only-jar-version-type)
(and (= :full-and-snapshot only-jar-version-type) v)
(and (= :full-and-pre only-jar-version-type) v)
(and (= :full only-jar-version-type)
v
(not s)))]
(not pre)))]
(if deploy?
(do
;; guards against the write-pom patch (top of this file) silently losing effect
Expand All @@ -171,14 +186,14 @@
":build/git-version $(printf '\"%s\"' $(git describe --tags))"
":build/git-version '\"v1.2.3\"' :deploy/only-jar-version-type :full-and-snapshot"]

:options.from-commands '[test build deploy]}
:options.from-commands '[test test-cljs build deploy]}
release
"Test, build and deploy.

Deploys *only* when name of the jar built has the right format, see option `deploy/only-jar-version-type`."
[opts]
#_(prn :release-opts opts)
(deploy (build (test opts))))
(deploy (build (test-cljs (test opts)))))

(comment

Expand Down
6 changes: 6 additions & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
{:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:exec-fn cognitect.test-runner.api/test}

:cljs-test ;; run tests on Node: clojure -M:cljs-test
{:extra-paths ["test"]
:extra-deps {org.clojure/clojurescript {:mvn/version "1.12.42"}
olical/cljs-test-runner {:mvn/version "3.8.1"}}
:main-opts ["-m" "cljs-test-runner.main"]}
:perf {#_#_:extra-paths ["perf"]
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
org.clojure/clojure {:mvn/version "1.12.0"}
Expand Down
Loading