clojure 规范 - 验证地图的内容

mme*_*mer 2 clojure clojure.spec

我想为地图创建一个 clojure 规范,该地图具有有关特定键存在的规则。

地图必须有一个:type并且可以有一个:default:value两个但不能同时有。我试过:

(s/def ::propertyDef
  (s/keys :req [::type (s/or ::default ::value) ] :opt [::description ::required]))
Run Code Online (Sandbox Code Playgroud)

但我得到了

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) 
Run Code Online (Sandbox Code Playgroud)

但是or给了我一个错误,因为它的格式错误。我不得不承认并没有真正理解s/or.

Jos*_*osh 6

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

要解决,只需使用or

(s/def ::propertyDef (s/keys :req [::type (or ::default ::value)]
                             :opt [::description ::required]))
Run Code Online (Sandbox Code Playgroud)

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

(let [myvalue (or (::value mymap) (::default mymap))] ...)
Run Code Online (Sandbox Code Playgroud)

映射中可能有数千个键,这不会影响您提取所需键的能力。这就是为什么 spec 没有提供一种内置的方法来指定不应该出现在地图中的键,而只提供了指定哪些键应该存在的方法(即,:req:req-unin s/keys)。想一想大多数 http 服务器是如何工作的:您可以为它们提供无意义的标头键和值,但它们不会拒绝为请求提供服务;他们只是忽略它们并返回响应。

因此,您可能不需要强制只存在一个或另一个,但如果必须,您可以定义一个exclusive or 函数:

(defn xor
  [p q]
  (and (or p q)
       (not (and p q))))
Run Code Online (Sandbox Code Playgroud)

然后将其添加为规范的附加谓词:

(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
Run Code Online (Sandbox Code Playgroud)