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

欢迎!有关本站如何运作的更多信息,请参阅关于 页面。

0
Spec
h3. 问题

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

h3. 详情

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

示例

* 一个 {{keyword?}} 规范
** 使用 EDN,不应执行强制转换(它可以显示 Keyword)
** 使用 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-arity {{conform*}} 和 {{clojure.spec/conform}} 支持选择性符合,两者都接受额外的用户提供的回调/访问函数。如果提供了回调,它将从规范的内侧的 {{conform*}} 调用,并将以当前规范作为参数传递,并返回 {{nil}} 或一个 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)))

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


h3. 替代方案

另一种支持此方法的方法是允许使用协议扩展规范。第三方库可以通过一个新的 {{Conforming}} 协议以及为其添加对所有当前规范的实施情况进行此操作。目前这是不可能的。

【1】 https://github.com/metosin/spec-tools

16 条答案

0

评论者:alexmiller

我认为我们没有兴趣通过conformer将spec转换成一个转换引擎,所以我怀疑我们可能不感兴趣。但是,我会留给Rich去评估。

0

评论者:ikitommi

目前,Plumatic Schema是在边界处使用的工具。现在,人们开始转向Spec,如果一个人必须为他们的应用程序使用两个不同的建模库,这对Clojure Web开发故事将非常不利。如果Spec不想通过conformer成为一个转换引擎,我希望能有替代建议允许第三方编写这类扩展:将Spec公开为Records/Types而不是具体协议会怎样?

0

评论者:kenrestivo

我可以理解为什么Clojure核心开发者可能不想让Spec支持这种类型的强制转换,但实际现实是有人必须这样做。如果Spec本身不支持,它将不得不在基于它的库中完成,例如Tommi的库。

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

0

评论者:ikitommi

这个项目评估有什么新消息吗?我很乐意提供一个补丁或修改后的{{clojure.spec}}链接,其中包含与3-arity conform的使用样本。一些想法: 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-tools。目前,需要将spec包装在spec记录中以启用3-arity conforming。这是我希望移除的样板代码。通过此更改,它应该可以支持所有(第三方)spec。


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

;;启用3-arity conforming
(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-arity conforming
(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)选择符合条件的内容,并从映射中删除多余的键
(上下文 "/spec" [])
  (资源
    {: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
by

评论者:ikitommi

intención:在我的clojure.spec分叉版本中创建内部PR,但最终在实际情况中对实际存储库进行了DUMMY PR。不管怎样,这就是它

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

如果进度有任何发展,我很乐意最终在Jira中完成并创建补丁。

0
by
_评论者: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
by

评论者:ikitommi

作为补丁的初始工作。

0
by

评论者:ikitommi

关于这个有什么新闻吗?

0
by
0
by

评论者:marco.m

你好,有什么新闻吗?

0
by
评论者:alexmiller

我们至少在完成下一批实现更改之前不会考虑这个问题。我仍然认为我们很可能会拒绝这个。
...