{"id":13352618,"url":"https://github.com/taoensso/truss","last_synced_at":"2025-05-14T17:07:08.977Z","repository":{"id":50414641,"uuid":"45588181","full_name":"taoensso/truss","owner":"taoensso","description":"Micro toolkit for Clojure/Script errors","archived":false,"fork":false,"pushed_at":"2025-05-06T10:06:56.000Z","size":693,"stargazers_count":320,"open_issues_count":1,"forks_count":15,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-05-06T11:27:32.457Z","etag":null,"topics":["assertions","clojure","clojure-spec","clojurescript","epl","schema","taoensso","validation"],"latest_commit_sha":null,"homepage":"https://www.taoensso.com/truss","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/taoensso.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":"FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"ptaoussanis","custom":"https://www.taoensso.com/clojure"}},"created_at":"2015-11-05T04:44:50.000Z","updated_at":"2025-05-06T10:06:56.000Z","dependencies_parsed_at":"2024-02-07T17:54:08.392Z","dependency_job_id":"9aedbefb-6844-45e5-9834-0231c1a1257b","html_url":"https://github.com/taoensso/truss","commit_stats":null,"previous_names":["taoensso/truss","ptaoussanis/truss"],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taoensso%2Ftruss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taoensso%2Ftruss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taoensso%2Ftruss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taoensso%2Ftruss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taoensso","download_url":"https://codeload.github.com/taoensso/truss/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254046358,"owners_count":22005576,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assertions","clojure","clojure-spec","clojurescript","epl","schema","taoensso","validation"],"created_at":"2024-07-29T21:01:24.453Z","updated_at":"2025-05-14T17:07:08.970Z","avatar_url":"https://github.com/taoensso.png","language":"Clojure","funding_links":["https://github.com/sponsors/ptaoussanis","https://www.taoensso.com/clojure"],"categories":["Awesome ClojureScript"],"sub_categories":["Development"],"readme":"\u003ca href=\"https://www.taoensso.com/clojure\" title=\"More stuff by @ptaoussanis at www.taoensso.com\"\u003e\u003cimg src=\"https://www.taoensso.com/open-source.png\" alt=\"Taoensso open source\" width=\"340\"/\u003e\u003c/a\u003e  \n[**API**][cljdoc] | [**Wiki**][GitHub wiki] | [Latest releases](#latest-releases) | [Slack channel][]\n\n# Truss\n\n### An opinionated micro toolkit for Clojure/Script errors\n\n**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.\n\nIt works great with [Telemere](https://www.taoensso.com/telemere) and [Tufte](https://www.taoensso.com/tufte), and includes practical tools for [inline assertions](#inline-assertions), [contextual exceptions](#contextual-exceptions), [cross-platform exceptions](cross-platform-exceptions), [testing exceptions](#testing-exceptions), and [more](#misc-utils).\n\n\u003cimg width=\"640\" src=\"../../blob/master/hero.png\" alt=\"Egyptian ship with rope truss, the oldest known use of trusses (about 1250 BC).\"/\u003e\n\n\u003e 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\n\n## Latest release/s\n\n- `2025-04-29` `v2.1.0`: [release info](../../releases/tag/v2.1.0) (v2 expands Truss's scope from just inline assertions to a general toolkit for Clojure/Script errors)\n\n[![Main tests][Main tests SVG]][Main tests URL]\n[![Graal tests][Graal tests SVG]][Graal tests URL]\n\nSee [here][GitHub releases] for earlier releases.\n\n## Inline assertions\n\nIn my experience a large proportion of production Clojure errors are caused by a small set of recurring patterns like:\n\n1. A **data type has changed**, breaking an obscure downstream consumer.\n2. An **abstract data type is provided** (e.g. seq) where a concrete type (e.g. vector) is needed for specific behaviour (e.g. right-side peek).\n3. An undocumented or easy-to-miss **state invariant is assumed** (e.g. if an item is in map `A`, its id will also be in set `B`) but broken during refactoring.\n\nThe resulting Clojure/Script error messages encountered in production are often unhelpful.\n\nTo 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`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#have), [`have?`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#have?), [`have!`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#have!), [`have!?`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#have!?).\n\n`have` is the most common and is basically sugar for:\n\n```clojure\n(have string? my-arg) ; -\u003e expands to:\n(if  (string? my-arg)\n  my-arg ; Returns given arg on success\n  (throw-detailed-exception!))\n```\n\nUse `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.\n\nWhen a Truss assertion fails, it'll throw a [`truss/ex-info`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.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*`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#*ctx*)) in which you can store the relevant Ring request, etc.\n\nI can't overstate how much difference having even this basic info can make.\n\nExamples:\n\n```clojure\n(have string? \"foo\") ; =\u003e \"foo\"\n(have string? 5)     ; =\u003e Throws detailed exception\n\n;; Multiple args can be given:\n(have string? \"foo\" \"bar\") ; =\u003e [\"foo\" \"bar\"]\n(have string? \"foo\" 5)     ; =\u003e Throws detailed exception\n\n;; Omit predicate to default to `some?` (non-nil):\n(have \"foo\") ; =\u003e \"foo\"\n(have false) ; =\u003e false\n(have nil)   ; =\u003e Throws detailed exception\n\n;; Add arb optional info to thrown ex-data using `:data`:\n(have string? \"foo\" :data {:user-id 101}) =\u003e \"foo\"\n\n;; Assert inside collections using `:in`:\n(have string? :in #{\"foo\" \"bar\"}) ; =\u003e #{\"foo\" \"bar\"}\n\n;; Several special predicates are supported:\n(have [:or nil? string?] \"foo\")  ; =\u003e \"foo\"\n(have [:ks\u003c= #{:a :b}]  my-map)  ; =\u003e Map,  or throws\n(have [:el #{:a :b :c}] my-arg)  ; =\u003e Arg,  or throws\n(have [:n\u003c= 10]         my-coll) ; =\u003e Coll, or throws\n\n;; An example exception:\n(have string? (/ 1 0)) ; =\u003e\n;; Truss assertion failed at truss-examples[29 1]:\n;; (clojure.core/string? (/ 1 0))\n;; Error evaluating arg: Divide by zero\n;; {:inst #inst \"2025-02-21T14:19:36.798972000-00:00\",\n;;  :ns \"truss-examples\",\n;;  :pred clojure.core/string?,\n;;  :arg\n;;  {:form (/ 1 0), :value :truss/exception, :type :truss/exception},\n;;  :coords [29 1]}\n```\n\nSee [examples.cljc](../../blob/master/examples.cljc) or [YouTube demo](https://www.youtube.com/watch?v=gMB4Y-EIArA) for more.\n\n## Contextual exceptions\n\nExceptions 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?\n\nTruss's [inline assertions](#inline-assertions) (above) provide one solution to the problem of vague exceptions.\n\nTruss's **contextual exception** API provides another. Any time a [`truss/ex-info`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) is thrown, it'll include the dynamic [`*ctx*`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#*ctx*) value in its `ex-data`.\n\nYou can use [`set-ctx!`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#set-ctx!), [`with-ctx`, `with-ctx+`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#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.\n\nExample:\n\n```clojure\n(defn wrap-ring-ctx\n  \"Wraps given Ring handler so that the Ring req will be\n  included in any thrown `truss/ex-info`s.\"\n  [ring-handler-fn]\n  (fn [ring-req]\n    (truss/with-ctx+ {:ring-req ring-req} ; Merge into `truss/*ctx*`\n      (ring-handler-fn ring-req))))\n```\n\nSee also [`truss/ex-info!`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info!) to directly throw a [`truss/ex-info`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info).\n\n\u003e I'll be updating all my [open source libraries](https://www.taoensso.com/clojure) over time to use [`truss/ex-info`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) everywhere exceptions are thrown.\n\n## Cross-platform exceptions\n\nCatching exceptions in cross-platform Clojure/Script code can be needlessly tedious. Truss provides a couple utils to make this easier:\n\n- [`catching`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#catching) just swallow exceptions: `(catching (my-code))`.\n- [`try*`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#try*) is like `core/try` but can catch special classes: `:ex-info`, `:common`, `:all`, `:default`. See docstring for details.\n\nA cross-platform [`error?`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#error?) predicate is also provided.\n\n## Testing exceptions\n\nWriting unit tests that need to check for specific exception types, messages, and/or ex-data? Truss provides some relevant utils: [`throws?`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#throws?), [`throws`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#throws), [`matching-error`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#matching-error).\n\nExample:\n\n```clojure\n(is (throws? :any \"Divide by zero\" (/ 1 0))) =\u003e true\n(is (throws? :ex-info {:user-name :stu, :user-id pos-int?} ...))\n```\n\n  When an error with (nested) causes doesn't match, a match will be attempted\n  against its (nested) causes.\n\n## Misc utils\n\n- Clojure's transducers are awesome, but can be an absolute pita to debug. See [`catching-xform`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#catching-xform) for a util this _far_ easier. [`catching-rf`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#catching-rf) is likewise available for regular reducing fns.\n- [`unexpected-arg!`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#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](https://www.taoensso.com/clojure) to make common user errors easier to debug.\n\n## Documentation\n\n- [Wiki][GitHub wiki] (getting started, usage, etc.)\n- API reference via [cljdoc][cljdoc]\n- Support via [Slack channel][] or [GitHub issues][]\n\n## Funding\n\nYou can [help support][sponsor] continued work on this project, thank you!! 🙏\n\n## License\n\nCopyright \u0026copy; 2014-2025 [Peter Taoussanis][].  \nLicensed under [EPL 1.0](LICENSE.txt) (same as Clojure).\n\n\u003c!-- Common --\u003e\n\n[GitHub releases]: ../../releases\n[GitHub issues]:   ../../issues\n[GitHub wiki]:     ../../wiki\n[Slack channel]: https://www.taoensso.com/truss/slack\n\n[Peter Taoussanis]: https://www.taoensso.com\n[sponsor]:          https://www.taoensso.com/sponsor\n\n\u003c!-- Project --\u003e\n\n[cljdoc]: https://cljdoc.org/d/com.taoensso/truss/\n\n[Clojars SVG]: https://img.shields.io/clojars/v/com.taoensso/truss.svg\n[Clojars URL]: https://clojars.org/com.taoensso/truss\n\n[Main tests SVG]:  https://github.com/taoensso/truss/actions/workflows/main-tests.yml/badge.svg\n[Main tests URL]:  https://github.com/taoensso/truss/actions/workflows/main-tests.yml\n[Graal tests SVG]: https://github.com/taoensso/truss/actions/workflows/graal-tests.yml/badge.svg\n[Graal tests URL]: https://github.com/taoensso/truss/actions/workflows/graal-tests.yml\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaoensso%2Ftruss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaoensso%2Ftruss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaoensso%2Ftruss/lists"}