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

欢迎!请在 关于 页面了解更多关于如何使用本站的信息。

0
Spec
h3. 问题

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

h3. 详细说明

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

示例

* 一个 {{keyword?}} 规范
** 使用 EDN,不应执行转换(可以显示关键词)
** 使用 JSON,应用 String->Keyword 转换
** 使用基于字符串的格式(CSV、查询参数等...),应用 String->Keyword 转换

* 一个 {{integer?}} 规范
** 使用 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-参数 {{conform*}} 和 {{clojure.spec/conform}},同时调用一个额外的用户提供的回调/访问者函数。如果提供了回调,它将根据当前规范作为参数从 Specs {{conform*}} 中调用,并将返回 {{nil}} 或一个应该用于实际符合的 2-参数符合器函数。

实际的符合匹配器实现可以在第三方库中维护,如 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)))

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


h3. 替代方案

另一种支持此功能的方法是允许通过协议扩展规范。第三方库可以有一个新的 {{Conforming}} 协议,带有 3-参数 {{conform}} 并添加对该协议的所有当前规范的实现。目前这不可能。

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

16 条答案

0

发布者:alexmiller

我认为我们可能对通过conformers将spec转换为转换引擎不感兴趣,所以我怀疑我们可能也不是很感兴趣。但是,我会留待Rich评估。

0

发布者:ikitommi

目前,Plumatic Schema是在边界处使用的工具。现在,人们开始转向Spec,如果一个人需要为他们的应用使用两个不同的建模库,这对Clojure Web Developement Story真的很糟糕。如果Spec不想通过conformers成为一个转换引擎,我希望替代建议允许第三方编写此类扩展:将Spec公开为Records/Types而不是实体化协议将完成这项工作?

0

发布者:kenrestivo

我可以理解为什么Clojure核心开发者可能不想Spec支持这种类型的强制性,但事实是,总会有人需要这样做。如果Spec本身不支持,那么它就必须在像Tommi的库这样的建立在其上的库中进行处理。

这里的案例是:我有一个YAML格式的配置文件。我使用Clojure库解析YAML,将其转换为map。现在我需要验证这个map,但YAML不支持关键词,例如,设置结构直接作为应用程序状态的一部分进入Component/Mount等,因此在读取配置后运行s/conform作为应用程序启动的第一个步骤是有意义的。考虑到将配置合并到中的可能性(环境变量、.properties文件等),这种强制转换将在某处变得必要。

0

发布者:ikitommi

关于评估这个内容的新闻有何进展?我将很高兴提供补丁或指向包含使用3元参数符合性样例修改的{{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

西蒙,如果你的目标是符合第三方规格,这不会起作用。这个建议的一个要点是,第三方可以为他们自己的规格编写自己的符合器,而无需重新定义这些规格。

0
_评论者:ikitommi_

感谢评论。我将很高兴提供补丁/示例仓库,其中包含需要的更改,希望这有助于决定是否将其添加到规范中。你有什么看法?

以下是将spec-tools工具集成到ring/http库中的初始spec示例,使用spec-tools。目前,需要将规格包装到spec记录中,才能启用3元参数符合性。这应该是我想看到的模板代码。有了这个更改,所有(第三方)规格应该能开箱即用。


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

;; 启用3元参数符合性
(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?) ;; 启用3元参数符合性
(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文档)的环处理程序
;; 根据请求的内容类型选择符合规范(例如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

原计划在我的fork版的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
by
0
by

评论者:marco.m

你好,有什么新闻吗?

0
by
_评论者:alexmiller_

我们至少要等待进行下一批实现更改后才会考虑这个。我仍然认为我们很可能拒绝这个。
...