嘗試 Clojure Spec 的筆記

工具當中須要檢測數據格式, 試着用了一下 Clojure Spec.
若是英文好, 直接看文檔就好了, 也不用這篇筆記, 太瑣碎了, 也缺失例子...
https://clojure.org/guides/sp...git

例子我整理在了 spec-examples 倉庫, 能夠用 Lumo 直接跑.github

首先添加依賴, 由於我在 ClojureScript 當中用, 因此用了 cljs.spec 這個代碼.
expound 是一個用於美化 Spec 輸出的類庫, 直接引用進來.c#

[cljs.spec.alpha :as s]
[expound.alpha :refer [expound]]

首先是一個很簡單以爲例子, 有 s/valid? 判斷數據是否符合格式.
首先用 s/def 定義好一個校驗的規則, 其中 ::example 會按照命名空間展開.數組

(s/def ::example boolean?)
(println (s/valid? ::example 1)) ; false
(println (s/valid? ::example true)) ; true

基礎的校驗用的是函數, 也能夠是 string?.app

s/conform 表示返回輸出的值.. 固然這個是正確的狀況, 返回了匹配到的字符串,ide

(s/def ::example string?)
(println (s/conform ::example "DEMO")) ; DEMO

若是不匹配, 返回值就是 invalid,函數

(println (s/conform number? ""))
:cljs.spec.alpha/invalid

能夠經過 s/explain 來打印失敗的緣由,工具

(s/def ::example string?)
(println (s/explain ::example 1))
; 1 - failed: string? spec: :app.main/example

能夠看到這個緣由比較精確, 可是可讀性不怎麼樣, 就能夠用 expound 替換了, 可讀性會好不少,ui

(s/def ::example string?)
(println (expound ::example 1))
-- Spec failed --------------------

  1

should satisfy

  string?

-- Relevant specs -------

:app.main/example:
  cljs.core/string?

-------------------------
Detected 1 error

既然校驗規則是函數, 也能夠寫成,spa

(s/def ::example #(and (> % 6) (< % 20)))
(println (s/valid? ::example 1)) ; false
(println (s/valid? ::example 10)) ; true
(println (s/valid? ::example 20)) ; false

校驗規則也能夠組合使用, 最簡單就是 s/or, 注意參數中奇數位置都用的 keyword,

(s/def ::example (s/or :as-number number? :as-boolean boolean?))

(let [data 0]
  (if (s/valid? ::example data)
    (println (s/conform ::example data))
    (println (expound ::example data)))))

打印的結果是,

[:as-number 0]

s/or 裏直接用函數式簡寫了, 能夠專門定義兩個規則出來, 而後再使用,

(s/def ::boolean boolean?)

(s/def ::number number?)

(s/def ::example (s/or :as-number ::number :as-boolean ::boolean))

(if (s/valid? ::example 20)
  (println (s/conform ::example 20))
  (println (expound ::example 20)))

返回依然獲得數據,

[:as-number 20]

對於數組的結構的數據, 用 s/coll-of 來判別,

(s/def ::number number?)

(s/def ::example (s/coll-of ::number))

(let [data [1]]
  (if (s/valid? ::example data)
    (println (s/conform ::example data))
    (println (expound ::example data))))

獲得,

[1]

s/coll-of 還支持好比 :count 這樣的校驗, 具體能夠再看文檔,

(s/def ::example (s/coll-of number? :count 2))

(defn task! []
 (let [data [1]]
   (if (s/valid? ::example data)
     (println (s/conform ::example data))
     (println (expound ::example data)))))
-- Spec failed --------------------

  [1]

should satisfy

  (= 2 (count %))

-- Relevant specs -------

:app.main/example:
  (cljs.spec.alpha/coll-of cljs.core/number? :count 2)

-------------------------
Detected 1 error

對於 Map, 用 s/keys 來判斷, :req-un 表示必選項, opt-un 是可選項,

(s/def ::age number?)

(s/def ::name string?)

(s/def ::example (s/keys :req-un [::age] :opt-un [::name]))

(let [data {:age 1, :name "a"}]
  (if (s/valid? ::example data)
      (println (s/conform ::example data))
      (println (expound ::example data))))

獲得,

{:age 1, :name a}

若是不知足校驗規則, 會準確提示出來, 好比可選項的規則不知足,

(s/def ::age number?)

(s/def ::name string?)

(s/def ::example (s/keys :req-un [::age] :opt-un [::name]))

(let [data {:age 1, :name 1}]
  (if (s/valid? ::example data)
    (println (s/conform ::example data))
    (println (expound ::example data))))
-- Spec failed --------------------

  {:age ..., :name 1}
                   ^

should satisfy

  string?

-- Relevant specs -------

:app.main/name:
  cljs.core/string?
:app.main/example:
  (cljs.spec.alpha/keys :req-un [:app.main/age] :opt-un [:app.main/name])

-------------------------
Detected 1 error

上面用到的 -un 的後綴表示 "unqualified", 若是沒有後綴, 意味着 keyword 要根據命名空間展開,

(s/def ::age number?)

(s/def ::name string?)

(s/def ::example (s/keys :req [::age] :opt [::name]))

(let [data {:age 1, :name 1}]
  (if (s/valid? ::example data)
    (println (s/conform ::example data))
    (println (expound ::example data))))

因而就不知足了,

-- Spec failed --------------------

  {:age 1, :name 1}

should contain key: :app.main/age

|           key |    spec |
|---------------+---------|
| :app.main/age | number? |

-- Relevant specs -------

:app.main/example:
  (cljs.spec.alpha/keys :req [:app.main/age] :opt [:app.main/name])

-------------------------
Detected 1 error

就須要改寫一下 key, 也用 ::x 的語法帶上命名空間,

(s/def ::age number?)

(s/def ::name string?)

(s/def ::example (s/keys :req [::age] :opt [::name]))

(let [data {::age 1, ::name "a"}]
  (if (s/valid? ::example data)
    (println (s/conform ::example data))
    (println (expound ::example data))))

獲得,

{:app.main/age 1, :app.main/name a}

Spec 也能夠對字符串進行校驗, 同時也能夠解析獲得數據,
其中須要用到 (s/conformer seq) 來對字符串進行轉化...
這個寫法目前我也不夠清晰, 參考了一下例子,
https://gist.github.com/thege...

(s/def ::left-paren #{"("})

(s/def ::right-paren #{")"})

(s/def ::space (s/and string? (s/conformer seq) (s/+ #{" "})))

(s/def ::token (s/and string? (s/conformer seq) (s/+ #{"a" "b" "c"})))

(s/def
 ::example
 (s/cat
  :left-paren
  ::left-paren
  :expr
  (s/+ (s/or :token ::token :space ::space))
  :right-paren
  ::right-paren))

(let [data (seq "(a b)")]
  (if (s/valid? ::example data)
    (println (pr-str (s/conform ::example data)))
    (println (s/explain ::example data))))

最終獲得,

{:left-paren "(", :expr [[:token ["a"]] [:space [" "]] [:token ["b"]]], :right-paren ")"}

更多

另外關於 multi-spec 的例子, 還有生成代碼的例子, 我在 GitHub 上整理了,
https://github.com/jiyinyiyon...
代碼比較成就不復制了.

另外細節的功能沒有記錄, 具體要看官方文檔. https://clojure.org/guides/sp...

相關文章
相關標籤/搜索