[new] Use Truss exceptions on errors

Ref. https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#*ctx*
This commit is contained in:
Peter Taoussanis 2025-04-14 18:22:51 +02:00
parent da57206b0d
commit 8d62dc2826
6 changed files with 87 additions and 86 deletions

View file

@ -4,6 +4,7 @@
(:require
[clojure.string :as str]
[clojure.java.io :as jio]
[taoensso.truss :as truss]
[taoensso.encore :as enc]
[taoensso.nippy
[impl :as impl]
@ -695,7 +696,7 @@
(sm-count? len) (do (write-id out id-kw-sm) (write-sm-count out len))
(md-count? len) (do (write-id out id-kw-md) (write-md-count out len))
;; :else (do (write-id out id-kw-lg) (write-lg-count out len)) ; Unrealistic
:else (throw (ex-info "Keyword too long" {:name s})))
:else (truss/ex-info! "Keyword too long" {:name s}))
(.write out ba 0 len)))
@ -707,7 +708,7 @@
(sm-count? len) (do (write-id out id-sym-sm) (write-sm-count out len))
(md-count? len) (do (write-id out id-sym-md) (write-md-count out len))
;; :else (do (write-id out id-sym-lg) (write-lg-count out len)) ; Unrealistic
:else (throw (ex-info "Symbol too long" {:name s})))
:else (truss/ex-info! "Symbol too long" {:name s}))
(.write out ba 0 len)))
@ -906,7 +907,7 @@
(sm-count? len) (do (write-id out id-sz-quarantined-sm) (write-bytes-sm out class-name-ba))
(md-count? len) (do (write-id out id-sz-quarantined-md) (write-bytes-md out class-name-ba))
;; :else (do (write-id out id-sz-quarantined-lg) (write-bytes-md out class-name-ba)) ; Unrealistic
:else (throw (ex-info "Serializable class name too long" {:name class-name})))
:else (truss/ex-info! "Serializable class name too long" {:name class-name}))
;; Legacy: write object directly to out.
;; (.writeObject (ObjectOutputStream. out) x)
@ -931,8 +932,8 @@
:else (do (write-id out id-reader-lg) (write-bytes-lg out edn-ba)))
true)))
(defn ^:deprecated try-write-serializable [out x] (enc/catching (write-serializable out x)))
(defn ^:deprecated try-write-readable [out x] (enc/catching (write-readable out x)))
(defn ^:deprecated try-write-serializable [out x] (truss/catching :all (write-serializable out x)))
(defn ^:deprecated try-write-readable [out x] (truss/catching :all (write-readable out x)))
(defn- try-pr-edn [x]
(try
@ -1028,7 +1029,7 @@
(when first-occurance? (-freeze-with-meta! x-val out)))
:else
;; (throw (ex-info "Max cache size exceeded" {:idx idx}))
;; (truss/ex-info! "Max cache size exceeded" {:idx idx})
(-freeze-with-meta! x-val out) ; Just freeze uncached
))
@ -1045,7 +1046,7 @@
(vswap! cache_ assoc idx x)
x)
v))
(throw (ex-info "Can't thaw without cache available. See `with-cache`." {}))))))
(truss/ex-info! "Can't thaw without cache available. See `with-cache`." {})))))
(comment
(thaw (freeze [(cache "foo") (cache "foo") (cache "foo")]))
@ -1129,7 +1130,7 @@
(sm-count? len) (do (write-id out id-record-sm) (write-bytes-sm out class-name-ba))
(md-count? len) (do (write-id out id-record-md) (write-bytes-md out class-name-ba))
;; :else (do (write-id out id-record-lg) (write-bytes-md out class-name-ba)) ; Unrealistic
:else (throw (ex-info "Record class name too long" {:name class-name})))
:else (truss/ex-info! "Record class name too long" {:name class-name}))
(-freeze-without-meta! (into {} x) out)))
@ -1191,14 +1192,13 @@
:if-let [fff *final-freeze-fallback*] (fff out x) ; Deprecated
:else
(let [t (type x)]
(throw
(ex-info (str "Failed to freeze type: " t)
(truss/ex-info! (str "Failed to freeze type: " t)
(enc/assoc-some
{:type t
:as-str (try-pr-edn x)}
{:serializable-error e1
:readable-error e2})
(or e1 e2))))))))
(or e1 e2)))))))
;;;;
@ -1212,9 +1212,8 @@
(defn- wrap-header [data-ba head-meta]
(if-let [head-ba (get-head-ba head-meta)]
(enc/ba-concat head-ba data-ba)
(throw
(ex-info (str "Unrecognized header meta: " head-meta)
{:head-meta head-meta}))))
(truss/ex-info! (str "Unrecognized header meta: " head-meta)
{:head-meta head-meta})))
(comment (wrap-header (.getBytes "foo") {:compressor-id :lz4
:encryptor-id nil}))
@ -1377,7 +1376,7 @@
(defmacro ^:private editable? [coll] `(instance? clojure.lang.IEditableCollection ~coll))
(defn- xform* [xform] (enc/catching-xform {:error/msg "Error thrown via `*thaw-xform*`"} xform))
(defn- xform* [xform] (truss/catching-xform {:error/msg "Error thrown via `*thaw-xform*`"} xform))
(let [rf! (fn rf! ([x] (persistent! x)) ([acc x] (conj! acc x)))
rf* (fn rf* ([x] x) ([acc x] (conj acc x)))]
@ -1414,14 +1413,12 @@
(try
(custom-reader in)
(catch Exception e
(throw
(ex-info
(truss/ex-info!
(str "Reader exception for custom type id: " type-id)
{:type-id type-id, :prefixed? prefixed?} e))))
(throw
(ex-info
{:type-id type-id, :prefixed? prefixed?} e)))
(truss/ex-info!
(str "No reader provided for custom type id: " type-id)
{:type-id type-id, :prefixed? prefixed?}))))
{:type-id type-id, :prefixed? prefixed?})))
(defn- read-edn [edn]
(try
@ -1498,9 +1495,9 @@
[^DataInput in class-name]
(if (thaw-serializable-allowed? class-name)
(read-object in class-name)
(throw ; No way to skip bytes, so best we can do is throw
(ex-info "Cannot thaw object: `taoensso.nippy/*thaw-serializable-allowlist*` check failed. This is a security feature. See `*thaw-serializable-allowlist*` docstring or https://github.com/ptaoussanis/nippy/issues/130 for details!"
{:class-name class-name}))))
(truss/ex-info! ; No way to skip bytes, so best we can do is throw
"Cannot thaw object: `taoensso.nippy/*thaw-serializable-allowlist*` check failed. This is a security feature. See `*thaw-serializable-allowlist*` docstring or https://github.com/ptaoussanis/nippy/issues/130 for details!"
{:class-name class-name})))
(defn- read-record [in class-name]
(let [content (thaw-from-in! in)]
@ -1753,15 +1750,13 @@
(if (neg? type-id)
(read-custom! in nil type-id) ; Unprefixed custom type
(throw
(ex-info
(truss/ex-info!
(str "Unrecognized type id (" type-id "). Data frozen with newer Nippy version?")
{:type-id type-id}))))
{:type-id type-id})))
(catch Throwable t
(throw
(ex-info (str "Thaw failed against type-id: " type-id)
{:type-id type-id} t))))))
(truss/ex-info! (str "Thaw failed against type-id: " type-id)
{:type-id type-id} t)))))
(let [head-sig head-sig] ; Not ^:const
(defn- try-parse-header [^bytes ba]
@ -1781,20 +1776,20 @@
:lzma2 lzma2-compressor
:lz4 lz4-compressor
:zstd zstd-compressor
:no-header (throw (ex-info ":auto not supported on headerless data." {}))
:else (throw (ex-info ":auto not supported for non-standard compressors." {}))
(do (throw (ex-info (str "Unrecognized :auto compressor id: " compressor-id)
{:compressor-id compressor-id})))))
:no-header (truss/ex-info! ":auto not supported on headerless data." {})
:else (truss/ex-info! ":auto not supported for non-standard compressors." {})
(do (truss/ex-info! (str "Unrecognized :auto compressor id: " compressor-id)
{:compressor-id compressor-id}))))
(defn- get-auto-encryptor [encryptor-id]
(case encryptor-id
nil nil
:aes128-gcm-sha512 aes128-gcm-encryptor
:aes128-cbc-sha512 aes128-cbc-encryptor
:no-header (throw (ex-info ":auto not supported on headerless data." {}))
:else (throw (ex-info ":auto not supported for non-standard encryptors." {}))
(do (throw (ex-info (str "Unrecognized :auto encryptor id: " encryptor-id)
{:encryptor-id encryptor-id})))))
:no-header (truss/ex-info! ":auto not supported on headerless data." {})
:else (truss/ex-info! ":auto not supported for non-standard encryptors." {})
(do (truss/ex-info! (str "Unrecognized :auto encryptor id: " encryptor-id)
{:encryptor-id encryptor-id}))))
(def ^:private err-msg-unknown-thaw-failure "Possible decryption/decompression error, unfrozen/damaged data, etc.")
(def ^:private err-msg-unrecognized-header "Unrecognized (but apparently well-formed) header. Data frozen with newer Nippy version?")
@ -1841,8 +1836,7 @@
(fn ex
([ msg] (ex nil msg))
([e msg]
(throw
(ex-info (str "Thaw failed. " msg)
(truss/ex-info! (str "Thaw failed. " msg)
{:opts
(assoc opts
:compressor compressor
@ -1858,7 +1852,7 @@
'*thaw-serializable-allowlist* *thaw-serializable-allowlist*
'*thaw-xform* *thaw-xform*)}
e))))
e)))
thaw-data
(fn [data-ba compressor-id encryptor-id ex-fn]
@ -2243,5 +2237,5 @@
(alter-var-root *thaw-serializable-allowlist* f) and/or
(alter-var-root *freeze-serializable-allow-list* f) instead."
[f]
(alter-var-root *freeze-serializable-allowlist* (fn [old] (f (enc/have set? old))))
(alter-var-root *thaw-serializable-allowlist* (fn [old] (f (enc/have set? old))))))
(alter-var-root *freeze-serializable-allowlist* (fn [old] (f (truss/have set? old))))
(alter-var-root *thaw-serializable-allowlist* (fn [old] (f (truss/have set? old))))))

View file

@ -1,6 +1,8 @@
(ns ^:no-doc taoensso.nippy.compression
"Private, implementation detail."
(:require [taoensso.encore :as enc])
(:require
[taoensso.truss :as truss]
[taoensso.encore :as enc])
(:import
[java.nio ByteBuffer]
[java.io
@ -156,7 +158,7 @@
(.read xzs ba 0 len-decomp)
(if (== -1 (.read xzs)) ; Good practice as extra safety measure
nil
(throw (ex-info "LZMA2 Decompress failed: corrupt data?" {:ba ba})))
(truss/ex-info! "LZMA2 Decompress failed: corrupt data?" {:ba ba}))
ba)))
;;;; Public API

View file

@ -2,7 +2,9 @@
"Low-level crypto utils.
Private & alpha, very likely to change!"
(:refer-clojure :exclude [rand-nth])
(:require [taoensso.encore :as enc]))
(:require
[taoensso.truss :as truss]
[taoensso.encore :as enc]))
;; Note that AES128 may be preferable to AES256 due to known attack
;; vectors specific to AES256, Ref. <https://goo.gl/qU4CCV>
@ -45,7 +47,7 @@
(defn take-ba ^bytes [n ^bytes ba] (java.util.Arrays/copyOf ba ^int n)) ; Pads if ba too small
(defn utf8->ba ^bytes [^String s] (.getBytes s "UTF-8"))
(defn- add-salt ^bytes [?salt-ba ba] (if ?salt-ba (enc/ba-concat ?salt-ba ba) ba))
(defn pwd-as-ba ^bytes [utf8-or-ba] (if (string? utf8-or-ba) (utf8->ba utf8-or-ba) (enc/have enc/bytes? utf8-or-ba)))
(defn pwd-as-ba ^bytes [utf8-or-ba] (if (string? utf8-or-ba) (utf8->ba utf8-or-ba) (truss/have enc/bytes? utf8-or-ba)))
(comment (seq (pwd-as-ba "foo")))

View file

@ -1,6 +1,7 @@
(ns ^:no-doc taoensso.nippy.encryption
"Private, implementation detail."
(:require
[taoensso.truss :as truss]
[taoensso.encore :as enc]
[taoensso.nippy.crypto :as crypto]))
@ -14,11 +15,11 @@
(decrypt ^bytes [encryptor pwd ba]))
(defn- throw-destructure-ex [typed-password]
(throw (ex-info
(truss/ex-info!
(str "Expected password form: "
"[<#{:salted :cached}> <password-string>].\n "
"See `aes128-encryptor` docstring for details!")
{:typed-password typed-password})))
{:typed-password typed-password}))
(defn- destructure-typed-pwd [typed-password]
(if (vector? typed-password)

View file

@ -2,6 +2,7 @@
"Private, implementation detail."
(:require
[clojure.string :as str]
[taoensso.truss :as truss]
[taoensso.encore :as enc]))
;;;; Fallback type tests
@ -63,7 +64,7 @@
(when x
(if (string? x)
(if (= x "") #{} (set (mapv str/trim (str/split x #"[,:]"))))
(enc/have set? x))))
(truss/have set? x))))
(comment
(mapv classname-set [nil #{"foo"} "" "foo, bar:baz"])

View file

@ -4,6 +4,7 @@
[clojure.test.check :as tc]
[clojure.test.check.generators :as tc-gens]
[clojure.test.check.properties :as tc-props]
[taoensso.truss :as truss :refer [throws?]]
[taoensso.encore :as enc :refer [ba=]]
[taoensso.nippy :as nippy :refer [freeze thaw]]
[taoensso.nippy.compression :as compr]
@ -75,9 +76,9 @@
#(freeze % {:compressor nippy/zstd-compressor}))
test-data)))
(is (enc/throws? Exception (thaw (freeze test-data {:password "malformed"}))))
(is (enc/throws? Exception (thaw (freeze test-data {:password [:salted "p"]}))))
(is (enc/throws? Exception (thaw (freeze test-data {:password [:salted "p"]}))))
(is (throws? Exception (thaw (freeze test-data {:password "malformed"}))))
(is (throws? Exception (thaw (freeze test-data {:password [:salted "p"]}))))
(is (throws? Exception (thaw (freeze test-data {:password [:salted "p"]}))))
(is
(= "payload"
@ -94,10 +95,10 @@
(let [n range-uint+] (= (thaw (freeze n)) n))
(let [n (- range-uint+)] (= (thaw (freeze n)) n))]))
(is (enc/throws? :ex-info "Failed to freeze type" (nippy/freeze (fn []))))
(is (throws? :ex-info "Failed to freeze type" (nippy/freeze (fn []))))
(testing "Clojure v1.10+ metadata protocol extensions"
[(is (enc/throws? :ex-info "Failed to freeze type" (nippy/freeze (with-meta [] {:a :A, 'b (fn [])}))))
[(is (throws? :ex-info "Failed to freeze type" (nippy/freeze (with-meta [] {:a :A, 'b (fn [])}))))
(is (= {:a :A} (meta (nippy/thaw (nippy/freeze (with-meta [] {:a :A, 'b/c (fn [])}))))))
(is (= nil (meta (nippy/thaw (nippy/freeze (with-meta [] { 'b/c (fn [])})))))
"Don't attach empty metadata")])
@ -112,7 +113,7 @@
(deftest _types
[(testing "Extend to custom type"
[(is
(enc/throws? Exception ; No thaw extension yet
(throws? Exception ; No thaw extension yet
(do
(alter-var-root #'nippy/*custom-readers* (constantly {}))
(nippy/extend-freeze MyType 1 [x s]
@ -323,7 +324,7 @@
[(is (= nippy/*thaw-serializable-allowlist* #{"base.1" "base.2" "add.1" "add.2"})
"JVM properties override initial allowlist values")
(is (enc/throws? Exception (nippy/freeze sem {:serializable-allowlist #{}}))
(is (throws? Exception (nippy/freeze sem {:serializable-allowlist #{}}))
"Can't freeze Serializable objects unless approved by allowlist")
(is (sem?
@ -435,8 +436,8 @@
(is (= (binding [nippy/*thaw-xform* (map (fn [x] (/ 1 0)))] (thaw (freeze []))) []) "rf not run on empty colls")
(let [ex (enc/throws :default (binding [nippy/*thaw-xform* (map (fn [x] (/ 1 0)))] (thaw (freeze [:a :b]))))]
(is (= (-> ex enc/ex-cause enc/ex-cause ex-data :call) '(rf acc in)) "Error thrown via `*thaw-xform*`"))])
(let [ex (truss/throws :default (binding [nippy/*thaw-xform* (map (fn [x] (/ 1 0)))] (thaw (freeze [:a :b]))))]
(is (= (-> ex ex-cause ex-cause ex-data :call) '(rf acc in)) "Error thrown via `*thaw-xform*`"))])
;;;; Compressors
@ -453,7 +454,7 @@
(print ".") (flush)
(dotimes [_ 1000]
(is
(nil? (enc/catching (compr/decompress c (crypto/rand-bytes 1024))))
(nil? (truss/catching :all (compr/decompress c (crypto/rand-bytes 1024))))
"Decompression never crashes JVM, even against invalid data")))
(println)))