What's the easiest way to do something like a "soft cut" e.g. core logic conda to lock a regex spec into validating an optional argument with a specific spec if it meets some condition, and failing otherwise?
Suppose I have a spec like this:
(s/def ::my-map
(s/keys :opt-un [...]))
(s/def ::my-spec
(s/cat :my-map (s/? ::my-map) :options (s/* (s/cat :k keyword? :v any?))))
If I pass in a map that fails the ::my-map spec I get an error like
identity - failed: Extra input in: [0] spec: ::my-spec
because it then tries to use that argument as an option. How can I force it to validate maps with the ::my-map spec?
I've tried stuff like (s/& map? ::my-map) but that doesn't work eitherjust checking - you have an optional followed by kv pairs, and with what input do you get this error? something that's neither a map or keyword as first arg?
I'm not sure if there's an easy alternative for you - seems like you could possibly treat this as two specs based on even/odd arg count. it's an unusual usage but you could probably build an s/multi that switched based on that and used different specs
I ended up doing something like
(s/&
(s/cat :my-map (s/? map?) :options (s/* (s/cat :k keyword? :v any?)))
(s/keys :opt-un [:my-map])
which works okay I guess but is a little icky I thinkHere's a more detailed example including something with neither a map nor keyword first arg. I just get an extra input error
(s/def ::k
integer?)
(s/def ::my-map
(s/keys :req-un [::k]))
(s/def ::my-spec
(s/cat :my-map (s/? ::my-map) :options (s/* (s/cat :k keyword? :v any?))))
(s/explain-str ::my-spec [{:k 100}])
(s/explain-str ::my-spec [{:k 100} :x :y])
;; => "Success!\n"
(s/explain-str ::my-spec [100])
;; => "(100) - failed: Extra input in: [0] spec: ::my-spec\n"
(s/explain-str ::my-spec [{}])
;; => "({}) - failed: Extra input in: [0] spec: ::my-spec\n"defmulti is actually one real-world example of where you have a usage like this. The metadata attribute map is optional and after that it supports dispatch-fn as a positional argument and then a few optional key-value arguments like :default and :hierarchy if I recall correctly.
This came up for me because I was working on a def macro that did something similar (supports and optional attribute map and keyword options) and I tried to tweak the spec to validate one of the keys in the attribute map specifically and it made things fail with confusing errors. In the example above I want the last failure to tell me it's failing because :k is not an integer?
This is the working version I came up with:
(s/def ::my-spec
(s/& (s/cat :my-map (s/? map?) :options (s/* (s/cat :k keyword? :v any?)))
(s/keys :opt-un [::my-map])))
(s/explain-str ::my-spec [{}])
=> "{} - failed: (contains? % :k) in: [:my-map] at: [:my-map] spec: ::my-map\n"
I was wondering if maybe there was some easier way to do this, e.g. some way to define my so-called "soft cut" inline rather than having to wrap the whole thing in s/&If I were to start using Spec today is it recommended to use Spec 1 or Spec 2? Also, my codebase is Clojure 1.8 and can't upgrade. The only issue I see with this is that ident? is missing but that's easy enough to add. Is there any strong reasons to avoid using Spec with Clojure versions before 1.9?
spec 2 has not been released and is not under active dev at this moment
I think someone did have a backport of spec at one point (we are not maintaining anything official though)
Excellent, thank you for the super fast response Alex! 🙂