Fork me on GitHub
Franco Gasperino03:05:54

How do i model a spec def to validate the value of an optional key in a map? Assuming the :c keyword and associated value are intended to be optional:

(spec/def ::acceptable-value? 
    (spec/and int? pos-int?))
  (spec/def ::a? ::acceptable-value?)
  (spec/def ::b? ::acceptable-value?)
  (spec/def ::c? ::acceptable-value?)

  (spec/def ::a ::a?)
  (spec/def ::b ::b?)
  (spec/def ::c ::c?)

  (spec/def ::acceptable-map? 
     (spec/map-of keyword? ::acceptable-value?)
     (spec/keys :req-un [::a ::b ::c])))

  (def my-map {:a 1 :b 2})
  (spec/valid? ::acceptable-map? my-map) => false
  (spec/explain ::acceptable-map? my-map) => {:a 1, :b 2} - failed: (contains? % :c) spec: ...


(require '[clojure.spec.alpha :as s])
(s/def ::c pos-int?)
(s/valid? (s/keys :opt-un [::c]) {}) ;; true
(s/valid? (s/keys :opt-un [::c]) {:c 3}) ;; true
(s/valid? (s/keys :opt-un [::c]) {:c "3"})  ;; false


the secret sauce you were missing was :opt-un


the docstring of s/keys didn't have quite the information i was expecting. > Creates and returns a map validating spec. :req and :opt are both > vectors of namespaced-qualified keywords. The validator will ensure > the :req keys are present. The :opt keys serve as documentation and > may be used by the generator.

Franco Gasperino03:05:44

that's the secret sauce. works as expected.

David Pham06:05:21

I am sorry if I should ask this on ask.Clojure or not, but what would be the potential disadvantage of using clojure.core.match, except for the potential slower speed?


two things i know of. 1) i believe it uses exception throwing as part of its backtracking. i thought i remembered reading somewhere that creating lots of exceptions can have some cost to it. 2) it's susceptible to creating code that is too large for a single method on jvms. The bad part of this is that it rears its head when you've gone down that path and add a few more cases and can be tricky because there's not much you can do