Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by
Full code example:

(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]))

  (->> (s/explain-data :db/foo {:foo/email "bar"})


({: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]})

*Dirty hack*
But if I use `(s/def :foo/email (s/and ::common-email))` instead it return

({: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]})

*expected behaviour*
{{(s/def :foo/email ::common-email)}}
{{:via [:db/foo :foo/email :api.foo/common-email]}}

*What happen here?*
{{[:db/foo :api.foo/common-email]}} vs {{[:db/foo :foo/email :api.foo/common-email]}}

So {{(s/def :foo/email ::common-email)}} is totally omit here. In my opinion it is a bug, not a feature ;)

*Why is it important to fix?*
It is important to keep it full tracked to turn this errors to right communication in User Interface.
So `[:db/foo :foo/email :api.foo/common-email]`, I am looking if I have proper message for UI. First `:api.foo/common-email`. No message, then check `:foo/email`. I have message for it so I can return "E-mail is not valid".

In practice it can be `:user/password` with split spec for length, special characters etc. validation or `:company/vat-id` based on country. But always I don't want to keep final validation at that moment, because things like street, phone number, vat-id, email etc. are common and I want to have one definition in one place.

On top of it I can do for example `(s/def :user/email (s/and (s/conformer clojure.string/lower-case) ::s-common/email))`. Here is the point. But not all e-mail validations will do lower-case. So today I have to use dirty hack or have redundant e-mail validation in all places which is harder to maintenance.

I don't want to base on `::common-email`, because this part is considered to change in any moment. It can be bookkeeping library with common definition for European Union vat-id. I don't want to base messages for UI for this library, I want to base on my definitions in my code, but `(s/def :company/vat-id ::bookkeeping-library/vat-id)` lose in `:via`.

We can imagine it is deeper and more complex structure. At that moment figuring out what is the issue of fail is rocket science, that is why I use dirty hack `(s/def :foo/email (s/and ::common-email))` to read it simple from `:via`.

1 Answer

0 votes
Reference: https://clojure.atlassian.net/browse/CLJ-2496 (reported by kwladyka)