Skip to content

Commit

Permalink
fix s/merge with any? param + un-ns options (#30)
Browse files Browse the repository at this point in the history
* fix s/merge with any? param + ns close option
 : previously supported, even tho it's an invalid s/merge argument 
* un-ns options
* bump to 2.x
  • Loading branch information
mpenet authored Jan 25, 2024
1 parent d13fd87 commit 7edb3f3
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 51 deletions.
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ merged with the internal registry at coerce time.

```clj
(s/def ::foo keyword?)
(c/coerce ::foo "bar" {::c/idents {::foo (fn [x opts] (str "keyword:" x))}}) -> "keyword:bar"
(c/coerce ::foo "bar" {:idents {::foo (fn [x opts] (str "keyword:" x))}}) -> "keyword:bar"
```

Coercers are functions of 2 args, the value, and the options coerce
Expand All @@ -60,19 +60,19 @@ The typical example would be :
```clj
(s/def ::foo (s/coll-of keyword?))
;; we'll namespace all keywords in that coll-of
(c/coerce ::foo ["a" "b"] {::c/idents {`keyword? (fn [x opts] (keyword "foo" x)})}) -> [foo/a foo/b]
(c/coerce ::foo ["a" "b"] {:idents {`keyword? (fn [x opts] (keyword "foo" x))}}) -> [foo/a foo/b]
```

You can specify multiple overrides per coerce call.

Another thing we added is the ability to reach and change the
behaviour of coercer generators via ::c/forms, essentially allowing
behavior of coercer generators via :forms, essentially allowing
you to support any spec form like inst-in, coll-of, .... You could
easily for instance generate open-api definitions using these.

```clj
(c/coerce ::foo (s/coll-of keyword?)
{::c/forms {`s/coll-of (fn [[_ spec]] (fn [x opts] (do-something-crazy-with-spec+the-value spec x opts)))}})
{:forms {`s/coll-of (fn [[_ spec]] (fn [x opts] (do-something-crazy-with-spec+the-value spec x opts)))}})
```

### Closed maps
Expand Down Expand Up @@ -169,7 +169,7 @@ Learn by example:
(c/coerce-structure {::number "42"
::not-defined "bla"
:sub {::odd-number "45"}}
{::c/idents {::not-defined `keyword?
{:idents {::not-defined `keyword?
; => {::number 42
; ::not-defined :bla
; :sub {::odd-number 45}}
Expand All @@ -182,7 +182,7 @@ Learn by example:
; Custom registered keywords always takes precedence over inference
(c/coerce ::my-custom-attr "Z") ; => #user.SomeClass{:x "Z"}

(c/coerce ::my-custom-attr "Z") {::c/idents {::my-custom-attr keyword}}) ; => :Z
(c/coerce ::my-custom-attr "Z") {:idents {::my-custom-attr keyword}}) ; => :Z
```

Examples from predicate to coerced value:
Expand Down Expand Up @@ -259,10 +259,10 @@ Examples from predicate to coerced value:
(c/conform ::number "40") ; 40

;; Throw on coerce structure
(c/coerce-structure {::number "42"} {::c/op c/coerce!})
(c/coerce-structure {::number "42"} {:op c/coerce!})

;; Conform on coerce structure
(c/coerce-structure {::number "42"} {::c/op c/conform})
(c/coerce-structure {::number "42"} {:op c/conform})
```

## Caching
Expand All @@ -278,11 +278,10 @@ about this, for instance when you define static coercers via
during development you might need to be aware of the existence of that
cache (ex if you defined a bugged coercer, or while doing REPL dev).

In any case you can turn off the cache by passing
`:exoscale.coax/cache false` to the options of
coerce/conform/coerce-structure, alternatively you can manually fiddle
with the cache under `exoscale.coax/coercer-cache`, for instance via
`(reset! exoscale.coax/coercer-cache {})`.
In any case you can turn off the cache by passing `:cache false` to the options
of coerce/conform/coerce-structure, alternatively you can manually fiddle with
the cache under `exoscale.coax/coercer-cache`, for instance via `(reset!
exoscale.coax/coercer-cache {})`.

## License

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.3-SNAPSHOT
2.0.0-SNAPSHOT
47 changes: 19 additions & 28 deletions src/exoscale/coax.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,20 @@

(defn gen-coerce-merge
[[_ & spec-forms]]
(fn [x {:as opts :keys [closed]}]
(fn [x opts]
(if (map? x)
(into (if closed {} x)
(map (fn [spec-form]
(coerce spec-form
x
(assoc opts :closed true))))
spec-forms)
(reduce (fn [m spec-form]
;; for every spec-form coerce to new value;
;; we need to compare key by key what changed so that
;; defaults do not overwrite coerced values
(into m
(keep (fn [[spec v]]
;; new-val doesn't match default, keep it
(when-not (= (get x spec) v)
[spec v])))
(coerce spec-form x (assoc opts :closed true))))
x
spec-forms)
:exoscale.coax/invalid)))

(defn gen-coerce-nilable
Expand Down Expand Up @@ -265,15 +271,11 @@
indicates a spec form likely it will return it's generated coercer
from registry :exoscale.coax/forms, otherwise it returns the
identity coercer"
[spec {:as opts :exoscale.coax/keys [enums]}]
[spec {:as _opts :keys [idents forms enums]}]
(let [spec-exp (si/spec-root spec)
{:as reg :exoscale.coax/keys [idents]} (-> @registry-ref
(update :exoscale.coax/idents
merge
(:exoscale.coax/idents opts))
(update :exoscale.coax/forms
merge
(:exoscale.coax/forms opts))
(update :exoscale.coax/idents merge idents)
(update :exoscale.coax/forms merge forms)
(cond-> enums
(assoc :exoscale.coax/enums enums)))]
(or (cond (qualified-ident? spec-exp)
Expand All @@ -292,7 +294,7 @@
"Get the coercing function from a given key. First it tries to lookup
the coercion on the registry, otherwise try to infer from the
specs. In case nothing is found, identity function is returned."
[spec {:exoscale.coax/keys [idents] :as opts}]
[spec {:keys [idents] :as opts}]
(or (when (qualified-keyword? spec)
(si/registry-lookup (merge (:exoscale.coax/idents @registry-ref)
idents)
Expand All @@ -315,7 +317,7 @@

(defn coerce-fn
[spec opts]
(if (:exoscale.coax/cache opts true)
(if (:cache opts true)
(cached-coerce-fn spec opts)
(coerce-fn* spec opts)))

Expand Down Expand Up @@ -415,7 +417,7 @@
(defn coerce-structure
"Recursively coerce map values on a structure."
([x] (coerce-structure x {}))
([x {:exoscale.coax/keys [idents op]
([x {:keys [idents op]
:or {op coerce}
:as opts}]
(walk/prewalk (fn [x]
Expand All @@ -427,14 +429,3 @@
[k (op (get idents k k) v opts)]
[k v]))))))
x)))

(s/def ::foo string?)
(s/def ::bar string?)
(s/def ::z string?)

(s/def ::m (s/keys :req-un [::foo ::bar]))

(coerce ::m {:foo "f" :bar "b" :baz "x"} {:closed true}) ; baz is not on the spec

(coerce `(s/merge ::m (s/keys :req-un [::z]))
{:foo "f" :bar "b" :baz "x" :z "z"} {:closed true}) ; baz is not on the spec
18 changes: 9 additions & 9 deletions test/exoscale/coax_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
(is (= (sc/coerce ::infer-nilable nil) nil))
(is (= (sc/coerce ::infer-nilable "") ""))
(is (= (sc/coerce ::nilable-int "10") 10))
(is (= (sc/coerce ::nilable-int "10" {::sc/idents {`int? (fn [x _] (keyword x))}}) :10))
(is (= (sc/coerce ::nilable-int "10" {:idents {`int? (fn [x _] (keyword x))}}) :10))
(is (= (sc/coerce ::nilable-pos-int "10") 10))

(is (= (sc/coerce ::nilable-string nil) nil))
Expand Down Expand Up @@ -287,13 +287,13 @@
::not-defined "bla"
:unqualified 12
:sub {::infer-int "42"}}
{::sc/idents {::not-defined `keyword?}})
{:idents {::not-defined `keyword?}})
{::some-coercion 321
::not-defined :bla
:unqualified 12
:sub {::infer-int 42}}))
(is (= (sc/coerce-structure {::or-example "321"}
{::sc/op sc/conform})
{:op sc/conform})
{::or-example [:int 321]}))

(defrecord SomeRec [a])
Expand Down Expand Up @@ -344,22 +344,22 @@
::legs ["7" "7"]
:foo "bar"
:name "john"}
{::sc/idents
{:idents
{::head c/to-long
::leg c/to-long
::name c/to-keyword}}))
"Coerce with option form")
(is (= 1 (sc/coerce `string? "1" {::sc/idents {`string? c/to-long}}))
(is (= 1 (sc/coerce `string? "1" {:idents {`string? c/to-long}}))
"overrides works on qualified-idents")

(is (= [1] (sc/coerce `(s/coll-of string?) ["1"]
{::sc/idents {`string? c/to-long}}))
{:idents {`string? c/to-long}}))
"overrides works on qualified-idents, also with composites")

(is (= ["foo" "bar" "baz"]
(sc/coerce `vector?
"foo,bar,baz"
{::sc/idents {`vector? (fn [x _] (str/split x #"[,]"))}}))
{:idents {`vector? (fn [x _] (str/split x #"[,]"))}}))
"override on real world use case with vector?")))

(s/def ::foo int?)
Expand Down Expand Up @@ -399,7 +399,7 @@
(s/def ::merge (s/merge (s/keys :req-un [::foo])
::unqualified
;; TODO: add s/multi-spec test
))
any?))
(is (= {:foo 1 :bar "1" :c {:a 2}}
(sc/coerce ::merge {:foo "1" :bar 1 :c {:a 2}}))
"Coerce new vals appropriately")
Expand Down Expand Up @@ -430,7 +430,7 @@
(is (= {::x :y/quux}
(sc/coerce ::mm
{::x "quux"}
{::sc/cache? false}))))
{:cache? false}))))

(def d :kw)
;; no vars in cljs
Expand Down

0 comments on commit 7edb3f3

Please sign in to comment.