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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Added

- `select-only`: explicit alternative to `^:only` metadata /
`{:prune-optionals true}`; forces pruning (also when the caller passes
`:prune-optionals false`).
- `:multi` schema support: branches are selected with a *map*-selection —
keys are dispatch values, values are sub-selections. `{:human [:name]}`
requires `:name` in branch `:human`; unmentioned branches become
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,16 @@ user=> (p (ms/select Person ['*]))


;; remove any optional attribute
user=> (p (ms/select Person [{:addresses ['*]}] {:prune-optionals true}))
;; or shorter:
user=> (p (ms/select Person ^:only [{:addresses ['*]}]))
user=> (p (ms/select-only Person [{:addresses ['*]}]))
;; example valid data:
;; {:name :not-a-string}
;;
;; Typically you'd use this to generate only specific data:
user=> (require '[malli.generator :as mg])
user=> (mg/generate (ms/select Person ^:only [:name]))
user=> (mg/generate (ms/select-only Person [:name]))
{:name "sNeLdUI5KtPw"}
;; also supported: (ms/select Person [:name] {:prune-optionals true})
;; and, via metadata: (ms/select Person ^:only [:name])

;; selecting something not contained in the schema:
user=> (ms/select Person [:a])
Expand Down Expand Up @@ -150,9 +150,9 @@ user=> (def Person [:map [:id :int] [:pet [:multi {:dispatch :kind}
[:cat [:map [:kind :keyword] [:lives :int]]]]]])
user=> (p (ms/select Person [:id {:pet {:dog [:breed]}}]))

;; pruning drops unmentioned branches (mentioning none keeps all),
;; select-only drops unmentioned branches (mentioning none keeps all),
;; e.g. to generate only humans:
user=> (mg/generate (ms/select Animal ^:only {:human [:name]}))
user=> (mg/generate (ms/select-only Animal {:human [:name]}))
{:name "x2Ep", :type :human}
```

Expand Down
6 changes: 4 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@

:test ;; added by neil
{:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}
org.clojure/test.check {:mvn/version "1.1.1"}}
: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"}}
olical/cljs-test-runner {:mvn/version "3.8.1"}
org.clojure/test.check {:mvn/version "1.1.1"}}
:main-opts ["-m" "cljs-test-runner.main"]}
:perf {#_#_:extra-paths ["perf"]
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
Expand Down
21 changes: 19 additions & 2 deletions src/malli_select/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@
- `:log` - print a warning (stderr on Clojure, `console.warn` on ClojureScript) and continue.
- a function - called with `{:paths ... :available ...}`, result ignored, selection continues.
- `:skip`, `false`, `nil` - don't verify.
- `prune-optionals` (`false` (default), `true`) - whether all fully optional subtrees should be removed from the resulting schema. Alternatively via metadata of selection: `^:only [:name]` (flag takes precedence over metadata).
- `prune-optionals` (`false` (default), `true`) - whether all fully optional subtrees should be removed from the resulting schema. Alternatively via metadata of selection: `^:only [:name]` (flag takes precedence over metadata). See also `select-only`, which forces this option.
Typically used when the selected schema is used for data generation.

Examples:
Expand All @@ -415,14 +415,31 @@
:prune-optionals prune-optionals))))


(defn select-only
"Like `select`, but the result contains only the selected attributes:
`:prune-optionals` is forced to `true` (a caller-supplied
`:prune-optionals false` is overridden). All other options (e.g.
`:verify-selection`) pass through unchanged.

Typically used to generate specific data:
```
(mg/generate (select-only Person [:name])) ;; => {:name \"...\"}
```
For `:multi` schemas, branches the selection does not mention are dropped."
([schema selection]
(select-only schema selection nil))
([schema selection options]
(select schema selection (assoc options :prune-optionals true))))


(defn selector
"Yields a function similar to `(partial ms/select schema)`.
A selector is faster when doing multiple selections from a schema as the schema is optionalized once.

Examples:
```
(let [person-selector (selector Person)]
(person-selector ^:only [:name]))
(person-selector [:name] {:prune-optionals true})) ;; what `select-only` does
```
"
[schema]
Expand Down
40 changes: 38 additions & 2 deletions test/malli_select/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
(:require
[clojure.pprint :refer [pprint]]
[clojure.test :as t :refer [deftest is testing]]
[malli-select.core :as sut :refer [select selector]]
[malli.core :as m]))
[malli-select.core :as sut :refer [select select-only selector]]
[malli.core :as m]
[malli.generator :as mg]))

(defonce ^:private ^:dynamic
*schema* nil)
Expand Down Expand Up @@ -371,6 +372,41 @@
(expect-selection-to-invalidate ^:only {:human [:name]} {:type :sized :size 1}
":sized-branch is dropped"))))

(deftest select-only-test
(let [Person [:map
[:name :string]
[:age :int]
[:addresses [:vector [:map [:street :string] [:zip :int]]]]]]
(testing "same result as select with pruning"
(is (= (m/form (select Person [:name] {:prune-optionals true}))
(m/form (select-only Person [:name]))))
(is (= (m/form (select Person ^:only [:name]))
(m/form (select-only Person [:name])))))

(testing ":prune-optionals is forced"
(is (= (m/form (select-only Person [:name]))
(m/form (select-only Person [:name] {:prune-optionals false})))))

(testing "other options pass through"
(let [ex-data (try
(select-only Person [:nope])
(catch #?(:clj clojure.lang.ExceptionInfo :cljs cljs.core/ExceptionInfo) e (ex-data e)))]
(is (= ::sut/unknown-paths (:type ex-data))))
(is (some? (select-only Person [:nope] {:verify-selection false}))))

(testing "multi: unmentioned branches are dropped"
;; the pruned dispatch key gets re-appended, hence :name before :type
(is (= [:multi {:dispatch :type}
[:human [:map [:name :string] [:type :keyword]]]]
(m/form (select-only [:multi {:dispatch :type}
[:human [:map [:type :keyword] [:name :string] [:age :int]]]
[:sized [:map [:type :keyword] [:size :int]]]]
{:human [:name]})))))

(testing "generated samples validate against the pruned schema"
(let [s (select-only Person [:name {:addresses [:street]}])]
(is (every? (partial m/validate s) (mg/sample s)))))))

(comment
(select [:map [:address [:map [:street string?]]]] [{:address [:street]}] {:prune-optionals true})

Expand Down