Add support for custom thaw readers
This commit is contained in:
parent
8e4cc072e3
commit
99091b0a32
2 changed files with 47 additions and 12 deletions
|
|
@ -23,6 +23,9 @@
|
||||||
|
|
||||||
;;;; Data type IDs
|
;;;; Data type IDs
|
||||||
|
|
||||||
|
;; **Negative ids reserved for user-defined types**
|
||||||
|
|
||||||
|
(def ^:const id-reserved (int 0))
|
||||||
;; 1
|
;; 1
|
||||||
(def ^:const id-bytes (int 2))
|
(def ^:const id-bytes (int 2))
|
||||||
(def ^:const id-nil (int 3))
|
(def ^:const id-nil (int 3))
|
||||||
|
|
@ -65,7 +68,7 @@
|
||||||
(def ^:const id-old-keyword (int 12)) ; as of 2.0.0-alpha5, for str consistecy
|
(def ^:const id-old-keyword (int 12)) ; as of 2.0.0-alpha5, for str consistecy
|
||||||
|
|
||||||
;;;; Freezing
|
;;;; Freezing
|
||||||
(defprotocol Freezable (freeze-to-stream* [this stream]))
|
(defprotocol Freezable (freeze-to-stream* [this ^DataOutputStream stream]))
|
||||||
|
|
||||||
(defmacro ^:private write-id [s id] `(.writeByte ~s ~id))
|
(defmacro ^:private write-id [s id] `(.writeByte ~s ~id))
|
||||||
(defmacro ^:private write-bytes [s ba]
|
(defmacro ^:private write-bytes [s ba]
|
||||||
|
|
@ -173,7 +176,14 @@
|
||||||
|
|
||||||
(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."
|
true to produce bytes readble by Nippy < 2.x.
|
||||||
|
|
||||||
|
For custom types extend the Clojure reader or Nippy's `Freezable` protocol:
|
||||||
|
(defrecord MyType [data]
|
||||||
|
nippy/Freezable
|
||||||
|
(freeze-to-stream* [x stream]
|
||||||
|
(.writeByte stream -1) ; Custom type id ∈ [-128, -1]
|
||||||
|
(.writeUTF stream (:data x))))"
|
||||||
^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
|
||||||
|
|
@ -214,10 +224,9 @@
|
||||||
[(thaw-from-stream s#) (thaw-from-stream s#)])))
|
[(thaw-from-stream s#) (thaw-from-stream s#)])))
|
||||||
|
|
||||||
(defn- thaw-from-stream
|
(defn- thaw-from-stream
|
||||||
[^DataInputStream s]
|
[^DataInputStream s & [readers]]
|
||||||
(let [type-id (.readByte s)]
|
(let [type-id (.readByte s)]
|
||||||
(utils/case-eval
|
(utils/case-eval type-id
|
||||||
type-id
|
|
||||||
|
|
||||||
id-reader (read-string (read-utf8 s))
|
id-reader (read-string (read-utf8 s))
|
||||||
id-bytes (read-bytes s)
|
id-bytes (read-bytes s)
|
||||||
|
|
@ -260,14 +269,22 @@
|
||||||
(* 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))
|
||||||
|
|
||||||
(throw (Exception. (str "Failed to thaw unknown type ID: " type-id))))))
|
;;; Custom types
|
||||||
|
(or (when-let [reader (get readers type-id)]
|
||||||
|
(try (reader s)
|
||||||
|
(catch Exception e
|
||||||
|
(throw (Exception. (str "Reader exception for custom type ID: "
|
||||||
|
type-id) e)))))
|
||||||
|
(if (neg? type-id)
|
||||||
|
(throw (Exception. (str "No reader provided for custom 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?]}]]
|
[data-input-stream & [{:keys [read-eval? readers]}]]
|
||||||
(binding [*read-eval* read-eval?]
|
(binding [*read-eval* read-eval?]
|
||||||
(thaw-from-stream data-input-stream)))
|
(thaw-from-stream data-input-stream readers)))
|
||||||
|
|
||||||
(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)]
|
||||||
|
|
@ -280,9 +297,12 @@
|
||||||
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 `Freezable` types provide a `:readers` arg:
|
||||||
|
(thaw (freeze (MyType. \"Joe\")) {:readers {-1 (fn [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."
|
||||||
[^bytes ba & [{:keys [read-eval? password compressor encryptor legacy-opts]
|
[^bytes ba & [{:keys [read-eval? password compressor encryptor legacy-opts readers]
|
||||||
:or {legacy-opts {:compressed? true}
|
:or {legacy-opts {:compressed? true}
|
||||||
compressor snappy-compressor
|
compressor snappy-compressor
|
||||||
encryptor aes128-encryptor}
|
encryptor aes128-encryptor}
|
||||||
|
|
@ -300,7 +320,9 @@
|
||||||
ba (if password (encryption/decrypt encryptor password ba) ba)
|
ba (if password (encryption/decrypt encryptor password ba) ba)
|
||||||
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?}))
|
|
||||||
|
(thaw-from-stream! stream {:read-eval? read-eval? :readers readers}))
|
||||||
|
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(cond
|
(cond
|
||||||
password (ex "Wrong password/encryptor?" e)
|
password (ex "Wrong password/encryptor?" e)
|
||||||
|
|
@ -323,7 +345,9 @@
|
||||||
:else (try (try-thaw-data data-ba head-meta)
|
:else (try (try-thaw-data data-ba head-meta)
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(if legacy-opts
|
(if legacy-opts
|
||||||
(try-thaw-data ba nil)
|
(try (try-thaw-data ba nil)
|
||||||
|
(catch Exception _
|
||||||
|
(throw e)))
|
||||||
(throw e)))))
|
(throw e)))))
|
||||||
|
|
||||||
;; Header definitely not okay
|
;; Header definitely not okay
|
||||||
|
|
@ -406,4 +430,4 @@
|
||||||
:or {compressed? true}}]
|
:or {compressed? true}}]
|
||||||
(thaw ba {:legacy-opts {:compressed? compressed?}
|
(thaw ba {:legacy-opts {:compressed? compressed?}
|
||||||
:read-eval? read-eval?
|
:read-eval? read-eval?
|
||||||
:password nil}))
|
:password nil}))
|
||||||
|
|
@ -30,4 +30,15 @@
|
||||||
(thaw (org.iq80.snappy.Snappy/uncompress iq80-ba 0 (alength iq80-ba)))
|
(thaw (org.iq80.snappy.Snappy/uncompress iq80-ba 0 (alength iq80-ba)))
|
||||||
(thaw (org.iq80.snappy.Snappy/uncompress xerial-ba 0 (alength xerial-ba))))))
|
(thaw (org.iq80.snappy.Snappy/uncompress xerial-ba 0 (alength xerial-ba))))))
|
||||||
|
|
||||||
|
;;; Custom types
|
||||||
|
(defrecord MyType [data]
|
||||||
|
nippy/Freezable
|
||||||
|
(freeze-to-stream* [x s]
|
||||||
|
(.writeByte s -1)
|
||||||
|
(.writeUTF s (:data x))))
|
||||||
|
|
||||||
|
(expect Exception (thaw (freeze (MyType. "Joe"))))
|
||||||
|
(expect "Joe" (thaw (freeze (MyType. "Joe"))
|
||||||
|
{:readers {-1 (fn [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