Skip to content

Instantly share code, notes, and snippets.

@olegakbarov
Forked from ghoseb/clj_spec_playground.clj
Created October 4, 2017 09:40
Show Gist options
  • Save olegakbarov/ea0437861b051d1ad03ac9ec6aefce67 to your computer and use it in GitHub Desktop.
Save olegakbarov/ea0437861b051d1ad03ac9ec6aefce67 to your computer and use it in GitHub Desktop.

Revisions

  1. @ghoseb ghoseb revised this gist Jun 1, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion clj_spec_playground.clj
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@
    [clojure.spec :as s]
    [clojure.test.check.generators :as gen]))

    ;;; examples of core.spec being used like a gradual/dependently typed system.
    ;;; examples of clojure.spec being used like a gradual/dependently typed system.

    (defn make-user
    "Create a map of inputs after splitting name."
  2. @ghoseb ghoseb revised this gist Jun 1, 2016. No changes.
  3. @ghoseb ghoseb created this gist May 24, 2016.
    118 changes: 118 additions & 0 deletions clj_spec_playground.clj
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    (ns clj-spec-playground
    (:require [clojure.string :as str]
    [clojure.spec :as s]
    [clojure.test.check.generators :as gen]))

    ;;; examples of core.spec being used like a gradual/dependently typed system.

    (defn make-user
    "Create a map of inputs after splitting name."
    ([name email]
    (let [[first-name last-name] (str/split name #"\ +")]
    {::first-name first-name
    ::last-name last-name
    ::email email}))
    ([name email phone]
    (assoc (make-user name email) ::phone (Long/parseLong phone))))


    (defn cleanup-user
    "Fix names, generate username and id for user."
    [u]
    (let [{:keys [::first-name ::last-name]} u
    [lf-name ll-name] (map (comp str/capitalize str/lower-case)
    [first-name last-name])]
    (assoc u
    ::first-name lf-name
    ::last-name ll-name
    ::uuid (java.util.UUID/randomUUID)
    ::username (str/lower-case (str "@" ll-name)))))

    ;;; and now for something completely different!
    ;;; specs!

    ;;; Do NOT use this regexp in production!
    (def ^:private email-re #"(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}")

    (defn ^:private ^:dynamic valid-email?
    [e]
    (re-matches email-re e))

    (defn ^:private valid-phone?
    [n]
    ;; lame. do NOT copy
    (<= 1000000000 n 9999999999))

    ;;; map specs
    (s/def ::first-name (s/and string? #(<= (count %) 20)))
    (s/def ::last-name (s/and string? #(<= (count %) 30)))
    (s/def ::email (s/and string? valid-email?))
    (s/def ::phone (s/and number? valid-phone?))

    (def user-spec (s/keys :req [::first-name ::last-name ::email]
    :opt [::phone]))

    ;;; play with the spec rightaway...
    ;;; conform can be used for parsing input, eg. in macros
    (s/conform user-spec {::first-name "anthony"
    ::last-name "gOnsalves"
    ::email "[email protected]"
    ::phone 9820740784})


    ;;; sequence specs

    (s/def ::name (s/and string? #(< (count %) 45)))
    (s/def ::phone-str (s/and string? #(= (count %) 10)))

    (def form-spec (s/cat :name ::name
    :email ::email
    :phone (s/? ::phone-str)))

    ;;; Specify make-user
    (s/fdef make-user
    :args (s/cat :u form-spec)
    :ret #(s/valid? user-spec %)
    ;; useful to map inputs to outputs. kinda dependent typing.
    ;; here we're asserting that the input and output emails must match
    :fn #(= (-> % :args :u :email) (-> % :ret ::email)))


    ;;; more specs
    (s/def ::uuid #(instance? java.util.UUID %))
    (s/def ::username (s/and string? #(= % (str/lower-case %))))

    ;;; gladly reusing previous specs
    ;;; is there a better way to compose specs?
    (def enriched-user-spec (s/keys :req [::first-name ::last-name ::email
    ::uuid ::username]
    :opt [::phone]))

    ;;; Specify cleanup-user
    (s/fdef cleanup-user
    :args (s/cat :u user-spec)
    :ret #(s/valid? enriched-user-spec %))



    ;;; try these inputs
    (def good-inputs [["ANthony Gonsalves" "[email protected]"]
    ["ANthony Gonsalves" "[email protected]" "1234567890"]])

    (def bad-inputs [["ANthony Gonsalves" "anthony@gmail"]
    ["ANthony Gonsalves" "[email protected]" "12367890"]
    ["ANthony Gonsalves" "[email protected]" 1234567890]])

    ;;; switch instrumentation on/off

    ;; (do (s/instrument #'make-user)
    ;; (s/instrument #'cleanup-user))


    ;; (do (s/unstrument #'make-user)
    ;; (s/unstrument #'cleanup-user))

    ;;; if you're working on the REPL, expect to reset instrumentation multiple
    ;;; times.