Truss

repository·master·Indexed 43 days ago

https://github.com/taoensso/truss

An opinionated micro toolkit for Clojure and ClojureScript designed to improve error handling and debugging. It provides tools for inline assertions via the `have` macro, contextual exceptions using `*ctx*`, and cross-platform exception handling with `try*` and `catching`. Truss includes utilities for testing exceptions with `throws?` and `matching-error`, as well as debugging transducers and reducing functions.

Tokens
2.1K
Snippets
8
Records
13
Agent score
56%

What's inside Truss

  1. Manage contextual exceptions with `*ctx*`

    master

    Truss allows you to attach execution context to exceptions using a dynamic variable *ctx*. Any truss/ex-info thrown while a context is set will automatically include that context in its ex-data. This is useful for including request information (like Ring requests) in production error logs.

    Use set-ctx!, with-ctx, or with-ctx+ (which merges into the existing context) to establish this information.

    (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))))
  2. Install Truss via Leiningen or deps.edn

    master

    To use Truss in your Clojure or ClojureScript project, add the dependency to your build configuration. Replace x-y-z with the latest version of com.taoensso/truss.

    ;; Leiningen
    [com.taoensso/truss "x-y-z"]
    
    ;; deps.edn
    com.taoensso/truss {:mvn/version "x-y-z"}
  3. Elide assertions in production for performance

    master

    To eliminate the runtime overhead of assertions in production, you can disable them by setting *assert* to false before macro expansion. When elided, Truss assertions pass their arguments through with no predicate checks and zero performance cost.

    If using Leiningen, add this to your project.clj:

    :global-vars {*assert* false}
  4. Handle failed assertions with `*failed-assertion-handler*`

    master

    By default, Truss throws an exception when an assertion fails. You can customize this behavior by adjusting the *failed-assertion-handler* dynamic var. This is useful for:

    • Capturing failures during unit testing using dynamic binding.
    • Emitting failures as Telemere signals before throwing.
  5. Use the `have` macro for basic assertions

    master

    The primary way to use Truss is the have macro. It takes a predicate and an argument. If the predicate returns truthy for the argument, the assertion succeeds and returns the argument. If the predicate returns falsey, a detailed error is thrown.

    Use this to formalize assumptions about function arguments, intermediate values, or application state to prevent undocumented type assumptions from causing silent failures.

    (defn greet
      "Given a string username, prints a greeting message."
      [username]
      (println "hello" (have string? username)))
  6. Debug transducers and reducing functions

    master

    Truss includes utilities to make debugging complex Clojure transducers and reducing functions easier:

    • catching-xform: A utility for debugging Clojure transducers.
    • catching-rf: A utility for debugging regular reducing functions.
  7. Reject arguments with `unexpected-arg!`

    master
    The unexpected-arg! macro provides a way to reject an argument with a clear, descriptive error message. This is useful for library authors to provide better feedback to users when they pass invalid arguments.
  8. Handle cross-platform exceptions with `try*` and `catching`

    master

    Truss provides utilities to simplify exception handling in code that runs on both Clojure (JVM) and ClojureScript.

    • catching: A utility that simply swallows exceptions: (catching (my-code)).
    • try*: A cross-platform version of core/try that can catch specific Truss exception classes: :ex-info, :common, :all, or :default.
    • error?: A cross-platform predicate to check if something is an error.
  9. Test exceptions with `throws?` and `matching-error`

    master

    When writing unit tests, Truss provides utilities to assert that code throws specific errors. These utilities can check for exception types, messages, and ex-data content. If a match fails on a top-level exception, Truss will attempt to match against its nested causes.

    Key functions:

    • throws?: Checks if an expression throws an error matching specific criteria.
    • throws: Asserts that an expression throws.
    • matching-error: Provides advanced matching for errors.
    ;; Check for any error and a specific message
    (is (throws? :any "Divide by zero" (/ 1 0))) ; => true
    
    ;; Check for specific ex-info data
    (is (throws? :ex-info {:user-name :stu, :user-id pos-int?} ...))
  10. Prevent assertion elision for critical checks using `have!`

    master

    If you have critical checks that must never be disabled (e.g., security assertions), use the have! macro. Unlike have, have! ignores the *assert* setting and will never be elided, ensuring the check always runs in production.

    (defn get-restricted-resource [ring-session]
      (let [auth (have! string? (:auth-token ring-session))] ; Never elide!
        ;; Do stuff...
        "Return restricted resource content"))
  11. Use inline assertions with `have` macros

    master

    Truss provides a set of macros for efficient runtime assertions that throw detailed exceptions when a predicate fails. The most common macro is have, which returns the argument on success or throws a truss/ex-info on failure.

    When an assertion fails, the resulting exception includes a timestamp, the failed predicate, the tested argument, the source location, and the current Truss context (*ctx*).

    Key features:

    • Return value: Returns the argument(s) on success.
    • Multiple arguments: Can return multiple arguments as a vector.
    • Default predicate: Omitting the predicate defaults to some? (non-nil check).
    • Custom data: Use :data to attach arbitrary information to the thrown exception.
    • Collection assertions: Use :in to assert over elements within a collection.
    • Special predicates: Supports composite predicates like :or and size/membership checks like :ks<=, :el, and :n<=.
    ;; Basic usage
    (have string? "foo") ; => "foo"
    (have string? 5)      ; => Throws detailed exception
    
    ;; Multiple args
    (have string? "foo" "bar") ; => ["foo" "bar"]
    
    ;; Default to some? (non-nil)
    (have "foo") ; => "foo"
    (have nil)    ; => Throws detailed exception
    
    ;; Add custom data to exception
    (have string? "foo" :data {:user-id 101}) ; => "foo"
    
    ;; Assert inside collections
    (have string? :in #{"foo" "bar"}) ; => #{"foo" "bar"}
    
    ;; Special predicates
    (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