API | Wiki | Latest releases | Slack channel
Truss is a lightweight, dependency-free library for Clojure and ClojureScript that offers a small set of high-value utils and patterns that I've honed over the years to help tame Clojure's famously impenetrable error messages.
It works great with Telemere and Tufte, and includes practical tools for inline assertions, contextual exceptions, cross-platform exceptions, testing exceptions, and more.
A doubtful friend is worse than a certain enemy. Let a man be one thing or the other, and we then know how to meet him. - Aesop
2025-02-27
v2.0.0
: release info(v2 expands Truss's scope from just inline assertions to a general toolkit for Clojure/Script errors).
See here for earlier releases.
In my experience a large proportion of production Clojure errors are caused by a small set of recurring patterns like:
A
, its id will also be in set B
) but broken during refactoring.The resulting Clojure/Script error messages encountered in production are often unhelpful.
To help with these kinds of cases, Truss offers a set of small macros that provide super efficient and flexible inline runtime assertions with terrific error messages when something goes wrong: have
, have?
, have!
, have!?
.
have
is the most common and is basically sugar for:
(have string? my-arg) ; -> expands to:
(if (string? my-arg)
my-arg ; Returns given arg on success
(throw-detailed-exception!))
Use have
inline, or in let
bindings. A few well-placed assertions can go a surprisingly long way to making unexpected errors in production easier to identify and understand.
When a Truss assertion fails, it'll throw a truss/ex-info
with ex-data that includes a timestamp, the failed predicate, the tested argument, the source location, and the current Truss context (*ctx*
) in which you can store the relevant Ring request, etc.
I can't overstate how much difference having even this basic info can make.
Examples:
(have string? "foo") ; => "foo"
(have string? 5) ; => Throws detailed exception
;; Multiple args can be given:
(have string? "foo" "bar") ; => ["foo" "bar"]
(have string? "foo" 5) ; => Throws detailed exception
;; Omit predicate to default to `some?` (non-nil):
(have "foo") ; => "foo"
(have false) ; => false
(have nil) ; => Throws detailed exception
;; Add arb optional info to thrown ex-data using `:data`:
(have string? "foo" :data {:user-id 101}) => "foo"
;; Assert inside collections using `:in`:
(have string? :in #{"foo" "bar"}) ; => #{"foo" "bar"}
;; Several special predicates are supported:
(have [:or nil? string?] "foo") ; => "foo"
(have [:ks<= #{:a :b}] my-map) ; => Map, or throws
(have [:el #{:a :b :c}] my-arg) ; => Arg, or throws
(have [:n<= 10] my-coll) ; => Coll, or throws
;; An example exception:
(have string? (/ 1 0)) ; =>
;; Truss assertion failed at truss-examples[29 1]:
;; (clojure.core/string? (/ 1 0))
;; Error evaluating arg: Divide by zero
;; {:inst #inst "2025-02-21T14:19:36.798972000-00:00",
;; :ns "truss-examples",
;; :pred clojure.core/string?,
;; :arg
;; {:form (/ 1 0), :value :truss/exception, :type :truss/exception},
;; :coords [29 1]}
See examples.cljc or YouTube demo for more.
Exceptions unexpectedly thrown in production can be fiendishly difficult to understand/debug. What argument triggered the exception? What was its type and value? What was the execution context when it was encountered?
Truss's inline assertions (above) provide one solution to the problem of vague exceptions.
Truss's contextual exception API provides another. Any time a truss/ex-info
is thrown, it'll include the dynamic *ctx*
value.
You can use set-ctx!
, with-ctx
, with-ctx+
to easily establish relevant information about what your code is doing. Then if something unexpectedly throws, this context info will be included in relevant exceptions.
Example:
(defn wrap-ring-ctx
"Wraps given Ring handler so that the Ring req will be
included in any thrown `truss/ex-info`s."
[ring-handler-fn]
(fn [ring-req]
(truss/with-ctx+ {:ring-req ring-req} ; Merge into `truss/*ctx*`
(ring-handler-fn ring-req))))
See also truss/ex-info!
to directly throw a truss/ex-info
.
I'll be updating all my open source libraries over time to use
truss/ex-info
everywhere exceptions are thrown.
Catching exceptions in cross-platform Clojure/Script code can be needlessly tedious. Truss provides a couple utils to make this easier:
catching
just swallow exceptions: (catching (my-code))
.try*
is like core/try
but can catch special classes: :ex-info
, :common
, :all
, :default
. See docstring for details.A cross-platform error?
predicate is also provided.
Writing unit tests that need to check for specific exception types, messages, and/or ex-data? Truss provides some relevant utils: throws?
, throws
, matching-error
.
Example:
(is (throws? :any "Divide by zero" (/ 1 0))) => true
(is (throws? :ex-info {:user-name :stu, :user-id pos-int?} ...))
When an error with (nested) causes doesn't match, a match will be attempted against its (nested) causes.
catching-xform
for a util this far easier. catching-rf
is likewise available for regular reducing fns.unexpected-arg!
provides an easy (if somewhat verbose) way to reject an argument with a clear error message. I'm increasingly using this in my own open source libraries to make common user errors easier to debug.You can help support continued work on this project, thank you!! 🙏
Copyright © 2014-2025 Peter Taoussanis.
Licensed under EPL 1.0 (same as Clojure).
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close