Skip to content

Commit

Permalink
Merge pull request #75 from crisptrutski/sorted-by-default
Browse files Browse the repository at this point in the history
Support configurable serialization
  • Loading branch information
crisptrutski committed Jun 7, 2016
2 parents 24da666 + 976d381 commit 8c40fd1
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 56 deletions.
5 changes: 3 additions & 2 deletions build.boot
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
;; optional namespace dependencies
[org.clojure/core.async "0.2.374" :scope "provided"]
[reagent "0.6.0-alpha" :scope "provided"]
[frankiesardo/linked "1.2.6" :scope "provided"]
;; build tooling
[adzerk/boot-cljs "1.7.228-1" :scope "test"]
[adzerk/bootlaces "0.1.13" :scope "test"]
[adzerk/boot-test "1.1.1"]
[crisptrutski/boot-cljs-test "0.2.2-SNAPSHOT"]]
[adzerk/boot-test "1.1.1" :scope "test"]
[crisptrutski/boot-cljs-test "0.2.2-SNAPSHOT" :scope "test"]]
:source-paths #{"src"})

(require
Expand Down
1 change: 1 addition & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
[org.clojure/clojurescript "1.8.34" :scope "provided"]
[org.clojure/core.async "0.2.374" :scope "provided"]
[reagent "0.6.0-alpha" :scope "provided"]
[frankiesardo/linked "1.2.6" :scope "provided"]
[com.firebase/firebase-client-jvm "2.5.2" :exclusions [org.apache.httpcomponents/httpclient]]
[org.apache.httpcomponents/httpclient "4.5.2"]
[cljsjs/firebase "2.4.1-0"]
Expand Down
25 changes: 4 additions & 21 deletions src/matchbox/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
[clojure.walk :as walk]
[matchbox.utils :as utils]
[matchbox.registry :refer [register-listener register-auth-listener disable-auth-listener!]]
[matchbox.serialization.keyword :as keyword]
#?(:cljs cljsjs.firebase)))

;; constants
Expand Down Expand Up @@ -115,29 +116,11 @@
(defn- strip-prefix [type]
(-> type name (str/replace #"^.+\-" "") keyword)))

(defn- keywords->strings [x]
(if (keyword? x) (str x) x))
(def data-config (utils/->Serializer keyword/hydrate keyword/serialize))

(defn- hydrate-keywords [x]
(if (and (string? x) (= \: (first x))) (keyword (subs x 1)) x))
(defn hydrate [x] (utils/hydrate data-config x))

#?(:clj
(defn- hydrate* [x]
(cond (instance? HashMap x) (recur (into {} x))
(instance? ArrayList x) (recur (into [] x))
(map? x) (zipmap (map keyword (keys x)) (vals x))
:else (hydrate-keywords x))))

(defn hydrate [v]
#?(:clj (walk/prewalk hydrate* v)
:cljs (walk/postwalk
hydrate-keywords
(js->clj v :keywordize-keys true))))

(defn serialize [v]
(->> (walk/stringify-keys v)
(walk/postwalk keywords->strings)
#?(:cljs clj->js)))
(defn serialize [x] (utils/serialize data-config x))

(defn key
"Last segment in reference or snapshot path"
Expand Down
25 changes: 25 additions & 0 deletions src/matchbox/serialization/keyword.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(ns matchbox.serialization.keyword
(:require
[matchbox.serialization.plain :as plain]
[matchbox.utils :as utils]
[clojure.walk :as walk]))

(defn hydrate-kw [x]
(if (and (string? x) (= \: (first x))) (keyword (subs x 1)) x))

(defn hydrate-map [x]
(if (map? x) (into (empty x) (map (fn [[k v]] [(keyword k) v]) x)) x))

(defn hydrate [v]
#?(:clj (walk/prewalk (comp hydrate-kw hydrate-map plain/hydrate-raw) v)
:cljs (walk/postwalk (comp hydrate-kw hydrate-map) (js->clj v :keywordize-keys true))))

(defn kw->str [x] (if (keyword? x) (str x) x))

(defn serialize [v]
(->> (walk/stringify-keys v)
(walk/postwalk kw->str)
#?(:cljs clj->js)))

(defn set-default! []
(utils/set-date-config! hydrate serialize))
24 changes: 24 additions & 0 deletions src/matchbox/serialization/plain.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(ns matchbox.serialization.plain
(:require
[clojure.walk :as walk]
[matchbox.utils :as utils])
#?(:clj (:import (java.util HashMap ArrayList))))

(defn hydrate-raw [x]
#?(:cljs x
:clj
(cond
(instance? HashMap x) (recur (into {} x))
(instance? ArrayList x) (recur (into [] x))
:else x)))

(defn hydrate [v]
#?(:clj (walk/prewalk hydrate-raw v)
:cljs (js->clj v)))

(defn serialize [v]
#?(:clj (walk/stringify-keys v)
:cljs (clj->js v)))

(defn set-default! []
(utils/set-date-config! hydrate serialize))
25 changes: 25 additions & 0 deletions src/matchbox/serialization/sorted.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(ns matchbox.serialization.sorted
(:require
[clojure.walk :as walk]
[matchbox.serialization.keyword :as keyword]
[matchbox.utils :as utils])
#?(:clj (:import (java.util HashMap ArrayList))))

(defn map->sorted [m]
(if (sorted? m) m (into (sorted-map) (map (juxt (comp keyword key) val) m))))

(defn hydrate-shallow [x]
(cond
#?@(:clj [(instance? HashMap x) (recur (map->sorted x))
(instance? ArrayList x) (recur (into [] x))])
(map? x) (map->sorted x)
#?@(:cljs [(array? x) (vec x)
(identical? js/Object (type x)) (into (sorted-map) (for [k (js-keys x)] [(keyword k) (aget x k)]))])
:else (keyword/hydrate-kw x)))

(defn hydrate [v] (walk/prewalk hydrate-shallow v))

(def serialize keyword/serialize)

(defn set-default! []
(utils/set-date-config! hydrate serialize))
26 changes: 26 additions & 0 deletions src/matchbox/serialization/stable.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(ns matchbox.serialization.stable
(:require
[clojure.walk :as walk]
[matchbox.serialization.keyword :as keyword]
[linked.map]
[matchbox.utils :as utils])
#?(:clj (:import (java.util HashMap ArrayList))))

(defn map->linked [m]
(if (instance? linked.map.LinkedMap m) m (linked.map/->linked-map (map (juxt (comp keyword key) val) m))))

(defn hydrate-shallow [x]
(cond
#?@(:clj [(instance? HashMap x) (recur (map->linked x))
(instance? ArrayList x) (recur (into [] x))])
(map? x) (map->linked x)
#?@(:cljs [(array? x) (vec x)
(identical? js/Object (type x)) (linked.map/->linked-map (for [k (js-keys x)] [(keyword k) (aget x k)]))])
:else (keyword/hydrate-kw x)))

(defn hydrate [v] (walk/prewalk hydrate-shallow v))

(def serialize keyword/serialize)

(defn set-default! []
(utils/set-date-config! hydrate serialize))
23 changes: 21 additions & 2 deletions src/matchbox/utils.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
(str/join "/" (map name korks))
(when korks (name korks))))

(defn no-op [& _])
(defn no-op ([_]) ([_ _]) ([_ _ _]) ([_ _ _ & _]))

(defn extract-cb [args]
(if (and (>= (count args) 2)
Expand All @@ -23,9 +23,28 @@

;;

(defprotocol ISerializer
(hydrate [this x])
(serialize [this x])
(config! [this hydrate serialize]))

(deftype Serializer
[#?(:clj ^:volatile-mutable hydrate :cljs ^:mutable hydrate)
#?(:clj ^:volatile-mutable serialize :cljs ^:mutable serialize)]
ISerializer
(hydrate [_ x] (hydrate x))
(serialize [_ x] (serialize x))
(config! [_ h s] (set! hydrate h) (set! serialize s)))

(defn set-date-config! [hydrate serialize]
(-> ^Serializer
#?(:clj @(resolve 'matchbox.core/data-config)
:cljs matchbox.core/data-config)
(config! hydrate serialize)))

#?(:clj (def repl-out *out*))

#?(:cj
#?(:clj
(defn prn
"Like clojure.core/prn, but always bound to root thread's *out*"
[& args]
Expand Down
4 changes: 2 additions & 2 deletions test/matchbox/core_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@
;; (sorts by name on equality)
(m/set-priority! child-1 "a")
(m/set-priority-in! ref (m/key child-2) 0)
(m/deref ref (fn [v] (is (= [3 2 1] (vals v))) (done))))))
(m/deref-list ref (fn [v] (is (= [3 2 1] v)) (done))))))

(deftest reset-with-priority!-test
(async done
(let [ref (random-ref)]
(m/reset-with-priority-in! ref "a" 1 "a")
(m/reset-with-priority-in! ref "b" 2 0)
(m/reset-in! ref "c" 3)
(m/deref ref (fn [v] (is (= [3 2 1] (vals v))) (done))))))
(m/deref-list ref (fn [v] (is (= [3 2 1] v)) (done))))))

(deftest disconnect!-reconnect!-test
;; default is connected
Expand Down
80 changes: 51 additions & 29 deletions test/matchbox/serialization_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,51 @@
[cljs.test :refer [deftest is testing async]]
[cljs.core.async.macros :refer [go]]))
(:require
#?(:clj [clojure.test :refer [deftest is testing]]
:cljs [cljs.test])
[matchbox.core :as m]
[matchbox.testing :refer [db-uri random-ref]]
[matchbox.testing
#?@(:clj [:refer [is=
round-trip=
round-trip<]]
:cljs [:refer-macros [is=
round-trip=
round-trip<]
:include-macros true])]
[#?(:clj clojure.core.async
:cljs cljs.core.async)
:refer [chan <! >! #?(:clj go)]]))
[matchbox.testing #?@(:clj [:refer [is= round-trip= round-trip<]]
:cljs [:refer-macros [is= round-trip= round-trip<] :include-macros true])]
#?(:clj [clojure.test :refer [deftest is testing]] :cljs [cljs.test])
[#?(:clj clojure.core.async :cljs cljs.core.async) :refer [chan <! >! #?(:clj go)]]
[matchbox.serialization.plain :as plain]
[matchbox.serialization.keyword :as keyword]
[matchbox.serialization.sorted :as sorted]
[matchbox.serialization.stable :as stable])
#?(:clj (:import (java.util HashMap))))

;; serialize / deserialize (specs)

#?(:clj
(deftest serialize-test
;; map keys from keywords -> strings
(let [orig {:hello "world" :bye "world"}
tran (m/serialize orig)]
tran #?(:clj (m/serialize orig)
:cljs (js->clj (m/serialize orig)))]
(is (contains? tran "hello"))
(is (not (contains? tran :hello))))
;; values are unchanged
(testing "bool, long, float, vector, string, set, list"
(doseq [x [true [1 2 3 4] 150.0 "hello" #{1 2 3} '(3 2 1)]]
(is (= x (m/serialize x)))))
(is (= #?(:clj x :cljs (js->clj (clj->js x)))
#?(:clj (m/serialize x)
:cljs (js->clj (m/serialize x)))))))
(testing "keyword"
(is (= ":a" (m/serialize :a))))))
(is (= ":a" (m/serialize :a)))))

#?(:clj
(deftest hydrate-test
;; map keys from strings -> keywords
(let [orig {"hello" "world" "bye" "world"}
tran (m/hydrate orig)]
(is (contains? tran :hello))
(is (not (contains? tran "hello"))))
(is (not (contains? (set (keys tran)) "hello"))))
;; values are unchanged
(testing "bool, long, float, vector, string, keyword, set, list"
(doseq [x [true [1 2 3 4] 150.0 "hello" :a #{1 2 3} '(3 2 1)]]
(is (= x (m/hydrate x)))))))
(is (= x (m/hydrate x))))))

(deftest serialize-hydrate-test
(is (= {:a 1, :b #?(:cljs [:b :a] :clj #{:a :b})}
(m/hydrate
(m/serialize {"a" 1, "b" #{:a :b}})))))
(m/serialize {"a" 1, "b" #{:a :b}})))))


;; round trip (integration)
Expand All @@ -68,12 +65,12 @@
;; Cast down from extended types though
(round-trip= 3.0 3N)
(round-trip< 4M value
#?(:clj (is (instance? Double value))
:cljs (is (= js/Number (type value)))))
#?(:clj (is (instance? Double value))
:cljs (is (= js/Number (type value)))))
;; Unless the decimal portion is explicit..
(round-trip< 4.0M value
#?(:clj (is (instance? Double value))
:cljs (is (= js/Number (type value)))))
#?(:clj (is (instance? Double value))
:cljs (is (= js/Number (type value)))))
#?(:clj
(round-trip= 4.3E90 4.3E90M)))

Expand All @@ -86,9 +83,9 @@

(testing "map"
(round-trip= {:a 1, :b 2}
{"a" 1, :b 2})
{"a" 1, :b 2})
(round-trip= {:nested {:data 4}}
{"nested" {"data" 4}}))
{"nested" {"data" 4}}))

(testing "list"
(round-trip= [1 2 3 4] '(1 2 3 4)))
Expand All @@ -98,9 +95,34 @@

(testing "set"
(round-trip< #{1 2 3 4} result
(is (vector? result))
(is (= [1 2 3 4] (sort result)))))
(is (vector? result))
(is (= [1 2 3 4] (sort result)))))

(testing "richer data"
(round-trip= {:a-field [2]} (ARecord. [2]))
(round-trip= {:an_attr [3]} (AType. #{3}))))


(def ref-map
(reduce
(fn [#?(:clj ^HashMap acc :cljs acc) [k v]]
#?(:clj (.put acc k v) :cljs (aset acc k v))
acc)
(let [base {"a" {"b" {"c" [{"d" "e" "f" ":f"}]}}}]
#?(:clj (HashMap. base) :cljs (clj->js base)))
(map vector (map (comp str char) (range 98 123)) (range 25))))

(deftest custom-serializers-test
(testing "Keys as expected"
(is (every? string? (keys (plain/hydrate ref-map))))
(is (every? keyword? (keys (keyword/hydrate ref-map))))
(is (every? keyword? (keys (sorted/hydrate ref-map))))
(is (every? keyword? (keys (stable/hydrate ref-map)))))
(testing "Values as expected"
(is (= ":f" (get-in (plain/hydrate ref-map) ["a" "b" "c" 0 "f"])))
(is (= :f (get-in (keyword/hydrate ref-map) [:a :b :c 0 :f])))
(is (= :f (get-in (sorted/hydrate ref-map) [:a :b :c 0 :f])))
(is (= :f (get-in (stable/hydrate ref-map) [:a :b :c 0 :f]))))
(testing "Sorted as expected"
(is (= (sort (keys (sorted/hydrate ref-map))) (keys (sorted/hydrate ref-map))))
(is (= (sort (keys (stable/hydrate ref-map))) (keys (stable/hydrate ref-map))))))

0 comments on commit 8c40fd1

Please sign in to comment.