SlideShare a Scribd company logo
Fun With Errors?
Clojure Finland Meetup, MOW@Tampere
Tommi Reiman
tommi@metosin.
@ikitommi
Who is interested in errors?
Developers <-- you, me!, the girl/guy next to you?
Programs
Systems
End Users
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)
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
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
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
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
Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
ETA
ELM
Clojure 3rd party thingies
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)
Pretty (developers)
https://github.com/AvisoNovate/pretty
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]}}
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} }
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
Expound 2/2
Ships with custom pretty printer
Spell-spec
https://github.com/bhauman/spell-spec
spell-spec.alpha/strict-keys
Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
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?
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)
Principles
Data-oriented libraries & tools
Design to Fail-fast
Good developer experience
Lead by Example
Towards clj-commons error formatter?
Fail Fast
During static analysis (e.g. linter)
At compile-time (macros, defs)
At creation-time
At development-time (schema/spec annos)
At runtime ( )
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]
...)})
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)]
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}))
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)))))
Formatting (DEMO)
Formatting Guide
Respect the user
Use lame colors, it's not xmas
Just simple tips (or none)
Link to documentation (if needed)
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})
On route con ict 2/2
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})
Invalid Route Data 2/2
All solved?
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?)
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]}])
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?
Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
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.
Thanks.

More Related Content

PDF
Stop Monkeys Fall
PDF
Binaries Are Not Only Output
PPT
A Life of breakpoint
ODP
Отладка в GDB
PDF
Interceptors: Into the Core of Pedestal
PDF
Debugging concurrency programs in go
ODP
The why and how of moving to php 5.4
PDF
Advanced debugging  techniques in different environments
Stop Monkeys Fall
Binaries Are Not Only Output
A Life of breakpoint
Отладка в GDB
Interceptors: Into the Core of Pedestal
Debugging concurrency programs in go
The why and how of moving to php 5.4
Advanced debugging  techniques in different environments

What's hot (20)

PDF
Modern PHP
PDF
A CTF Hackers Toolbox
PDF
Oredev 2015 - Taming Java Agents
PPT
typemap in Perl/XS
PDF
Rust LDN 24 7 19 Oxidising the Command Line
ODP
The why and how of moving to PHP 5.5/5.6
PDF
Building robust and friendly command line applications in go
TXT
Mona cheatsheet
ODP
The why and how of moving to PHP 5.4/5.5
PDF
Hacking with ruby2ruby
PDF
Effective ES6
PDF
KCDC 2018 - Rapid API Development with Sails
TXT
Exploit techniques - a quick review
ODP
The why and how of moving to php 5.4/5.5
PPTX
C++ Core Guidelines
PPTX
Modern JS with ES6
ODP
Caching and tuning fun for high scalability @ FrOSCon 2011
PDF
What you need to remember when you upload to CPAN
ODP
Clojure: Practical functional approach on JVM
PDF
Os Treat
Modern PHP
A CTF Hackers Toolbox
Oredev 2015 - Taming Java Agents
typemap in Perl/XS
Rust LDN 24 7 19 Oxidising the Command Line
The why and how of moving to PHP 5.5/5.6
Building robust and friendly command line applications in go
Mona cheatsheet
The why and how of moving to PHP 5.4/5.5
Hacking with ruby2ruby
Effective ES6
KCDC 2018 - Rapid API Development with Sails
Exploit techniques - a quick review
The why and how of moving to php 5.4/5.5
C++ Core Guidelines
Modern JS with ES6
Caching and tuning fun for high scalability @ FrOSCon 2011
What you need to remember when you upload to CPAN
Clojure: Practical functional approach on JVM
Os Treat
Ad

Similar to Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere (13)

PDF
Reitit - Clojure/North 2019
PPTX
Schema, validation and generative testing
PDF
Clojure class
PDF
Clojure functions midje
PDF
Spec: a lisp-flavoured type system
PDF
Practical REPL-driven Development with Clojure
PDF
Рефакторинг Ruby-кода / Анатолий Макаревич (Evrone)
PDF
The details of CI/CD environment for Ruby
PDF
How CPAN Testers helped me improve my module
PDF
Refactoring to Macros with Clojure
PDF
Solving New School with the Old School (Clojure)
KEY
Hidden treasures of Ruby
ODP
Clojure made simple - Lightning talk
Reitit - Clojure/North 2019
Schema, validation and generative testing
Clojure class
Clojure functions midje
Spec: a lisp-flavoured type system
Practical REPL-driven Development with Clojure
Рефакторинг Ruby-кода / Анатолий Макаревич (Evrone)
The details of CI/CD environment for Ruby
How CPAN Testers helped me improve my module
Refactoring to Macros with Clojure
Solving New School with the Old School (Clojure)
Hidden treasures of Ruby
Clojure made simple - Lightning talk
Ad

More from Metosin Oy (19)

PDF
Navigating container technology for enhanced security by Niklas Saari
PPTX
Where is Technical Debt?
PPTX
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
PDF
Serverless Clojure and ML prototyping: an experience report
PDF
Designing with malli
PDF
Malli: inside data-driven schemas
PDF
Naked Performance With Clojure
PDF
Clojutre Real Life (2012 ClojuTRE Retro Edition)
PDF
The Ancient Art of Data-Driven - reitit, the library -
PDF
Craft Beer & Clojure
PDF
Performance and Abstractions
PDF
ClojuTRE2016 Opening slides
PDF
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
PDF
ClojuTRE - a (very) brief history
PDF
Wieldy remote apis with Kekkonen - ClojureD 2016
PDF
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
PDF
Clojure in real life 17.10.2014
PDF
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
PDF
Swaggered web apis in Clojure
Navigating container technology for enhanced security by Niklas Saari
Where is Technical Debt?
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
Serverless Clojure and ML prototyping: an experience report
Designing with malli
Malli: inside data-driven schemas
Naked Performance With Clojure
Clojutre Real Life (2012 ClojuTRE Retro Edition)
The Ancient Art of Data-Driven - reitit, the library -
Craft Beer & Clojure
Performance and Abstractions
ClojuTRE2016 Opening slides
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
ClojuTRE - a (very) brief history
Wieldy remote apis with Kekkonen - ClojureD 2016
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
Clojure in real life 17.10.2014
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Swaggered web apis in Clojure

Recently uploaded (20)

PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
How Creative Agencies Leverage Project Management Software.pdf
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
Transform Your Business with a Software ERP System
PPTX
Essential Infomation Tech presentation.pptx
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
ai tools demonstartion for schools and inter college
PPTX
L1 - Introduction to python Backend.pptx
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
AI in Product Development-omnex systems
PPTX
Operating system designcfffgfgggggggvggggggggg
PDF
medical staffing services at VALiNTRY
PDF
PTS Company Brochure 2025 (1).pdf.......
Odoo POS Development Services by CandidRoot Solutions
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
How Creative Agencies Leverage Project Management Software.pdf
VVF-Customer-Presentation2025-Ver1.9.pptx
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Transform Your Business with a Software ERP System
Essential Infomation Tech presentation.pptx
CHAPTER 2 - PM Management and IT Context
ai tools demonstartion for schools and inter college
L1 - Introduction to python Backend.pptx
Wondershare Filmora 15 Crack With Activation Key [2025
How to Choose the Right IT Partner for Your Business in Malaysia
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Reimagine Home Health with the Power of Agentic AI​
AI in Product Development-omnex systems
Operating system designcfffgfgggggggvggggggggg
medical staffing services at VALiNTRY
PTS Company Brochure 2025 (1).pdf.......

Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere

  • 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
  • 9. ETA
  • 10. ELM
  • 11. Clojure 3rd party thingies
  • 12. 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)
  • 14. 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]}}
  • 15. 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} }
  • 16. 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
  • 17. Expound 2/2 Ships with custom pretty printer
  • 20. 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?
  • 21. 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)
  • 22. Principles Data-oriented libraries & tools Design to Fail-fast Good developer experience Lead by Example Towards clj-commons error formatter?
  • 23. Fail Fast During static analysis (e.g. linter) At compile-time (macros, defs) At creation-time At development-time (schema/spec annos) At runtime ( )
  • 24. 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] ...)})
  • 25. 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)]
  • 26. 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}))
  • 27. 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)))))
  • 29. Formatting Guide Respect the user Use lame colors, it's not xmas Just simple tips (or none) Link to documentation (if needed)
  • 30. 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})
  • 31. On route con ict 2/2
  • 32. 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})
  • 35. 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?)
  • 36. 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]}])
  • 37. 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?
  • 39. 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.