完整代码示例
(ns api.foo
(:require [clojure.spec.alpha :as s]))
(s/def ::common-email (s/and string? (partial re-matches #"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,63}$")))
;(s/def :foo/email (s/and ::common-email))
(s/def :foo/email ::common-email)
(s/def :db/foo (s/keys :req [:foo/email]))
(comment
(->> (s/explain-data :db/foo {:foo/email "bar"})
::s/problems))
*问题:*
{:path [:foo/email],
:pred (clojure.core/partial clojure.core/re-matches #"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,63}$"),
:val "bar",
:via [:db/foo :api.foo/common-email],
:in [:foo/email]})
*粗略的解决方案*
但是,如果我用`(s/def :foo/email (s/and ::common-email))`代替它,则返回
{:path [:foo/email],
:pred (clojure.core/partial clojure.core/re-matches #"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,63}$"),
:val "bar",
:via [:db/foo :foo/email :api.foo/common-email],
:in [:foo/email]})
*预期行为*
{{(s/def :foo/email ::common-email)}}
返回
{{:via [:db/foo :foo/email :api.foo/common-email]}}
*这里发生了什么?*
{{[:db/foo :api.foo/common-email]}} 与 {{[:db/foo :foo/email :api.foo/common-email]}}
所以在这里完全忽略了{{(s/def :foo/email ::common-email)}}。在我看来,这应该是一个错误,而不是一个特性。 ;)
*为什么修复它很重要?*
保持错误不会被传递到用户界面,以便进行正确的通信,这是很重要的。
所以 `[:db/foo :foo/email :api.foo/common-email]`,我在寻找是否有适当的用户界面消息。首先,检查 `:api.foo/common-email`。没有消息,然后检查 `:foo/email`。我有关于它的消息,因此可以返回 "电子邮件不是有效的"。
在实际情况中,它可以是 `:user/password`,具有针对长度、特殊字符等的分解规范进行验证,或者是基于国家的 `:company/vat-id`。但通常我不想在那一刻保留最终验证,因为像街道、电话号码、vat-id、电子邮件等都是常见的,我想在单一位置有一个定义。
例如,我还可以这样做:(s/def :user/email (s/and (s/conformer clojure.string/lower-case) ::s-common/email))。这里的关键点。但并不是所有的电子邮件验证都将执行小写。所以今天我必须使用粗略的解决方案或具有冗余电子邮件验证的所有地方,这使得维护更困难。
我不想基于`::common-email`,因为这部分在任何时候都可能更改。它可以是一个具有欧盟 vat-id 的常见定义的收支图书馆。我不想为这个图书馆的用户界面创建消息,我想基于我的代码中的定义,但是`(s/def :company/vat-id ::bookkeeping-library/vat-id)`在 `:via` 中丢失。
我们可以想象这可能更深、更复杂。在那一刻,找出失败的故障是火箭科学,这就是为什么我使用粗略的解决方案 `(s/def :foo/email (s/and ::common-email))` 来简单从 `:via` 中读取它。