Switch to simpler extend-freeze, extend-thaw custom type API (ALPHA)
This commit is contained in:
parent
c2a964932c
commit
4071d0f3ec
2 changed files with 58 additions and 44 deletions
|
|
@ -108,22 +108,6 @@
|
||||||
(write-id ~'s ~id)
|
(write-id ~'s ~id)
|
||||||
~@body)))
|
~@body)))
|
||||||
|
|
||||||
(defmacro custom-freezer
|
|
||||||
"Helper to extend Freezable protocol to custom types with id ∈[1, 128]:
|
|
||||||
(defrecord MyType [data])
|
|
||||||
(custom-freezer MyType 1 x s (.writeUTF s (:data x)))"
|
|
||||||
[type id x data-output-stream & body]
|
|
||||||
(assert (and (>= id 1) (<= id 128)))
|
|
||||||
`(extend-type ~type
|
|
||||||
Freezable
|
|
||||||
(~'freeze-to-stream* [~x ~(with-meta data-output-stream
|
|
||||||
{:tag 'DataOutputStream})]
|
|
||||||
(write-id ~data-output-stream ~(int (- id)))
|
|
||||||
~@body)))
|
|
||||||
|
|
||||||
(comment (defrecord MyType [data])
|
|
||||||
(custom-freezer MyType 1 x s (.writeUTF s (:data x))))
|
|
||||||
|
|
||||||
(defmacro ^:private coll-freezer
|
(defmacro ^:private coll-freezer
|
||||||
"Extends Freezable to simple collection types."
|
"Extends Freezable to simple collection types."
|
||||||
[type id & body]
|
[type id & body]
|
||||||
|
|
@ -193,7 +177,7 @@
|
||||||
(defn freeze
|
(defn freeze
|
||||||
"Serializes arg (any Clojure data type) to a byte array. Set :legacy-mode to
|
"Serializes arg (any Clojure data type) to a byte array. Set :legacy-mode to
|
||||||
true to produce bytes readble by Nippy < 2.x. For custom types extend the
|
true to produce bytes readble by Nippy < 2.x. For custom types extend the
|
||||||
Clojure reader or see `custom-freezer`."
|
Clojure reader or see `extend-freeze`."
|
||||||
^bytes [x & [{:keys [print-dup? password compressor encryptor legacy-mode]
|
^bytes [x & [{:keys [print-dup? password compressor encryptor legacy-mode]
|
||||||
:or {print-dup? true
|
:or {print-dup? true
|
||||||
compressor snappy-compressor
|
compressor snappy-compressor
|
||||||
|
|
@ -233,8 +217,10 @@
|
||||||
(utils/repeatedly-into ~coll (/ (.readInt s#) 2)
|
(utils/repeatedly-into ~coll (/ (.readInt s#) 2)
|
||||||
[(thaw-from-stream s#) (thaw-from-stream s#)])))
|
[(thaw-from-stream s#) (thaw-from-stream s#)])))
|
||||||
|
|
||||||
|
(declare ^:private custom-readers)
|
||||||
|
|
||||||
(defn- thaw-from-stream
|
(defn- thaw-from-stream
|
||||||
[^DataInputStream s & [readers]]
|
[^DataInputStream s]
|
||||||
(let [type-id (.readByte s)]
|
(let [type-id (.readByte s)]
|
||||||
(utils/case-eval type-id
|
(utils/case-eval type-id
|
||||||
|
|
||||||
|
|
@ -279,23 +265,24 @@
|
||||||
(* 2 (.readInt s)) (thaw-from-stream s)))
|
(* 2 (.readInt s)) (thaw-from-stream s)))
|
||||||
id-old-keyword (keyword (.readUTF s))
|
id-old-keyword (keyword (.readUTF s))
|
||||||
|
|
||||||
;;; Custom types
|
(if-not (neg? type-id)
|
||||||
(or (when-let [reader (get readers (- type-id))]
|
(throw (Exception. (str "Unknown type ID: " type-id)))
|
||||||
|
|
||||||
|
;; Custom types
|
||||||
|
(if-let [reader (get @custom-readers type-id)]
|
||||||
(try (reader s)
|
(try (reader s)
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(throw (Exception. (str "Reader exception for custom type ID: "
|
(throw (Exception. (str "Reader exception for custom type ID: "
|
||||||
(- type-id)) e)))))
|
(- type-id)) e))))
|
||||||
(if (neg? type-id)
|
|
||||||
(throw (Exception. (str "No reader provided for custom type ID: "
|
(throw (Exception. (str "No reader provided for custom type ID: "
|
||||||
(- type-id))))
|
(- type-id)))))))))
|
||||||
(throw (Exception. (str "Unknown type ID: " type-id))))))))
|
|
||||||
|
|
||||||
(defn thaw-from-stream!
|
(defn thaw-from-stream!
|
||||||
"Low-level API. Deserializes a frozen object from given DataInputStream to its
|
"Low-level API. Deserializes a frozen object from given DataInputStream to its
|
||||||
original Clojure data type."
|
original Clojure data type."
|
||||||
[data-input-stream & [{:keys [read-eval? readers]}]]
|
[data-input-stream & [{:keys [read-eval?]}]]
|
||||||
(binding [*read-eval* read-eval?]
|
(binding [*read-eval* read-eval?]
|
||||||
(thaw-from-stream data-input-stream readers)))
|
(thaw-from-stream data-input-stream)))
|
||||||
|
|
||||||
(defn- try-parse-header [ba]
|
(defn- try-parse-header [ba]
|
||||||
(when-let [[head-ba data-ba] (utils/ba-split ba 4)]
|
(when-let [[head-ba data-ba] (utils/ba-split ba 4)]
|
||||||
|
|
@ -306,11 +293,7 @@
|
||||||
(defn thaw
|
(defn thaw
|
||||||
"Deserializes a frozen object from given byte array to its original Clojure
|
"Deserializes a frozen object from given byte array to its original Clojure
|
||||||
data type. Supports data frozen with current and all previous versions of
|
data type. Supports data frozen with current and all previous versions of
|
||||||
Nippy.
|
Nippy. For custom types extend the Clojure reader or see `extend-thaw`.
|
||||||
|
|
||||||
For custom `Freezable` types provide a `:readers` arg:
|
|
||||||
(thaw (freeze (MyType. \"Joe\"))
|
|
||||||
{:readers {1 (fn [^DataInputStream stream] (.readUTF stream))}})
|
|
||||||
|
|
||||||
WARNING: Enabling `:read-eval?` can lead to security vulnerabilities unless
|
WARNING: Enabling `:read-eval?` can lead to security vulnerabilities unless
|
||||||
you are sure you know what you're doing."
|
you are sure you know what you're doing."
|
||||||
|
|
@ -333,7 +316,7 @@
|
||||||
ba (if compressor (compression/decompress compressor ba) ba)
|
ba (if compressor (compression/decompress compressor ba) ba)
|
||||||
stream (DataInputStream. (ByteArrayInputStream. ba))]
|
stream (DataInputStream. (ByteArrayInputStream. ba))]
|
||||||
|
|
||||||
(thaw-from-stream! stream {:read-eval? read-eval? :readers readers}))
|
(thaw-from-stream! stream {:read-eval? read-eval?}))
|
||||||
|
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(cond
|
(cond
|
||||||
|
|
@ -372,6 +355,39 @@
|
||||||
(thaw (freeze "hello" {:password [:salted "p"]})) ; ex
|
(thaw (freeze "hello" {:password [:salted "p"]})) ; ex
|
||||||
(thaw (freeze "hello") {:password [:salted "p"]}))
|
(thaw (freeze "hello") {:password [:salted "p"]}))
|
||||||
|
|
||||||
|
;;;; Custom types
|
||||||
|
|
||||||
|
(defmacro extend-freeze
|
||||||
|
"Alpha - subject to change.
|
||||||
|
Extends Nippy to support freezing of a custom type with id ∈[1, 128]:
|
||||||
|
(defrecord MyType [data])
|
||||||
|
(extend-freeze MyType 1 [x data-output-stream]
|
||||||
|
(.writeUTF [data-output-stream] (:data x)))"
|
||||||
|
[type custom-type-id [x stream] & body]
|
||||||
|
(assert (and (>= custom-type-id 1) (<= custom-type-id 128)))
|
||||||
|
`(extend-type ~type
|
||||||
|
Freezable
|
||||||
|
(~'freeze-to-stream* [~x ~(with-meta stream {:tag 'java.io.DataOutputStream})]
|
||||||
|
(write-id ~stream ~(int (- custom-type-id)))
|
||||||
|
~@body)))
|
||||||
|
|
||||||
|
(defonce custom-readers (atom {})) ; {<custom-type-id> (fn [data-input-stream]) ...}
|
||||||
|
(defmacro extend-thaw
|
||||||
|
"Alpha - subject to change.
|
||||||
|
Extends Nippy to support thawing of a custom type with id ∈[1, 128]:
|
||||||
|
(extend-thaw 1 [data-input-stream]
|
||||||
|
(->MyType (.readUTF data-input-stream)))"
|
||||||
|
[custom-type-id [stream] & body]
|
||||||
|
(assert (and (>= custom-type-id 1) (<= custom-type-id 128)))
|
||||||
|
`(swap! custom-readers assoc ~(int (- custom-type-id))
|
||||||
|
(fn [~(with-meta stream {:tag 'java.io.DataInputStream})]
|
||||||
|
~@body)))
|
||||||
|
|
||||||
|
(comment (defrecord MyType [data])
|
||||||
|
(extend-freeze MyType 1 [x s] (.writeUTF s (:data x)))
|
||||||
|
(extend-thaw 1 [s] (->MyType (.readUTF s)))
|
||||||
|
(thaw (freeze (->MyType "Joe"))))
|
||||||
|
|
||||||
;;;; Stress data
|
;;;; Stress data
|
||||||
|
|
||||||
(def stress-data "Reference data used for tests & benchmarks."
|
(def stress-data "Reference data used for tests & benchmarks."
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
(ns taoensso.nippy.tests.main
|
(ns taoensso.nippy.tests.main
|
||||||
(:require [expectations :as test :refer :all]
|
(:require [expectations :as test :refer :all]
|
||||||
[taoensso.nippy :as nippy :refer (freeze thaw)]
|
[taoensso.nippy :as nippy :refer (freeze thaw)]
|
||||||
[taoensso.nippy.benchmarks :as benchmarks])
|
[taoensso.nippy.benchmarks :as benchmarks]))
|
||||||
(:import [java.io DataInputStream DataOutputStream]))
|
|
||||||
|
|
||||||
;; Remove stuff from stress-data that breaks roundtrip equality
|
;; Remove stuff from stress-data that breaks roundtrip equality
|
||||||
(def test-data (dissoc nippy/stress-data :bytes))
|
(def test-data (dissoc nippy/stress-data :bytes))
|
||||||
|
|
@ -33,10 +32,9 @@
|
||||||
|
|
||||||
;;; Custom types
|
;;; Custom types
|
||||||
(defrecord MyType [data])
|
(defrecord MyType [data])
|
||||||
(nippy/custom-freezer MyType 1 x s (.writeUTF s (:data x)))
|
(nippy/extend-freeze MyType 1 [x s] (.writeUTF s (:data x)))
|
||||||
|
(expect Exception (thaw (freeze (->MyType "Joe"))))
|
||||||
(expect Exception (thaw (freeze (MyType. "Joe"))))
|
(expect (MyType. "Joe") (do (nippy/extend-thaw 1 [s] (->MyType (.readUTF s)))
|
||||||
(expect "Joe" (thaw (freeze (MyType. "Joe"))
|
(thaw (freeze (->MyType "Joe")))))
|
||||||
{:readers {1 (fn [^DataInputStream s] (.readUTF s))}}))
|
|
||||||
|
|
||||||
(expect (benchmarks/bench {:reader? false})) ; Also tests :cached passwords
|
(expect (benchmarks/bench {:reader? false})) ; Also tests :cached passwords
|
||||||
Loading…
Reference in a new issue