{"id":22004937,"url":"https://github.com/jpmonettas/inspectable","last_synced_at":"2025-05-05T17:33:39.065Z","repository":{"id":62432980,"uuid":"97350733","full_name":"jpmonettas/inspectable","owner":"jpmonettas","description":"In the spec table","archived":false,"fork":false,"pushed_at":"2017-08-07T13:47:18.000Z","size":105,"stargazers_count":70,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-05T17:32:43.934Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/jpmonettas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-16T00:47:17.000Z","updated_at":"2025-03-24T07:54:08.000Z","dependencies_parsed_at":"2022-11-01T21:01:25.832Z","dependency_job_id":null,"html_url":"https://github.com/jpmonettas/inspectable","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpmonettas%2Finspectable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpmonettas%2Finspectable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpmonettas%2Finspectable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpmonettas%2Finspectable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpmonettas","download_url":"https://codeload.github.com/jpmonettas/inspectable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252542606,"owners_count":21764996,"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-11-30T00:17:44.303Z","updated_at":"2025-05-05T17:33:39.046Z","avatar_url":"https://github.com/jpmonettas.png","language":"Clojure","funding_links":[],"categories":["Tools"],"sub_categories":[],"readme":"# Inspectable\n\nIn your spec table.\n\nA bunch of tools to help improve your repl experience when using clojure.spec with clojure and clojurescript.\n\n## Installation\n\nFor clojure make sure you are using `[org.clojure/clojure \"1.9.0-alpha17\"]` or newer.\n\nFor clojurescript make sure you are using `[org.clojure/clojurescript \"1.9.854\"]` or newer.\n\nTo include the library add the following to your `:dependencies`.\n\n    [inspectable \"0.2.2\"]\n    \n## Usage\n\nFor clojure :\n\n```clojure\nuser\u003e (require '[inspectable.repl :refer [why browse-spec]])\n```\n\nFor clojurescript :\n\n```clojure\n;; In the clojure repl \nuser\u003e (use 'inspectable.repl)\nuser\u003e (start-cljs)\n\n\n;; In your clojurescript repl\ncljs.user\u003e (require '[inspectable.cljs-repl :refer-macros [why] :refer [browse-spec]])\n```\n\nCurrently inspectable provides two tools: `browse-spec` and `why`. See below to understand how they can help you.\n\n\n## The spec browser (browse-spec)\n\nThe spec browser lets you explore your spec registry through a java swing graphical interface.\nYou can invoke it with different type of arguments:\n\n```clojure\n(browse-spec) ;; open the browser showing all specs in the registry\n(browse-spec \"ring\") ;; open the browser showing specs matching \"ring\" regex\n(browse-spec :ring/request) ;; open the browser pointing to :ring/request spec\n(browse-spec 'clojure.core/let) ;; open the browser pointing to 'clojure.core/let spec\n```\n\nFor example, suppose you are working with [ring](https://github.com/ring-clojure/ring) and have [ring-spec](https://github.com/ring-clojure/ring-spec) loaded,\nthen you can use ```(browse-spec \"ring\")``` to see a list of all specs in the registry that matches \"ring\" in their names. \n\n\u003cimg src=\"/doc/images/browser-all-ring.png?raw=true\"/\u003e\n\nOnce in the browser, you can click on the specs to navigate down to sub specs, or use the top bar \nto navigate back to previous visited specs:\n\n\u003cimg src=\"/doc/images/browser-ring-request.png?raw=true\"/\u003e\n\nAs you can see, browsing a spec shows you a pretty print of the spec form together with a generated spec sample.\n\n\u003cimg src=\"/doc/images/browser-ring-server-port.png?raw=true\"/\u003e\n\nInspectable spec browser also supports browsing multi-specs: \n\n\u003cimg src=\"/doc/images/browser-multi.png?raw=true\"/\u003e\n\nIn case a sample can't be generated the exception will be displayed intead of the example\n\n\u003cimg src=\"/doc/images/browser-sample-not.png?raw=true\"/\u003e\n\n## Specs fail explain (why)\n\nAnother useful tool provided by inspectable is `why`.\nYou can use `why` to understand why clojure.spec is complaining about a spec in three different situations.\n\nFirst, you can use `why` to help you understand the output of `s/explain-data`\n\n```clojure\n(def bad-request (ring.mock.request/request :get \"htp://localhost:69000/test\"))\n\n(s/valid? :ring/request bad-request) ;; =\u003e false\n\n(why (s/explain-data :ring/request bad-request))\n```\n\n\u003cimg src=\"/doc/images/ring-req-fail-pp.png?raw=true\"/\u003e\n\nIn this case the pprint is enough, but if the data structure is a big and deeply nested one, try\nusing the collapsible tree:\n\n\u003cimg src=\"/doc/images/ring-req-fail-tree.png?raw=true\"/\u003e\n\nSecond, `why` can help in situations like calling an instrumented function that fails.\n\nSee [Integrating inspectable with your repl](#integrating-inspectable-with-your-repl) for a way of automatically\ncalling why on this situations.\n\nSuppose we are working with events as defined in [clojure spec guide](https://clojure.org/guides/spec#_multi_spec)\nand some function :\n\n```clojure\n\n(s/fdef only-with-code\n        :args (s/cat :code :error/code\n                     :events (s/coll-of :event/event))\n        :ret :event/event)\n\n(defn only-with-code [code events]\n  ...)\n\n;; instrument it\n(stest/instrument)\n\n;; try to call it with some args\n(why\n (only-with-code 4\n                  [{:event/type :event/search\n                    :event/timestamp 0\n                    :search/url \"/home\"}\n                   {:event/type :event/search\n                    :event/timestamp 5.5\n                    :search/url \"/home\"}\n                   {:event/type :event/error\n                    :error/code 4\n                    :search/url \"/home\"}\n                   {:event/type :event/search\n                    :search/url \"/about\"}]))\n\n```\n\nand you will get :\n\n\u003cimg src=\"/doc/images/fn-instrument-fail.png?raw=true\"/\u003e\n\nThird, `why` can also be used to catch macro expansion exceptions like in the case of:\n\n```clojure\n(why (let [a 1\n           b 2\n           3]\n       (+ a b c)))\n```\n\nwill show you :\n\n\u003cimg src=\"/doc/images/let-fail.png?raw=true\"/\u003e\n\nSee [Integrating inspectable with your repl](#integrating-inspectable-with-your-repl) for a way of automatically\ncalling why on this situations.\n\n\n\n## Integrating inspectable with your repl\n\nYou can call (inspectable.repl/install) to install inspectable on your current repl so every time spec\nthrows a exceptions `why` will be automatically applied over it.\n\n```clojure\n(inspectable.repl/install)\n```\n\nThere is a function `inspectable.repl/repl-caught` you can use for the same purpose if you are starting\nyour own sub repl.\n\nStarting a new repl :\n\n```clojure\n(clojure.main/repl :caught inspectable.repl/repl-caught)\n```\n\n## Workflows integration ideas\n\n### Re-frame\n\nIf you are using re-frame and you have specs for your db you can modify your spec check middleware like :\n\n```clojure\n\n(defn check-and-throw\n  \"throw an exception if db doesn't match the spec\"\n  [a-spec db]\n  (when-not (s/valid? a-spec db)\n    (why (s/explain-data a-spec db)))) ;; \u003c---- use why here\n\n(def check-spec (after (partial check-and-throw :my-app.db/db)))\n\n```\n\n### Cider\n\nIf you are using Cider and Clojurescript \n\n```elisp\n(setq cider-cljs-lein-repl \"(do (use 'inspectable.repl) (start-cljs))\")\n```\n\nso after your `cider-jack-in-clojurescript` everything is ready.\n\n## Related work\n\nIf you are interested in understanding specs when they fail also checkout [expound](https://github.com/bhb/expound)!\n\n## Roadmap\n\n- Multiple themes\n- Reference to the caller for instrumented functions fails.\n- Test and add instructions for React Native\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpmonettas%2Finspectable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpmonettas%2Finspectable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpmonettas%2Finspectable/lists"}