请分享您的想法,参加2024年Clojure状态调查!

欢迎!请参阅关于页面了解有关如何操作的更多信息。

0
Spec
h3. 问题

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

h3. 详细说明

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

示例

* a {{keyword?}} spec
** EDN,不需要强制转换(它可以显示关键字)
** JSON,应用String->Keyword强制转换
** 基于字符串的格式(CSV、查询参数、...),应用String->Keyword强制转换

* a {{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. 推荐

在Spec协议中支持选择性符合性,使用新的3个参数{{conform*}}和{{clojure.spec/conform}},都接受额外的用户提供的回调/访问函数。如果提供了回调,它将在Spec的{{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. 代替方案

支持这种功能的另一种方法是允许使用协议扩展Specs。第三方库可以有一个新的{{Conforming}}协议,带有3价{{conform}}函数,并且为所有当前Specs添加其实现。目前这不可能。

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

16 个回答

0

评论者:alexmiller

我认为我们不会感兴趣将Spec转换为一个转换引擎,所以我怀疑我们可能不太感兴趣。然而,我将留给Rich来评估。

0

评论者:ikitommi

目前,Plumatic Schema 被用作边界的工具。现在,人们开始使用Spec,如果一个人不得不为他们的应用程序使用两个不同的建模库,这将真的很糟糕,这对于Clojure Web 开发故事来说确实不好。如果Spec不想通过conformer成为一个转换引擎,我希望能有替代建议,允许第三方编写这种类型的扩展:将Spec作为记录/类型公开,而不是用协议体现,不是吗?

0

评论者:kenrestivo

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

此处用例为:我有一个YAML格式的配置文件。我用Clojure库解析YAML,将其转换为map。现在我必须验证这个map,但YAML不支持关键词,例如,设置结构直接作为应用状态的一部分进入Component/Mount等,所以在读取配置后作为应用启动的第一步运行s/conform是合理的。此外,还有其他合并配置的方法(环境变量、}.properties文件等),在这种情况下,这种强制转换将必须在某处完成。

0

评论者:ikitommi

是否有关于此评估的消息?我将乐意提供一个补丁或链接到一个已经修改过的{{clojure.spec}},其中包含对其中的3元符合使用的示例。一些思考: 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-tools。目前,必须将规范包装到spec记录中,以启用3元符合。这是我希望能够移除的样板代码。在此更改之后,它应该可以为所有(第三方)规格开箱即用。


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

;; 启用3元符合
(定义枚举 [values]
  (st/spec (s/and (st/spec 关键字?) values)))

(s/def ::id 整数?)
(s/def ::name string?)
(s/def ::description 字符串?)
(s/def ::size (枚举 #{:L :M :S}))
(s/def ::country (st/spec 关键字?) ;; 开启三参数兼容性
(s/def ::city 字符串?)
(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
            整型? (fn [_ x _]
                   (cond
                     (整型? x) x
                     (字符串? x) (try
                                   (Long/parseLong x)
                                   (catch Exception _
                                     ::s/invalid))
                     :else ::s/invalid))
        :else nil))]

    (testing "没有兼容回调函数"
      (is (= 1 (s/conform 整型? 1)))
      (is (= ::s/invalid (s/conform 整型? "1"))))

    (testing "有兼容回调函数"
      (is (= 1 (s/conform 整型? 1 string->int-conforming)))
      (is (= 1 (s/conform 整型? "1" string->int-conforming))))))
0
by

评论者:ikitommi

最初作为补丁 munki

0
by

评论者:ikitommi

对此有何新消息?

0
by

评论者:ikitommi

相关问题 https://dev.clojure.org/jira/browse/CLJ-2251

0
by

评论者:marco.m

你好,有任何消息吗?

0
by
评论:_Comment made by: alexmiller_

我们将至少等到我们完成下一批实现更改后才会查看这个问题。我仍然认为我们很可能拒绝这个问题。
...