Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere

50 %
50 %
Information about Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere

Published on March 27, 2019

Author: metosin

Source: slideshare.net

1. Fun With Errors? Clojure Finland Meetup, MOW@Tampere Tommi Reiman tommi@metosin. @ikitommi

2. Who is interested in errors? Developers <-- you, me!, the girl/guy next to you? Programs Systems End Users

3. Clojure & Error Messages Bare-bones before 1.9.0 Really bad with 1.9.0 (clojure.spec) Kinda ok in 1.10.0 (data + helpers)

4. 1.9.0 (ns kikka (require '[clojure.string])) ; CompilerException clojure.lang.ExceptionInfo: Call to clojure. ; did not conform to spec: In: [1] val: ((require (quote [clojur ; fails spec: :clojure.core.specs.alpha/ns-form at: [:args] pred ; (cat :docstring (? string?) :attr-map (? map?) :clauses :cloju ; Extra input #:clojure.spec.alpha{:problems [{:path [:args], :r ; "Extra input", :pred (clojure.spec.alpha/cat :docstring (cloju ; clojure.core/string?) :attr-map (clojure.spec.alpha/? clojure. ; :clojure.core.specs.alpha/ns-clauses), :val ((require (quote [ ; :via [:clojure.core.specs.alpha/ns-form], :in [1]}], :spec #ob ; "clojure.spec.alpha$regex_spec_impl$reify__2436@3c18dcbf"], :v ; (kikka (require (quote [clojure.string]))), :args (kikka (requ ; [clojure.string])))}, compiling:(/Users/tommi/projects/metosin

5. 1.10.0 (ns kikka (require '[clojure.string])) ; Syntax error macroexpanding clojure.core/ns at ; (spec.cljc:141:1). ((require (quote [clojure.string]))) - ; failed: Extra input spec: :clojure.core.specs.alpha/ns-form

6. New helpers in 1.10.0 Throwable->map , ex-triage , ex-str clojure.main/repl with :caught option https://clojure.org/reference/repl_and_main

7. New ex-data in 1.10.0 :clojure.error/phase - phase indicator :clojure.error/source - le name (no path) :clojure.error/line - integer line number :clojure.error/column - integer column number :clojure.error/symbol - symbol being expanded/compiled/invoked :clojure.error/class - cause exception symbol :clojure.error/cause - cause exception message :clojure.error/spec - explain-data for a spec error

8. ETA

9. ELM

10. Clojure 3rd party thingies

11. IDEs & Linters Kibit (https://github.com/jonase/kibit) Eastwood (https://github.com/jonase/eastwood) Cursive (https://cursive-ide.com/) Joker (https://github.com/candid82/joker) CLJ-Kondo (https://github.com/borkdude/clj- kondo)

12. Pretty (developers) https://github.com/AvisoNovate/pretty

13. Schema https://github.com/plumatic/schema (app {:request-method :post :uri "/api/plus" :query-params {"x" "1", "y" "-7"}}) ; {:status 500, ; :body {:schema {:total "(constrained Int PositiveInt)"}, ; :errors {:total "(not (PositiveInt -6))"}, ; :type :reitit.coercion/response-coercion, ; :coercion :schema, ; :value {:total -6}, ; :in [:response :body]}}

14. clojure.spec (require '[clojure.spec.alpha :as s]) (s/def ::city string?) (s/def ::state string?) (s/def ::place (s/keys :req-un [::city ::state])) (s/explain-data ::place {:city "Denver", :state :CO}) ;(:problems ({:path [:state], ; :pred clojure.core/string?, ; :val :CO, ; :via [::place ::state], ; :in [:state]}), ; :spec :spec-tools.data-spec-test/place, ; :value {:city "Denver", :state :CO} }

15. Expound 1/2 https://github.com/bhb/expound (require '[expound.alpha :as expound]) (expound/expound ::place {:city "Denver", :state :CO}) ;; -- Spec failed -------------------- ;; ;; {:city ..., :state :CO} ;; ^^^ ;; ;; should satisfy ;; ;; string? ;; ;; ------------------------- ;; Detected 1 error

16. Expound 2/2 Ships with custom pretty printer

17. Spell-spec https://github.com/bhauman/spell-spec spell-spec.alpha/strict-keys

18. Metosin Public Corner Integrating spell-spec with spec-tools for closed validation of spec'd con gurations (with @bhauman-grade error reporting) metosin/reitit is a playground of joyful things, including data, performance and... error messages! Some Schema too?

19. Ingredients Fipp, fast Idiomatic Pretty-Printer https://github.com/brandonbloom/ pp Expound & Spell-Spec Lovely colors from rebel-readline https://github.com/bhauman/rebel-readline Our libraries (spec-tools & friends)

20. Principles Data-oriented libraries & tools Design to Fail-fast Good developer experience Lead by Example Towards clj-commons error formatter?

21. Fail Fast During static analysis (e.g. linter) At compile-time (macros, defs) At creation-time At development-time (schema/spec annos) At runtime ( )

22. On Component 1/2 Clean separation of creation and request-time Support declarative validation at creation-time Correct Initialization, Fast Runtime (defn coerce-request-interceptor "Interceptor for pluggable request coercion. Expects a :coercion of type `reitit.coercion/Coercion` and :parameters, otherwise does not mount." [] {:name ::coerce-request :spec ::rs/parameters :compile (fn [{:keys [coercion parameters]} opts] ...)})

23. On Component 2/2 Schema is great at validating function args: clojure.spec has fdef and s/assert (require '[schema.core :as s]) (s/defn ^:always-validate interceptor-x [opts :- {:string? s/Bool}] ...) (coerce-request-interceptor {}) ; Syntax error (ExceptionInfo) compiling at (test.cljc:150:1). ; Input to interceptor-x does not match schema: ; ; [(named {:string? missing-required-key} opts)]

24. On Error No formatting here, just emit a unique id for the error and all the needed info for print the error: (when-let [problems (validate-route-data routes spec)] (exception/fail! ::invalid-route-data {:problems problems}))

25. On (Router) Creation Before there is a way to hook a custom exception handler into a running REPL, we just catch the exceptions ourself on the public api and use a con gured formatter for the errors: (defn router [raw-routes opts] (try ... (catch #?(:clj Exception, :cljs js/Error) e (throw ((get opts :exception identity) e)))))

26. Formatting (DEMO)

27. Formatting Guide Respect the user Use lame colors, it's not xmas Just simple tips (or none) Link to documentation (if needed)

28. On route con ict 1/2 (require '[reitit.core :as r]) (require '[reitit.dev.pretty :as pretty]) (r/router [["/ping"] ["/:user-id/orders"] ["/bulk/:bulk-id"] ["/public/*path"] ["/:version/status"]] {:exception pretty/exception})

29. On route con ict 2/2

30. Invalid Route Data 1/2 (require '[reitit.spec :as spec]) (require '[clojure.spec.alpha :as s]) (s/def ::role #{:admin :user}) (s/def ::roles (s/coll-of ::role :into #{})) (r/router ["/api/admin" {::roles #{:adminz}}] {:validate spec/validate :exception pretty/exception})

31. Invalid Route Data 2/2

32. All solved?

33. Challenges clojure.spec is open by design enables growth (and typos!) clojure.spec is macros (Spec2 adds functions) clojure.spec doesn't support rt-transformations Spec2 is xing some of the things, WIP (Remember Schema, anyone?)

34. Batteries to clojure.spec ? Ability to deep-merge (map) specs Ability to close (map) specs s/select in Spec2 looks promising (WIP, TBD): (s/select ::user [::first ::last ::addr {::addr [::street ::city ::state ::zip]}])

35. While waiting spec-tools.spell will have functions to close specs Work nicely with data-specs (DEMO) Might work with normal specs using Coercion https://cljdoc.org/d/metosin/spec- tools/0.9.1/doc/spec-coercion Will integrate to reitit if that ever works st/defn to help validating component options?

36. Final Words Clojure is a Dynamic language -> Fail Fast(!!!) Fast linters coming ( joker & clj-kondo ) Ongoing work to make reitit awesome Fipp, Expound, spell-spec & spec-tools Community should build a kick-ass error formatter & rules for Clojure, Made in ? Join #reitit & #clj-commons in Slack join@metosin.

37. Thanks.

Add a comment