Last active
June 6, 2016 08:11
-
-
Save sniperliu/c4e1d223827191305c0b1921d09a0dbe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns clj-spec.fish | |
(:require [schema.core :as schema] | |
[clojure.spec :as spec] | |
[clojure.spec :as s])) | |
;; just beginning | |
;; Schema does this | |
(def Data | |
"A schema for a nested data type" | |
{:a {:b schema/Str | |
:c schema/Int} | |
:d [{:e schema/Keyword | |
:f [schema/Num]}]}) | |
(schema/validate | |
Data | |
{:a {:b "abc" | |
:c 123} | |
:d [{:e :bc | |
:f [12.2 13 100]} | |
{:e :bc | |
:f [-1]}]}) | |
(schema/validate | |
Data | |
{:a {:b "abc" | |
:c 123} | |
:d []}) | |
;; Success! | |
#_(schema/validate | |
Data | |
{:a {:b 123 | |
:c "ABC"}}) | |
;; Exception -- Value does not match schema: | |
;; {:a {:b (not (instance? java.lang.String 123)), | |
;; :c (not (integer? "ABC"))}, | |
;; :d missing-required-key} | |
;; Spec does this | |
(spec/def ::b string?) | |
(spec/def ::c integer?) | |
(spec/def ::a (spec/keys :req-un [::b ::c])) | |
(spec/def ::e keyword?) | |
(spec/def ::f (spec/* number?)) | |
(spec/def ::d (spec/* (spec/keys :req-un [::e ::f]))) | |
(spec/def ::data | |
(spec/keys :req-un [::a ::d])) | |
(spec/valid? | |
::data | |
{:a {:b "abc" | |
:c 123} | |
:d [{:e :bc | |
:f [12.2 13 100]} | |
{:e :bc | |
:f [-1]}]}) | |
(spec/explain-data ::data {:a {:b "abc" | |
:c 123} | |
:d [{:e :bc | |
:f [12.2 13 100]} | |
{:e :bc | |
:f [-1]}]}) | |
(spec/valid? | |
::data | |
{:a {:b 123 | |
:c "ABC"}}) | |
(spec/explain-data ::data {:a {:b 123 | |
:c "ABC"}}) | |
;; considering http://plumatic.github.io/schema-for-clojurescript-data-shape-declaration-and-validation/ | |
;; Extensible protocol | |
(schema/validate schema/Num 42) | |
(spec/def ::num number?) | |
(spec/valid? ::num 42.0) | |
(spec/valid? ::num "42") | |
(schema/validate schema/Keyword :key) | |
(spec/def ::key keyword?) | |
(spec/valid? ::key :key) | |
(schema/validate schema/Int 42) | |
(spec/def ::int integer?) | |
(spec/valid? ::int 42) | |
(schema/validate schema/Str "hello") | |
(spec/def ::str string?) | |
(spec/valid? ::str "hello") | |
;; On the JVM, you can use classes for instance? checks | |
(schema/validate java.lang.String "schema") | |
;; On JS, you can use prototype functions | |
#_(schema/validate Element document.getElementById("some-div-id")) | |
(schema/validate [schema/Num] [1 2 3.0]) | |
(spec/def ::num-vector (spec/coll-of number? [])) | |
(spec/valid? ::num-vector [1 2 3.0]) | |
(spec/def ::int-list? (spec/coll-of integer? '())) | |
(spec/def ::int-list (spec/and (spec/coll-of integer? ()) | |
list?)) | |
(spec/valid? ::int-list [1 2 3]) ;; what? why => true | |
(spec/valid? ::int-list [1.0 2 3]) | |
(spec/valid? ::int-list '(1 2 3)) | |
(schema/validate (schema/enum :a :b :c) :a) | |
(spec/def ::key-set #{:a :b :c}) | |
(spec/valid? ::key-set :a) | |
(schema/validate {:name schema/Str :id schema/Int} {:name "Bob" :id 42}) | |
(spec/def ::name string?) | |
(spec/def ::id integer?) | |
(spec/def ::a-map (spec/keys :req-un [::name ::id])) | |
(spec/valid? ::a-map {:name "Bob" :id 42}) | |
(schema/validate {(schema/enum :a :b :c) schema/Num} {:a 1 :b 2 :c 3}) | |
(spec/def ::b-map (spec/map-of #{:a :b :c} number?)) | |
(spec/valid? ::b-map {:a 1 :b 2 :c 3}) | |
(schema/validate [(schema/both schema/Str (schema/pred (comp odd? count)))] | |
["a" "aaa" "aaaaa"]) | |
(spec/def ::with-content-check | |
(spec/coll-of (spec/and string? | |
#(odd? (count %))) [])) | |
(spec/valid? ::with-content-check | |
["a" "aaa" "aaaaa"]) | |
(spec/valid? ::with-content-check | |
["aa" "aaa" "a"]) | |
;; Specifiy a function | |
;; Schema do this | |
(schema/defn with-full-name-schema | |
[m :- {:first-name schema/Str :last-name schema/Str schema/Any schema/Any}] | |
;; Blows up if not given a map without a string under | |
;; :first-name or :last-name keys | |
(assoc m :name (str (:first-name m) " " (:last-name m)))) | |
(schema/with-fn-validation | |
(with-full-name-schema {:first-name "Hao" :last-name "Liu" :age 38 :hobby "clojure"})) | |
;; Spec do this | |
(defn with-full-name | |
[m] | |
(-> (assoc m :name (str (:first-name m) " " (:last-name m))))) | |
(spec/def ::first-name string?) | |
(spec/def ::last-name string?) | |
(spec/fdef with-full-name | |
:args (spec/cat :m (spec/keys :req-un [::first-name ::last-name])) | |
:ret (spec/keys :req-un [::first-name ::last-name])) | |
(spec/instrument #'with-full-name) | |
;; More | |
;;Schema | |
(def ShareAction (schema/enum :twitter :email :facebook)) | |
(def ShareCounts {ShareAction (schema/named long "share count")}) | |
(def UserShareCounts {(schema/named long "user-id") ShareCounts}) | |
(def UserShareUpdate [(schema/one long "user-id") | |
(schema/one ShareAction "share action") | |
(schema/one long "share delta")]) | |
(schema/defn update-share-counts-schema :- UserShareCounts | |
[share-counts :- UserShareCounts | |
updates :- [UserShareUpdate]] | |
(reduce | |
(fn [result [user-id share-type delta]] | |
(update-in result | |
[user-id share-type] | |
(fnil + 0) | |
delta)) | |
share-counts | |
updates)) | |
;; Spec | |
(spec/def ::share-action #{:twitter :email :facebook}) | |
(spec/def ::share-counts (spec/map-of ::share-action integer?)) | |
(spec/def ::user-share-counts (spec/map-of integer? ::share-counts)) | |
(spec/def ::user-share-update (spec/cat :user-id integer? | |
:share-action ::share-action | |
:share-delta integer?)) | |
(spec/valid? ::share-counts {:twitter 1 :email 2}) | |
(spec/valid? ::user-share-counts {1 {:twitter 10} 2 {:email 5}}) | |
(spec/explain-data ::user-share-update [1 :twitter 10]) | |
(defn update-share-counts | |
[share-counts updates] | |
(reduce | |
(fn [result [user-id share-type delta]] | |
(update-in result | |
[user-id share-type] | |
(fnil + 0) | |
delta)) | |
share-counts | |
updates)) | |
(spec/fdef update-share-counts | |
:args (spec/cat :share-counts (spec/spec ::user-share-counts) | |
:updates (spec/spec (spec/coll-of ::user-share-update []))) | |
:ret ::user-share-counts) | |
(spec/instrument #'update-share-counts) | |
;; Card Game | |
(def kenny | |
{::name "Kenny Rogers" | |
::score 100 | |
::hand []}) | |
;; Schema | |
(def Suit (schema/enum :club :diamond :heart :space)) | |
(def Rank (apply schema/enum (into '(:jack :queen :king :ace) (range 2 11)))) | |
(def Card [(schema/one Suit "suit") | |
(schema/one Rank "rank")]) | |
(def Player | |
{::name schema/Str | |
::score schema/Int | |
::hand [Card]}) | |
(schema/validate Card [:club 2]) | |
(schema/check Card [:club 2 2]) | |
(schema/explain Card) | |
(schema/validate Player kenny) | |
;; Spec | |
(def suit? #{:club :diamond :heart :spade}) | |
(def rank? (into #{:jack :queen :king :ace} (range 2 11))) | |
(def deck (for [suit suit? rank rank?] [rank suit])) | |
(spec/def ::card (spec/tuple rank? suit?)) | |
(spec/def ::hand (spec/* ::card)) | |
(spec/def ::name string?) | |
(spec/def ::score integer?) | |
(spec/def ::player (spec/keys :req [::name ::score ::hand])) | |
(spec/def ::players (spec/* ::player)) | |
(spec/def ::deck (spec/* ::card)) | |
(spec/def ::game (spec/keys :req [::players ::deck])) | |
(spec/valid? ::player kenny) | |
;; Employee | |
(schema/defrecord Employee | |
[emp-name :- (schema/pred #(re-matches #"[0-9]{10}")) | |
emp-hire-date :- org.joda.time.DateTime]) | |
(spec/def ::geid (spec/and string? | |
#(re-matches #"[0-9]{10}" %))) | |
(spec/def ::date-time #(instance? org.joda.time.DateTime)) | |
(spec/def ::emp-name ::geid) | |
(spec/def ::emp-hier-date ::date-time) | |
(spec/def ::employee | |
(spec/keys :req-un [::emp-name ::emp-hire-date])) | |
(defrecord Employee [emp-name emp-hire-date]) | |
;; test.check | |
;; example from http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/ | |
;; Schema does this | |
;; Spec does this | |
;; Only Spec have | |
;; multi-spec | |
;; conform, custom conformer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment