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

欢迎!有关如何使用本平台的更多信息,请查阅关于页面。

0
Spec
h3. 问题

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

h3. 详细信息

当前在clojure.spec(alpha-14)中,conformer是在创建Spec实例时附加的,并且在每次conform时都会调用它们。这在系统边界验证中并不很有用,在这种验证中,应根据运行时数据选择conform/coercion函数,例如交换格式。

示例

** 一种keyword?规范
** 使用EDN,不应执行转换(它可以展示关键词)
** 使用JSON,应应用String->Keyword转换
** 使用基于String的格式(CSV、查询参数等),应应用String->Keyword转换

** 一种integer?规范
** 使用EDN,不应执行转换(它可以展示数字)
** 使用JSON,不应执行转换(它可以展示数字)
** 使用基于String的格式(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)


要使用这些,现在需要手动为所有不同的交换格式创建新的边界规范,并为每个格式创建具有不同conformer的新规范。非限定关键字可以映射到{{s/keys}}中工作(例如,{{::title}} => {{::title$JSON}}),但如果完全限定的键在边界上公开(如示例中的{{::id}}),将不起作用 - 不能为相同的规范名注册多个具有不同转换功能的规范版本。

h3. 建议

在Spec协议中支持选择性conforming,使用新的3参数{{conform*}}和{{clojure.spec/conform}},两者都接受一个额外的用户提供的回调/访问器函数。如果提供了回调,则从Spec的{{conform*}}中调用它,并将当前规范作为参数传递。它将返回{{nil}}或一个2参数conformer函数,该函数应用于实际conform。

实际的conforming-matcher实现可以在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
(断言 (= conformed-person (s/conform ::person string-person string-conforming-matcher)))


h3. 代替方案

支持这一功能的另一种方法是允许Spec通过协议进行扩展。第三方库可以有一个新的{{Conforming}}协议,其中包含3参数的{{conform}}函数,并为其添加所有当前spec的实现。目前这不可能。

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

16答案

0
by

评论由: alexmiller 发表

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

0
by

评论由: ikitommi 发表

目前,Plumatic Schema是用在边界的工具。现在,人们开始转向Spec,如果一个人需要为他的应用使用两个不同的建模库,那将非常糟糕。如果Spec不想通过转换器成为转换引擎,我希望替代方案能够允许第三方编写此类扩展:将Spec公开为记录/类型而不是实现协议可以完成这项工作?

0
by

评论由: kenrestivo 发表

我可以理解为什么Clojure的core开发者可能不希望Spec支持这种类型的强制转换,但实际情况是有人在使用。如果这不是在Spec本身中,它将需要在Tommi创建的基于它的库中进行。

这里的用例是:我有一个YAML格式的配置文件。我使用Clojure库解析YAML,将其转换为映射。现在我需要验证映射,但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

如果你正在尝试对第三方规范进行 conformity,那么这不会起作用。这个建议的一个要点是第三方能够为现有的规范编写自己的conformers,而不需要重新定义这些规范。

0
_评论者:ikitommi_

感谢大家的评论。我很乐意提供一个包含所需的更改的补丁/示例存储库,希望通过这能帮助决定是否应该将其添加到规范中。您怎么看?

以下是使用spec-tools将初始spec集成到ring/http库的示例。目前,需要将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
(定义枚举 [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文档的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
by

评论由: ikitommi 发表

打算在我的clojure.spec分叉版本中创建内部PR,但最终为实际仓库创建了一个真正的DUMMY PR。嗯,无论如何,它就在这里

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

如果这个走上了进一步的道路,我很乐意完成它并创建一个patch到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

评论由: ikitommi 发表

相关链接 https://dev.clojure.org/jira/browse/CLJ-2251

0
by

评论者:marco.m

你好,有什么新的消息吗?

0
by
_评论者:alexmiller_

我们至少要等到我们完成下一批次的实施更改后才会考虑这个问题。我仍然认为我们很可能会拒绝这个。
...