2024 Clojure状态调查中分享您的想法!

欢迎!请参阅关于页面以获取有关如何工作的更多信息。

0票数
Spec
h3. 问题

在运行时边界验证中,使用 {{clojure.spec}} 支持多个交换格式很困难。

h3. 详细信息

目前,在clojure.spec (alpha-14) 中,验证器在创建Spec实例时附加到Spec实例,并且在每个验证中调用。这在系统边界验证中不太有用,在系统边界验证中,应根据运行时数据选择验证/强制转换函数,例如交换格式。

示例

* 一个 {{keyword?}} spec
** 对于EDN,不应执行任何强制转换(它可以表示关键字)
** 对于JSON,应应用String->Keyword强制转换
** 对于基于字符串的格式(CSV、查询参数等),应应用String->Keyword强制转换

* 一个 {{integer?}} spec
** 对于EDN,不应执行任何强制转换(它可以表示数字)
** 对于JSON,不应执行任何强制转换(它可以表示数字)
** 对于基于字符串的格式(CSV、查询参数等),应应用String->Long强制转换

这里是一个更完整的示例


(s/def ::id integer?)
(s/def ::name string?)
(s/def ::title keyword?)
(s/def ::person (s/keys :opt [::id], :req-un [::name ::title]))

;; 这是我们在不同的交换格式中看到数据的方式
(def edn-person {::id 1, :name "Tiina", :title :boss})
(def json-person {::id 1, :name "Tiina", :title "boss"})
(def string-person {::id "1", :name "Tiina", :title "boss"})

;; 这是我们要做的
(def conformed-person edn-person)


要使用此功能,今天一个人需要手动为所有不同的交换格式创建带有不同验证器的新的边界规格。非限定关键字可以映射到s/keys中(例如,{{::title}} => {{::title$JSON}})来工作,但如果完全限定的关键字在边界上公开了(例如,例如示例中的{{::id}}) - 您不能使用同名的多个不同验证的规格版本。

h3. 建议

通过新的3-arity {{conform*}} 和 {{clojure.spec/conform}} 支持Spec协议中的选择性验证,两者都接受一个额外的用户提供的回调/访问者函数。如果提供了回调,则在Spec的{{conform*}}内调用它,并以当前规格作为参数,它将返回一个或一个2-arity的验证器函数,该函数应用于实际验证。

实际验证器实现可以由第三方库(如spec-tools[1])维护。

使用方法如下


;; edn
(assert (= conformed-person (s/conform ::person edn-person)))
(assert (= conformed-person (s/conform ::person edn-person nil)))

;; json
(assert (= conformed-person (s/conform ::person json-person json-conforming-matcher)))

;; 字符串
(断言 (= conformed-person (s/conform ::person string-person string-conforming-matcher)))


h3. 替代方案

支持此功能的另一个选项是允许规格通过协议进行扩展。第三方库可以拥有一个新的 {{Conforming}} 协议,其有三个参数的 {{conform}},并在所有当前规范中为其添加实现。目前,这不可行。

[1] https://github.com/metosin/spec-tools

16 答案

0票数

评论者:alexmiller

我认为我们可能不希望通过 conformer 将 spec 转换为转换引擎,所以我怀疑我们可能不感兴趣。然而,我将留待 Rich 评估。

0票数

评论者:ikitommi

目前,Plumatic Schema 被用于边界处。现在,人们开始转向 Spec,如果一个人不得不为他们的应用程序使用两个不同的建模库,那么这对于 Clojure Web 开发故事来说将会非常糟糕。如果 Spec 不想通过 conformer 成为转换引擎,我建议允许第三方编写此类扩展:将 Spec 作为记录/类型公开而不是实现协议将会起作用?

0票数

评论者:kenrestivo

我可以理解 Clojure 核心开发者为什么可能不希望 Spec 支持这种类型的强制转换,但实际情况是,有人必须这样做。如果它不在 Spec 本身,它将必须由构建在其上的库(例如 Tommi 的)来完成。

这个用例是:我有一个 YAML 配置文件。我正在使用 Clojure 库解析 YAML,将其转换为映射。现在我必须验证映射,但 YAML 不支持关键词,例如,设置结构直接作为应用程序状态的一部分进入 Component/Mount 等,因此从读取配置后启动应用程序的第一步运行 s/conform 很有意义。此外,还有其他合并配置的方法(环境变量、.properties 文件等),因此这种强制转换将在某处是必要的。

0票数

评论者:ikitommi

对这个问题的评估有什么新闻吗?我会很高兴提供一个补丁或一个包含使用3-arity符合性的示例的修改版{{clojure.spec}}的链接。一些随想:http://www.metosin.fi/blog/clojure-spec-as-a-runtime-transformation-engine/

0票数

评论者:alexmiller

Rich尚未查看。我仍然猜测我们对此更改不感兴趣。虽然我认为帖子中描述了一些有趣的问题,但我不同意那里采取的大多数方法。

0票数

评论者:sbelak

为什么不直接使用 s/or (或 s/alt) 并然后在标签上进行调度。例如:

`
(s/def ::id (s/and (s/or :int integer?

                     :str string?)
               (s/conformer (fn [[tag x]]
                              (case tag
                                :int x
                                :str (Integer/parseInt x))))))

`

我经常在 https://github.com/sbelak/huri中使用这种模式,并且通过一点语法糖它表现得相当不错。

0票数

评论者:imre

如果你试图符合第三方规范,那么Simon这样做将不会起作用。这个建议的一个点是,第三方能够为现有的规范编写他们自己的符合器,而不必重新定义这些规范。

0票数
_评论者:ikitommi_

感谢您的评论。我将很高兴提供一个包含所需更改的补丁/示例回购库,希望它能帮助决定是否可以将此功能最终纳入规范。您怎么看?

以下是在使用spec-tools将初始spec集成到ring/http库中的示例,为先决条件。目前,需要将规范包装到spec记录中才能启用3-arity符合性。这是我希望看到的样板。通过此更改,它应支持所有(第三方)规格开出箱即用。


(require '[compojure.api.sweet :refer :all])
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])

;; 启用3-arity符合性
(defn enum [values]
  (st/spec (s/and (st/spec keyword?) values)))

(s/def ::id int?)
(s/def ::name string?)
(s/def ::description string?)
(s/def ::size (enum #{:L :M :S}))
(s/def ::country (st/spec keyword?) ;; 以启用三参数适应
(s/def ::city string?)
(s/def ::origin (s/keys :req-un [::country ::city]))
(s/def ::new-pizza (st/spec (s/keys :req-un [::name ::size ::origin] :opt-un [::description])))
(s/def ::pizza (st/spec (s/keys :req [::id] :req-un [::name ::size ::origin] :opt-un [::description])))

;; 输出带有输入和输出验证(以及swagger文档)的ring处理器
;; 根据请求内容类型(例如json/edn)选择适应,并从映射中删除额外的键
(context "/spec" []
  (resource
    {:coercion :spec
     :parameters {:body-params ::new-pizza}
     :responses {200 {:schema ::pizza}}
     :post {:handler (fn [{new-pizza :body-params}]
          (ok (assoc new-pizza ::id 1))}}))
0票数

评论者:ikitommi

打算在我的clojure.spec分叉中创建内部PR,但最终为实际存储库做了一份真正的DUMMY PR。好吧,不管怎样,这就是它了

https://github.com/clojure/spec.alpha/pull/1

如果进展进一步,我很乐意将其最终确定并创建一个补丁到Jira。

0票数
_评论者:ikitommi_

欢迎评论。这里是一个示例测试


(deftest conforming-callback-test
  (let [string->int-conforming
        (fn [spec]
          (condp = spec
            int? (fn [_ x _]
                   (cond
                     (int? x) x
                     (string? x) (try
                                   (Long/parseLong x)
                                   (catch Exception _
                                     ::s/invalid))
                     :else ::s/invalid))
            :else nil))]

    (testing "没有适应回调"
      (is (= 1 (s/conform int? 1)))
      (is (= ::s/invalid (s/conform int? "1"))))

    (testing "具有适应回调"
      (is (= 1 (s/conform int? 1 string->int-conforming)))
      (is (= 1 (s/conform int? "1" string->int-conforming))))))
0票数

评论者:ikitommi

作为补丁的初步工作。

0票数

评论者:ikitommi

对这个有什么新消息吗?

0票数
0票数

评论由:marco.m

你好,有什么新闻吗?

0票数
评论由:alexmiller

至少在我们进行下一批实现更改之前,我们不会去看这个。我仍然认为我们很可能会拒绝。
...