clojure 规范 - 验证地图的内容

clojure spec - validating contents of maps

我想为地图创建一个 clojure 规范,其中包含关于特定键存在的规则。

地图必须有 :type,可以有 :default:value,但不能同时有。我试过了:

(s/def ::propertyDef
  (s/keys :req [::type (s/or ::default ::value) ] :opt [::description ::required]))

但是我得到了

CompilerException java.lang.AssertionError: Assert failed:
    spec/or expects k1 p1 k2 p2..., where ks are keywords
    (c/and (even? (count key-pred-forms)) (every? keyword? keys)),
    compiling:(C:\Users\MartinRoberts\AppData\Local\Temp\form-init4830956164341520551.clj:1:22) 

但是 or 给了我一个错误,因为它的格式不对。我不得不承认在 s/or.

的文档中并没有真正理解

首先:您正在使用 s/or 在所需键列表中指定 ::default::values/or 需要 :label spec 对,而您只给出规格本身,这就是错误的原因。

要解决,只需使用or代替:

(s/def ::propertyDef (s/keys :req [::type (or ::default ::value)]
                             :opt [::description ::required]))

这允许 ::default::value 都出现在地图中,但这几乎总是可以的。实际使用地图的代码可以简单地检查 ::value 的存在并使用它,如果它不存在,则使用 ::default (或者你的逻辑恰好是什么)。通常这样做:

(let [myvalue (or (::value mymap) (::default mymap))] ...)

地图中可能有数千个键,这不会影响您提取所需键的能力。这就是为什么规范不提供 built-in 方法来指定不应该在地图中的键,只提供指定哪些键 应该 存在的方法(即 :req:req-uns/keys 中)。想一想大多数 http 服务器的工作方式:您可以给它们无意义的 header 键和值,但它们不会拒绝为请求提供服务;他们只是忽略它们并 return 回应。

因此,您可能不需要强制只出现一个或另一个,但如果必须,您可以定义一个异或函数:

(defn xor
  [p q]
  (and (or p q)
       (not (and p q))))

然后将其作为附加谓词添加到规范中:

(s/def ::propertyDef (s/and (s/keys :req [::type (or ::default ::value)]
                                    :opt [::description ::required])
                            #(xor (::default %) (::value %))))

(s/valid? ::propertyDef {::type "type" ::default "default"})
=> true
(s/valid? ::propertyDef {::type "type" ::value "value"})
=> true
(s/valid? ::propertyDef {::type "type" ::default "default" ::value "value"})
=> false