Clojure spec.alpha 如何校验第二个参数的关键字必须存在于首个入参哈希映射
优化方案
你可以通过「拆分基础类型校验 + 命名跨参数校验谓词 + 可选自定义报错」的方式解决报错不友好的问题,这也是Clojure spec处理跨参数依赖校验的标准实现方式,比直接使用匿名函数可读性高很多。
完整实现示例
(ns my-example.core (:require [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as stest])) ;; 第一个参数的基础spec:键为keyword、值为string的map (s/def ::kw-string-map (s/map-of keyword? string?)) ;; 第二个参数的基础spec:元素为string或keyword的向量 (s/def ::mixed-str-kw-vec (s/coll-of (s/or :str string? :kw keyword?) :kind vector?)) ;; 命名的跨参数校验谓词:第二个向量中的所有keyword都存在于第一个map的键中 (defn all-kws-exist-in-map? [[m v]] (every? (fn [elem] (if (keyword? elem) (contains? m elem) true)) v)) ;; 可选:自定义报错逻辑,触发校验失败时直接返回不存在的关键字 (s/defexplain all-kws-exist-in-map? [path _ [m v] via in] (when-let [invalid-kws (filter #(and (keyword? %) (not (contains? m %))) v)] {:path path :pred 'all-kws-exist-in-map? :val invalid-kws :via via :in in :reason (format "向量中以下关键字不存在于第一个参数的Map中: %s" invalid-kws)})) ;; 组合参数spec (s/def ::my-example-args (s/and ;; 先用s/cat拆分两个参数,各自做基础校验,这部分报错是spec内置的,非常清晰 (s/cat :m ::kw-string-map :v ::mixed-str-kw-vec) ;; 基础校验通过后再做跨参数的存在性校验 all-kws-exist-in-map?)) (defn my-example [m v] nil) (s/fdef my-example :args ::my-example-args) ;; 测试instrument (stest/instrument `my-example)
优势说明
- 基础参数类型校验由spec内置能力实现,报错会明确提示是第一个参数类型不对还是第二个参数元素类型不对,不需要自己写额外逻辑
- 跨参数校验使用命名函数代替匿名函数,默认报错会直接显示函数名
all-kws-exist-in-map?校验失败,不会输出匿名函数的代码片段 - 增加自定义explain逻辑后,报错会直接输出不存在的关键字列表,定位问题效率更高
这个是目前Clojure生态中处理这类跨参数依赖校验的最优实践,没有更简化的原生实现方式。
内容的提问来源于stack exchange,提问作者link2name
相关产品推荐
相关产品推荐

