我理解你的感受,因为一旦我进入 Spec,它就会让我产生同样的想法。真正帮助我解决我心中的问题的是考虑到 Spec 不是最终的库,而是一个框架。在我的项目中,通常我有一个特殊的模块,其中包含高于基本规范功能的高级包装器。我相信,您可能会这样做:定义一个函数,该函数接受数据、规范并根据您的业务逻辑提出您希望拥有的错误消息。这是我的代码的一个小例子:
(ns project.spec
(:require [clojure.spec.alpha :as s]))
;; it's better to define that value is a constant
(def invalid :clojure.spec.alpha/invalid)
(defn validate
"Either returns coerced data or nil in case of error."
[spec value]
(let [result (s/conform spec value)]
(if (= result invalid)
nil
result)))
(defn spec-error
"Returns an error map for data structure that does not fit spec."
[spec data]
(s/explain-data spec data))
现在,让我们准备一些规格:
(defn x-integer? [x]
(if (integer? x)
x
(if (string? x)
(try
(Integer/parseInt x)
(catch Exception e
invalid))
invalid)))
(def ->int (s/conformer x-integer?))
(s/def :opt.visits/fromDate ->int)
(s/def :opt.visits/toDate ->int)
(s/def :opt.visits/country string?)
(s/def :opt.visits/toDistance ->int)
(s/def :opt.visits/params
(s/keys :opt-un [:opt.visits/fromDate
:opt.visits/toDate
:opt.visits/country
:opt.visits/toDistance]))
以下是一些使用示例:
(let [spec :opt.visits/params
data {:some :map :goes :here}]
(if-let [cleaned-data (validate spec data)]
;; cleaned-data has values coerced from strings to integers,
;; quite useful for POST parameters
(positive-logic cleaned-data)
;; error values holds a map that describes an error
(let [error (spec-error spec data)]
(error-logic-goes-here error))))
这里可能需要改进的是拥有一个兼具validate
和功能的组合error
功能。这样的函数可以返回两个值的向量:成功标志和结果或错误数据结构,如下所示:
[true {:foo 42}] ;; good result
[false {:error :map}] ;; bad result
Spec 库没有规定单一的数据处理方式。这就是为什么它非常好和灵活。