工具當中須要檢測數據格式, 試着用了一下 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...