I need a spec for a map like {:a :b}
where :b
has to be a required key, but only when value of :a
is not nil
. Can someone help me with that?
user=> (s/def ::ab (s/and (s/keys :opt-un [::a ::b]) #(or (nil? (:a %)) (contains? % :b))))
:user/ab
user=> (s/valid? ::ab {:x 1})
true
user=> (s/valid? ::ab {:a nil})
true
user=> (s/valid? ::ab {:a 1})
false
user=> (s/valid? ::ab {:a 1 :b 2})
true
@ag how about that?If :a
should be required, use (s/keys :req-un [::a] :opt-un [::b])
I guess.
ah, I was playing around, come up with something like this:
(s/def ::a string?)
(s/def ::b string?)
(s/def ::base-map (s/keys :req-un [::a ::b]))
(s/def
:foo/bar
(s/merge
::base-map
(s/and
(fn [m]
(if (= (m :a) "bobo-included")
(contains? m :bobo)
true
)))))
there’s probably better way of doing this
Basically now :foo/bar is a spec for a map that must have :a
and :b
, keys, but when value of :a
is equal to “bobo-included”, it must have :bobo
, key, otherwise, :bobo
key is optionals/merge
is intended for two key specs -- I don't think you should use it for a key spec and a predicate.
And it looks like you have s/and
with just a single predicate there?
Now that you've restated the problem, it sounds like you want to look at multi-specs.
ah… yeah. let me try digging in there
Those are intended to select different specs based on some function of a value, in your case, the value of the :a
key.
well, yeah, I guess I didn’t exactly follow my own requirement, but the approach you showed me probably would work for me. I’m gonna refresh my memory on multi-specs anyway.
Thank you Sean!
Hello, I was playing a bit with spec 2 on an Advent of Code exercise and bumped into this case:
(def dam
{:firstname "Dam"
:age 35})
(s/def ::user (s/schema {:firstname string?
:lastname string?}))
(s/explain (s/select ::user [*])
dam)
;; => {:firstname "Dam", :age 35} - failed: (fn [m] (contains? m :lastname))
(s/explain (s/select {:firstname string?
:lastname string?}
[*])
dam)
;; => Success!
(s/explain (s/select [{:firstname string?
:lastname string?}]
[*])
dam)
;; => {:firstname "Dam", :age 35} - failed: (fn [m] (contains? m :lastname))
(s/explain (s/select [{:firstname string?}
{:lastname string?}]
[*])
dam)
;; => {:firstname "Dam", :age 35} - failed: (fn [m] (contains? m :lastname))
The second explain
is weird (considering https://github.com/clojure/spec-alpha2/wiki/Schema-and-select#literal-schemas).
Is there a subtle detail that I'm missing? (ping @alexmiller)
ThanksAlso, is there a specific reason not to instrument :ret
and :fn
in fdef
(even optionally) in spec 2?
I use https://github.com/jeaye/orchestra with spec 1 and find it very useful.
Also
(s/valid?
(s/schema {:foo string?})
{:foo 1})
=> false
(s/assert
(s/schema {:foo string?})
{:foo 1})
=> {:foo 1}
... weirdDunno, probably a bug
Ah ok. Thanks.
I get an exception when using a schema referring a spec with an indirection to another spec. It works as expected if I just use the spec. Am I doing anything wrong here or is it a bug?
(s/def ::thing string?)
(s/def ::other-thing ::thing)
(gen/sample (s/gen ::other-thing)) ;; => works as expected
(s/def ::foo (s/schema [::thing]))
(s/def ::bar (s/schema [::other-thing]))
(gen/sample (s/gen ::foo)) ;; => works as expected
(gen/sample (s/gen ::bar)) ;; => Exception below
No implementation of method: :conform* of protocol:
#'clojure.alpha.spec.protocols/Spec found for class:
clojure.lang.Keyword
this is a bug in spec 2, so won't work yet
Does spec2 let you write a spec for a qualified keyword such that its definition changes depending on the context? For example, if I were to spec Datomic's :db/id
, it would only ever be a nat-int?
when part of the result is from a pull query. When transacting to Datomic, :db/id
could be a lookup ref (e.g., (s/tuple keyword? some?)
), a nat-int?
, or a string?
. One could spec :db/id
using s/or
but that means everything that takes a db id as an input needs to handle all of those cases, which does not always make sense.
in short, no
in long, the general recommendation is to try to give attributes specs that truly reflect the data
within certain contexts, you can add on additional specs that narrow the scope if needed
By this do you mean you can add additional predicates to the :db/id
spec in different places?
yes, you could s/valid while s/and'ing in an additional narrower predicate for example
Oh cool. Is there an example of what that looks like?
just what I said?
Oh - that works if the :db/id
is taken as an argument itself. I was imagining a function that takes a map (perhaps nested) that has :db/id
s on the maps.
Or do you mean needing to write a predicate that walks that structure validating db/id against this new predicate?
you can still s/and a predicate to a map that checks a value in the map
it's also an option to just not spec :db/id