Skip to content

Instantly share code, notes, and snippets.

@sniperliu
Last active June 6, 2016 08:11
Show Gist options
  • Save sniperliu/c4e1d223827191305c0b1921d09a0dbe to your computer and use it in GitHub Desktop.
Save sniperliu/c4e1d223827191305c0b1921d09a0dbe to your computer and use it in GitHub Desktop.
(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