{"id":13568481,"url":"https://github.com/fulcrologic/guardrails","last_synced_at":"2025-05-14T23:05:44.518Z","repository":{"id":35521759,"uuid":"218070981","full_name":"fulcrologic/guardrails","owner":"fulcrologic","description":"Efficient, hassle-free function call validation with a concise inline syntax for clojure.spec and Malli","archived":false,"fork":false,"pushed_at":"2025-03-16T17:34:39.000Z","size":319,"stargazers_count":242,"open_issues_count":1,"forks_count":26,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-04-14T02:58:33.976Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fulcrologic.png","metadata":{"files":{"readme":"README.adoc","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"github":"awkay"}},"created_at":"2019-10-28T14:51:38.000Z","updated_at":"2025-03-16T17:29:04.000Z","dependencies_parsed_at":"2024-06-19T11:14:26.128Z","dependency_job_id":"3dab4406-f285-429d-b1fd-12dd335c0b79","html_url":"https://github.com/fulcrologic/guardrails","commit_stats":{"total_commits":110,"total_committers":16,"mean_commits":6.875,"dds":"0.23636363636363633","last_synced_commit":"6e2835ec7751e34e9d6f2d91972721a337ffa135"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulcrologic%2Fguardrails","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulcrologic%2Fguardrails/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulcrologic%2Fguardrails/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulcrologic%2Fguardrails/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fulcrologic","download_url":"https://codeload.github.com/fulcrologic/guardrails/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254243358,"owners_count":22038046,"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":[],"created_at":"2024-08-01T14:00:26.418Z","updated_at":"2025-05-14T23:05:39.468Z","avatar_url":"https://github.com/fulcrologic.png","language":"Clojure","funding_links":["https://github.com/sponsors/awkay"],"categories":["Clojure","Data Validation"],"sub_categories":[],"readme":":sectanchors:\nifdef::env-github,env-cljdoc[]\n:tip-caption: :bulb:\n:note-caption: :information_source:\n:important-caption: :heavy_exclamation_mark:\n:caution-caption: :fire:\n:warning-caption: :warning:\nendif::[]\n\n= Guardrails\n\nimage:https://img.shields.io/clojars/v/com.fulcrologic/guardrails.svg[link=https://clojars.org/com.fulcrologic/guardrails]\nimage:https://circleci.com/gh/fulcrologic/guardrails/tree/main.svg?style=svg[\"CircleCI\", link=\"https://circleci.com/gh/fulcrologic/guardrails/tree/main\"]\n\nEfficient, hassle-free function call validation with a concise inline syntax for clojure.spec and Malli:\n\n[source, clojure]\n----\n(\u003edefn ranged-rand\n  [start end]\n  [int? int? | #(\u003c start end)            ; `|` = such that\n   =\u003e int? | #(\u003e= % start) #(\u003c % end)]\n  (+ start (long (rand (- end start)))))\n----\n\nGuardrails is intended to make it possible (and easy) to use specs/schemas as a loose but informative (even advisory) type system during development\nso you can better see where you are making mistakes as you make them, with zero impact on _production_ build size or performance, and minimal impact during development.\n\nIt's the evolution of https://github.com/gnl/ghostwheel[Ghostwheel's] runtime spec validation functionality, which it streamlines, optimizes and extends, while keeping the original inline syntax. See the \u003c\u003cWhy?, \"Why?\"\u003e\u003e section for more details on Guardrails' rationale and origin story.\n\n== NEW in 1.2\n\nVersion 1.2 adds some exciting new features and performance improvements (partially funded by https://www.dataico.com[Dataico]):\n\n* Use https://github.com/metosin/malli[Malli] as your \u003c\u003cmalli-support,data specification language\u003e\u003e. You can even mix-and-match Clojure Spec and Malli in the same project/namespace.\n* \u003c\u003ccheck-throttling, Runtime check throttling / non-exhaustive checking\u003e\u003e: Limit the number of function call checks per second at the namespace, function, or global level.\n* \u003c\u003cstatic-exclusions, Library exclusions\u003e\u003e: Authors can auto-exclude internal implementation functions from validation when their libraries are used in dependent projects downstream.\n* \u003c\u003cdynamic-exclusions, Dynamic exclusions\u003e\u003e: Users can disable and enable active checking for any namespace or function at runtime, as well as override library exclusions.\n* \u003c\u003cSuch That, Such-that predicates on the return value\u003e\u003e: Full support for checking such-that predicates on a function's output, with the ability to reference any input arguments in both clojure.spec and Malli.\n\n== Quick Start\n\n. Add this library to your dependencies\n. Create a `guardrails.edn` with `{}` in it in your project root\n. When you run a REPL or CLJS compiler, include the JVM option `-Dguardrails.enabled`\n** Optionally: If you're using CLJS, set your compiler options to include `{:external-config {:guardrails {}}}`\n\nAnd code as follows:\n\n[source, clojure]\n-----\n(ns com.domain.app-ns\n  (:require\n    [com.fulcrologic.guardrails.core :refer [\u003edefn \u003edef | ? =\u003e]]))\n\n;; \u003edef (and \u003efdef) can be used to remove specs from production builds. Use them to define\n;; specs that you only need in development. See the docstring of\n;; `com.fulcrologic.guardrails.noop` for details.\n(\u003edef ::thing (s/or :i int? :s string?))\n\n;; When guardrails is disabled this will just be a normal `defn`, and no fspec overhead will\n;; appear in cljs builds. When enabled it will check the inputs/outputs and *always* log\n;; an error using `expound`, and then *optionally* throw an exception,\n(\u003edefn f\n  [i]\n  [::thing =\u003e int?]\n  (if (string? i)\n    0\n    (inc i)))\n-----\n\nWhen the function is misused you'll get an error:\n\n[source, bash]\n-----\nuser=\u003e (f 3.2)\nERROR /Users/user/project/src/com/domain/app_ns.clj:12 f's argument list\n -- Spec failed --------------------\n\n  [3.2]\n   ^^^\n\nshould satisfy\n\n  int?\n\nor\n\n  string?\n\n-- Relevant specs -------\n\n:user/thing:\n  (clojure.spec.alpha/or :i clojure.core/int? :s clojure.core/string?)\n-----\n\nYou can control if spec failures are advisory or fatal by editing `guardrails.edn` and setting the `:throw?` option. See\n\u003c\u003cConfiguration\u003e\u003e for more details.\n\nMake sure to set your editor or IDE to resolve Guardrails' `\u003edefn` and `\u003efdef` as Clojure's `defn`, and `\u003edefn-` as `defn-` respectively – this way you get proper highlighting, formatting, error handling, structural navigation, symbol resolution, and refactoring support. A linting configuration for clj-kondo is included and should work out of the box.\n\n=== Output Options\n\nThe configuration can be changed to help the signal to noise ratio on failures. If you have heavily instrumented your code with Guardrails, then turning on these two config options will likely give you a clearer picture on failures:\n\n* `:guardrails/compact? true` - Remove blank/excess lines from the failure output.\n* `:guardrails/stack-trace :none` - Change what is shown for the stack trace. :none elides it, :prune causes a more pruned stack trace on a single line, and :full is the default of showing the whole thing.\n* `:guardrails/trace? true` - Shows the GR function call stack on failures (with argument values)\n\nTry the above settings as shown.  You can always see the full stack by calling `last-failure` (which is printed with any failures for convenience). The result is way less noisy, and often sufficient to find the problem. When it's not, the full stack trace is just a copy/paste away.\n\n== Clojurescript Considerations\n\nI use `shadow-cljs` as the build tool for all of my projects, and highly recommend it. Version 0.0.11 of Guardrails\nchecks the compiler optimizations and refuses to output guardrails checks except in development mode (no optimizations). This\nprevents you from accidentally releasing a CLJS project with big runtime performance penalties due to spec checking\nat every function call.\n\nThe recommended approach for using guardrails in your project is to make a separate `:dev` and `:release` section of your\nshadow-cljs config, like so:\n\n[source, clojure]\n------\n{:builds   {:main              {:target            :browser\n                                ...\n                                :dev               {:compiler-options\n                                                    {:closure-defines {'goog.DEBUG true}\n                                                     :external-config {:guardrails {}}}}\n                                :release           {}}}\n ...}\n------\n\nDoing so will prevent you from accidentally generating a release build with guardrails enabled in case you had\na shadow-cljs server running in dev mode (which would cache that guardrails was enabled) and built a release\ntarget:\n\n[source, bash]\n-----\n# in one terminal:\n$ shadow-cljs server\n# later, in a different terminal\n$ shadow-cljs release main\n-----\n\nIn this scenario Guardrails will detect that you have accidentally enabled it on a production build and will\nthrow an exception.  The only way to get guardrails to build into a CLJS release build is to explicitly set\nthe JVM property \"guardrails.enabled\" to \"production\" (NOTE: any truthy value will enable it in CLJ).\n\nYou can set JVM options in shadow-cljs using the `:jvm-opts` key:\n\n[source, clojure]\n-----\n :jvm-opts [\"-Dguardrails.enabled=production\"]\n-----\n\nbut this is highly discouraged.\n\n=== Dead Code Elimination\n\nThere is a a noop namespace that can be used in your build settings to attempt to eliminate all traces of guardrails\nand dependent code. This will not remove spec dependencies unless you only use spec for guardrails, so do similar tricks\nfor your inclusions of spec namespaces.\n\nSee https://github.com/fulcrologic/guardrails/blob/develop/src/main/com/fulcrologic/guardrails/noop.cljc[noop.cljc].\n\n[[gspec-syntax]]\n== The Gspec Syntax\n\n`[arg-specs* (| arg-preds+)? \\=\u003e ret-spec (| ret-preds+)? (\\\u003c- generator-fn)?]`\n\n`|` : such that\n\nThe number of `arg-specs` must match the number of function arguments, including a possible variadic argument – Guardrails will shout at you if it doesn't.\n\n=== Single/Multiple Arities\n\nWrite the function as normal, and put a gspec after the argument list:\n\n[source, clojure]\n-----\n(\u003edefn myf\n  ([x]\n   [int? =\u003e number?]\n   ...)\n  ([x y]\n   [int? int? =\u003e int?]\n   ...))\n-----\n\n=== Variadic Argument Lists\n\n`arg-specs` for variadic arguments are defined as one would expect from standard fspec:\n\n[source, clojure]\n-----\n(\u003efdef clojure.core/max\n  [x \u0026 more]\n  [number? (s/* number?) =\u003e number?])\n-----\n\n[NOTE]\n--\nThe `arg-preds`, if defined, are `s/and`-wrapped together with the `arg-specs` when desugared.\n\nThe `ret-preds` are equivalent to (and desugar to) spec's `:fn` predicates, except that the anonymous function parameter\nis the ret, and the args are referenced using their symbols. That's because in the gspec syntax spec's `:fn` is simply\nconsidered a 'such that' clause on the ret.\n--\n\n=== Such That\n\nTo add an additional condition add `|` after either the argument specs (just before `\\=\u003e`) or return value spec\nand supply a lambda that uses the symbol names from the argument list (and `%` for return value).\n\n[source, clojure]\n-----\n(\u003edefn f\n  [i]\n  [int? | #(\u003c 0 i 10) =\u003e int? | #(pos-int? %)]\n  ...)\n-----\n\n=== Nilable\n\nThe `?` macro can be used as a shorthand for `s/nilable`:\n\n[source, clojure]\n-----\n(\u003efdef clojure.core/empty?\n  [coll]\n  [(? seqable?) =\u003e boolean?])\n-----\n\n=== Nested Specs\n\nNested gspecs are defined using the exact same syntax:\n\n[source, clojure]\n-----\n(\u003efdef clojure.core/map-indexed\n  ([f]\n   [[nat-int? any? =\u003e any?] =\u003e fn?])\n  ([f coll]\n   [[nat-int? any? =\u003e any?] (? seqable?) =\u003e seq?]))\n-----\n\nIn the rare cases when a nilable gspec is needed `?` is put in a vector rather than a list:\n\n[source, clojure]\n-----\n(\u003efdef clojure.core/set-validator!\n  [a f]\n  [atom? [? [any? =\u003e any?]] =\u003e any?])\n-----\n\nTIP: For nested gspecs there's no way to reference the args in the `arg-preds` or `ret-preds` by symbol. The recommended\napproach here is to register the required gspec separately by using `\u003efdef` with a keyword.\n//You can do it with `#(\\-\u003e % :arg1)` in the `arg-preds`, but that won't work in the `ret-preds` and it's quite messy anyway. You could theoretically use a nested `(s/fspec ...)` instead of a gspec, but that gets unwieldy quick.\n\nNOTE: Nested gspecs with one or more `any?` argspecs desugar to `ifn?`, so as not to mess up generative testing. This\ncan be overridden by passing a generator – even an empty one, that is simply adding `\\\u003c-` or `:gen` to the gspec – in which case the gspec will desugar exactly as specified.\n{zwsp}\nThe assumption here is that `any?` does not imply that the function can in fact handle any type of argument.\n{zwsp}\nYou should still write out nested gspecs, even if they are as simple as `[any? \\=\u003e any?]` – this is useful as succinct\ndocumentation that this particular function receives exactly one argument.\n\n=== Gspec Advantages\n\n- It's much more concise and easier to write and read.\n- It's inline, so you can see at a glance what kind of data a function expects and returns right under the\ndocstring and arg list, for example when previewing the function definition in your editor.\n- Writing specs for multi-arity functions adds zero complexity, even with multiple return types and such-that predicates\n- Renaming/refactoring parameters is a breeze – just use your IDE's symbol rename functionality and all references in\nthe predicate functions will be handled correctly, because `\u003edefn` syntax is valid `defn` syntax.\n+\nFrom the point of view of the programmer and the editor, the function arguments are bound to their respective symbols and can be freely referenced in any expression as expected, including the gspec which is considered just another body form.\n- For the same reason, you can reliably bypass Guardrails temporarily by simply changing `\u003edefn` to `defn` - the minimal performance impact\nof evaluating the gspec vector as the first body form aside, nothing will break.\n- It can be elided to have zero impact on the build by an external control (config file/JVM parameter).\n\nCredit: The above documentation was largely taken from https://github.com/gnl/ghostwheel#the-gspec-syntax[Ghostwheel's documentation].\n\n[#malli-support]\n== Malli Support\n\nVersion 1.2.0 includes full support for Malli. If you use the latter for data validation, you no longer need to maintain a separate spec-based set of schema for Guardrails – it is, after all, the same data you use in your functions!\n\nAll you have to do to use it instead of spec is change your require statement. In fact, you can alias BOTH spec-based and malli-based Guardrails in the same namespace – just make sure you use the right kind of schema with the corresponding function!\n\nThe special operators `\\=\u003e`, `|`, and `?` can come from either implementation, as they are purely symbolic.\n\n[source, clojure]\n-----\n(ns foo.bar\n  (:require\n    [clojure.spec.alpha :as s]\n    [com.fulcrologic.guardrails.core :as gr.spec]\n    [com.fulcrologic.guardrails.malli.core :as gr.malli :refer [=\u003e | ?]))\n\n(gr.spec/\u003edefn f\n  [x]\n  [(s/keys :req [:thing/x]) =\u003e int?]\n  ...)\n\n(gr.malli/\u003edefn f\n  [x]\n  [[:map :thing/x] =\u003e :int]\n  ...)\n-----\n\nAll configuration options apply to both variants (max checks per second, throw configuration, etc.). Other than the items used *within* the gspec, they are identical.\n\n=== The Guardrails Malli Registry\n\nClojure Spec forces you to use a shared global registry, and carefully ensure that your keywords are qualified and do not collide with others.\n\nMalli does have a default registry, but it is not mutable. This lets you to pick registries at will, and allows for more lenient use of \"poor\" naming because the threat of collision is reduced; however, it makes the writing of function schemas a lot more tedious:\n\n[source, clojure]\n-----\n(\u003edefn f\n  [x]\n  [[:map {:registry my-reg} ...\n-----\n\nFortunately, Malli supports mutable registries, so we can provide the convenience of a global registry and dramatically reduce the boilerplate.\n\nThe mutable Guardrails registry is initiated with the exact content of the default Malli registry, and is held in the `com.fulcrologic.guardrails.malli.registry` namespace, which also includes functions that you can use to directly add your own schema to it. You'll need to do this for any qualified keywords you want to use in `\u003edefn`s that leverage Malli. For example you can merge in some other schema maps with:\n\n[source, clojure]\n-----\n(gr.reg/merge-schemas! my-custom-stuff my-other-stuff)\n-----\n\nThe `com.fulcrologic.guardrails.malli.core` namespace also has a convenient `\u003edef` that is like the Clojure Spec `def`, in that it will register a schema under a qualified keyword for you:\n\n[source, clojure]\n-----\n(\u003edef :member/name :string)\n-----\n\n== Configuration\n\n=== Enabling\n\nGuardrails is disabled by default, emitting *exactly* what a plain `defn` would until you explicitly turn it on, which is done via a JVM option. We chose this path because it is highly effective at preventing its accidental enabling in production, which could cause huge performance impacts.\n\nThe JVM option `-Dguardrails.enabled=true` should be used to turn on\nguardrails. When not defined `\u003edefn` will emit exactly what `defn` would.\n\nYou may also enable it in cljs in your shadow-cljs config\n(see Configuration...adding even an empty config map will enable it).\n\n=== The Configuration File\n\nThe default config goes in the root of the project as `guardrails.edn`:\n\n[source, clojure]\n-----\n{\n ; what to emit instead of defn, if you have another defn macro\n :defn-macro nil\n\n ;; Nilable map of Expound configuration options.\n :expound    {:show-valid-values? true\n              :print-specs?       true}\n\n ;; GLOBALLY enable non-exhaustive checking (this is NOT recommended, you'd\n ;; usually want to set it in a more granular fashion on the function or\n ;; namespace level using metadata.)\n ;; Limits function call checks per second to this maximum number.\n ;; The intermittent checks are spread out evenly to ensure sufficient coverage,\n ;; so if MCPS is set to 50 and you have 1000 calls per second, roughly every 20th\n ;; call will be checked.\n :guardrails/mcps 100\n\n ;; Low-level stack trace output\n ;; default :full\n :guardrails/stack-trace :prune ; or :full or :none\n\n ;; nREPL hates using stderr, but in other REPLs seeing your problems in red is nice.\n ;; default false\n :guardrails/use-stderr? true\n\n ;; Optional (default false). Compress the explanation of the problem.\n :guardrails/compact? true\n\n ;; Keep track of the active GR-instrumented calls as a stack, and show that on errors.\n ;; default false\n :guardrails/trace? true\n\n ;; should a spec failure on args or ret throw an exception?\n ;; (always logs an informative message)\n :throw?     false\n\n ;; should a failure be forwarded through tap\u003e ?\n :tap\u003e?      false}\n-----\n\nYou can override the config file *name* using JVM option\n`-Dguardrails.config=filename`.\nIn your shadow-cljs config file you can override settings via the `[:compiler-options :external-config :guardrails]`\nconfig path of a build:\n\n[source, clojure]\n-----\n...\n     :app  {:target            :browser\n            :dev               {:compiler-options\n                                {:external-config {:guardrails {:throw? false}}\n                                 :closure-defines {'goog.DEBUG true}}}}\n...\n-----\n\n== Performance\n\nGuardrails adds an overhead that is roughly equivalent to the cost of running a Clojure spec or Malli validation. On an\nApple M1 Max, the average check on a generic code base (tested against https://github.com/fulcrologic/statecharts[our statecharts library]) takes around 11 microseconds. This actually tested out to roughly the same for Malli AND Spec, though we did find cases where\nMalli was roughly 2x faster. We did not do further deep analysis.\n\n[source]\n-----\n    nCalls        Max       Mean   MAD      Clock  Total\n   174,586    14.72ms    11.44μs  ±78%     2.00s     91%\n-----\n(measured using https://github.com/taoensso/tufte[Tufte])\n\nAs you can see, if you instrument a lot of your functions, the number of calls can add up quickly (this result was from running 8 tests). So, even though the runtime checks are only taking microseconds, the overall effect can be dramatic.\n\nHere's how fast those tests are when we turn off Guardrails altogether (one call, because\nwe measured the entire test suite runtime instead of the overhead of non-existent runtime checks):\n\n[source]\n-----\n     nCalls        Max       Mean   MAD      Clock  Total\n          1    72.38ms    72.38ms   ±0%    72.38ms   100%\n-----\n\nAs you can see, the performance can be a significant drag on development, often leading people to strip out their checks, a thing that I've had to do in my own libraries in the past because it hurt downstream users. No more! We now have various ways of improving the situation.\n\n[#check-throttling]\n=== Limiting Max Checks Per Second\n\nIn version 1.2.0 and above you can tune Guardrails to limit the number of times a function is checked per second. This can have a huge performance benefit for functions that are called in loops and possibly involve complex and expensive checks.\n\nThe limit can be applied globally, to a namespace, or even to a function (recommended), by setting `:guardrails/mcps` to an integer. Like most other options, you can place it in the global `guardrails.edn`, the compiler config, the metadata of a namespace, or in the attribute map of a `\u003edefn`. For example:\n\n[source, clojure]\n-----\n(\u003edefn f\n  {:guardrails/mcps 100}\n  [x]\n  [int? =\u003e int?]\n  ...)\n-----\n\nThe performance boost from this setting can be dramatic. The Fulcrologic Statecharts library uses Guardrails extensively for internal function checks, and without an MCPS limit some state changes can take human-perceptible amounts of time (like seconds). With it applied the performance impact returns to a virtually unnoticeable level. Measurements on this particular library indicate that each *check* takes around 20 microseconds, but the overhead of the max-calls-per-second is only 20 or so nanoseconds (on an M1 Max Mac Studio). So, when a calculation ends up causing 100k+ checks (remember there is a check for each arg, and one for the return value) enabling MCPS makes things run literally 1000x faster.\n\nHere's that same set of 8 tests we showed earlier, but with MCPS set to 100 the Guardrails overhead is reduced to only a few milliseconds! In other words, the non-exhaustive checking makes it appear as if guardrails isn't even there. Since it is very common to have just a handful of heavily called functions, dropping each of their check counts to 100 means that you're more likely to only run a few thousand checks in total.\n\nOf course, the downside is that you are no longer getting rigorous data flow checking, but for functions that are called heavily this is an acceptable trade-off, since the probability of detecting some kind of problem can be tuned as you see fit.\n\nThe throttling always checks the \"leading edge\" first; from there it tracks a counter, and uses the high resolution timer to calculate the current number of checks per second that have been done. If that exceeds the set limit, the check is skipped (and the time will change, but not the check count), so after enough time has elapsed, more checks will happen. This has the tendency to \"spread out\" the checks over time, but of course even high resolution timers are going to give you a lot of jitter at a high call frequency.\n\n[#dynamic-exclusions]\n=== Dynamic Exclusions\n\nVersion 1.2.0 also includes the ability to turn checks completely on or off, including at runtime, on a wide or granular level, such as for a namespace or even a function, both in CLJ and CLJS. The functions for controlling this are in `com.fulcrologic.guardrails.config`:\n\n* `(config/exclude-checks! ns-or-fn)` - Turns off checking for an entire ns, or just a single fully-qualified symbol.\n* `(config/allow-checks! ns-or-fn)` - Turns on checking for an entire ns, or just a single fully-qualified symbol.\n* `(config/excluded? ns-or-fn)` - Indicate if the given (entire ns) or fn (qualified symbol) is excluded from checks.\n* `(config/clear-exclusions!)` - Make everything, even in libraries that export exclusions, run checks. See next section.\n* `(config/reset-exclusions!)` - Re-apply any library exclusion exports (resets exclusions to what they were at startup). See next section.\n\n[#static-exclusions]\n=== Static Exclusions (Special Attention Library Authors)\n\nMost libraries have a main surface API, and then a bunch of internal functions. It is useful to instrument all of these with Guardrails in order to get the benefits of documentation, validation during development, and verification while testing.\n\nUnfortunately, this can have a huge performance impact on downstream consumers of your library that also use Guardrails. It makes sense that a library author should indicate which functions comprise the *public* API (and should be checked by downstream users), and which are considered more *internal* and should only be checked when the author is working on the library itself.\n\nLibrary authors (and application authors as well) can include a file at the top level of their classpath (e.g. src or resources folder, usually) with the special name `guardrails-export.edn` which contains a config map that can exclude a set of namespaces from ALL checks. To get the checking config at runtime, the `\u003edefn` functions in those namespaces will only run a simple check on a volatile, so setting these exclusions returns things to pretty much full performance (~= no Guardrails used at all).\n\nFor example, `src/main/guardrails-export.edn` in the https://github.com/fulcrologic/statecharts[Fulcrologic statecharts library] looks something like this:\n\n[source, clojure]\n-----\n{:exclude #{com.fulcrologic.statecharts.algorithms.v20150901-impl}}\n-----\n\nRemember, this goes in a *specially-named* file, *not* in the primary guardrails configuration file, since these are meant to be seen by downstream consumers (like data_readers.clj).\n\nA quick implementation note: In order to make this work in Clojurescript a macro must run that can read the JVM classpath, and compile all the exclusions found in on-disk (and in JAR files) into a runtime set. The same happens in Clojure (though in CLJ you can read the fs again at any time).\n\nThe set of exclusions found in export files at load time is what `reset-exclusions!` will restore if you have dynamically changed the exclusions at runtime. Basically this load-time set is kept in a var for exactly this reason since CLJS cannot re-trigger a classpath scan.\n\nNOTE: As a library *author* these exclusions will end up applying to your code as well, since it is difficult to tell which export file belongs to which project on the classpath. Thus the beginning of your test namespaces (and possibly your non-published user ns) should all start with a call to `config/clear-exclusions!` if you want to include your implementation checks while running your tests and working on your library code.\n\n== Why?\n\nClojure spec's instrument (and Orchestra's outstrument) have a number of disadvantages when trying to use them for\nthis purpose. Specifically, they are side-effecting after-calls that do not play particularly well with hot code reload,\nand always throw when there is a failed spec.  Furthermore, management of the accidental inclusion of specs in your cljs\nbuilds (which increase build size) is a constant pain when writing separate specs for functions (the specs end up in\na whole other file, inclusion needs to be via a development ns, and things easily get out of date).\n\nThis library is a middle ground between the features of raw Clojure spec and George Lipov's Ghostwheel.\nMuch of the source code in this library is directly from https://github.com/gnl/ghostwheel[Ghostwheel].\n\nThis library's goals are:\n\n- The ability to use a simple DSL to declare the spec with a function (taken from Ghostwheel). See that library's docs\nfor *syntax* of `\u003edefn`, `\u003edef`, etc.\n- The ability to support dead-code elimination in cljs.\n- No reliance on generative testing facilities/checkers. No orchestra/instrument stuff.\n- Good output when a function receives or emits an incorrect value.\n- The ability to control if a spec failure causes a throw (instrument always throws), because a lot of the time\nduring development your spec is just wrong, and crashing your program is very inconvenient. You just want a log message\nto make you aware.\n\nwithout the extra overhead of Ghostwheel's support for:\n\n* Automatic generative testing stuff.\n* Tracing.\n* Side-effect detection/warning.\n\n\n== Copyright and License\n\nThe code and documentation taken from Ghostwheel is by George Lipov and follows the ownership/copyright of that library.\nThe modifications in this library are copyrighted by Fulcrologic, LLC.\n\nThis library follows Ghostwheel's original license: Eclipse public license version 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffulcrologic%2Fguardrails","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffulcrologic%2Fguardrails","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffulcrologic%2Fguardrails/lists"}