duckyuck/jiffy

Catching custom exceptions?

magnars opened this issue · 7 comments

Hi! I started work on the zone-id namespace, thinking that would be an easy place to start. However, I quickly ran into an issue with exceptions.

The generated code for ZoneRulesException looks like this:

(defn ZoneRulesException
  ([s] (ZoneRulesException s nil nil))
  ([s m] (ZoneRulesException s m nil))
  ([s m e] (ex-info s (or m {}))))

The normalized-method needs to catch ZoneRulesException, looking like this:

(defn -normalized [this]
  (try
    (let [rules (.getRules this)]
      (if (.isFixedOffset rules)
        (.getOffset rules Instant/EPOCH)
        this))
    (catch ZoneRulesException e
      this)))  ;; invalid ZoneRegion is not important to this method

So, a plain old ex-info exception won't do. How do we handle that?

Haven't given this much thought, but how about make the custom exceptions in jiffy (there are three of them) records for now? Clojurescript try/catch dispatches on type AFAIK.

If interop from Javascript is a concern down the line we might consider extending js/Error (ala what ex-info does).

I just noticed java.time's three exceptions form a hierarchy

  • ZoneRulesException extends DateTimeException
  • UnsupportedTemporalTypeException extends DateTimeException
  • DateTimeException is the base (albeit concrete) class (extending RuntimeException)

The java.time-implementation is riddled with catch'es of DateTimeException (which includes the subclasses), so this control flow needs to be reflected somehow. My initial though was representing this hierarchy with a marker protocol (e.g. jiffy.date-time-exception/DateTimeException), and having all concrete exception records extend this protocol. Unfortunately, Clojurescript's try/catch dispatches on type and not something akin to a satisfies? check.

As far as I can see we're either 1) stuck with rolling our own try/catch which dispatches on both type and satifisies?, or 2) replace every try/catch of DateTimeException with a try/catch that includes the other two exception subclasses as well.

Nevertheless, we'll have to represent the exceptions with records/types rather than ex-info.

How about a custom try-catch macro that resolves a DateTimeException-catch into all three?

In other words, a custom try-macro that expands in a regular try-macro with handlers for the subclasses of DateTimeException if it is listed?

Sounds good 👍

I've made the decision to target both Clojurescript and Clojure (changes have already been comitted). As such, it would be nice to support https://dev.clojure.org/jira/browse/CLJ-1293 in our hand-rolled try-macro.

The rationale behind targeting both platforms; 1) jiffy can be used as a drop-in replacement for both clj/cljs, particularly useful for cljc-code, and 2) it makes cross-check against OpenJDK implementation for local developent and generative testing more convenient.

As a side note: for the time being, exception constructors in jiffy will return java.time.* exceptions, as they need to extend java.lang.Throwable. This is probably a terrible idea as the burden of handling these differences (require'ing jiffy defrecords vs importing java.time exceptions) falls on every ns catching exceptions. There are probably other pitfalls. I'll be looking into producing Throwable instances from the constructors when code is loaded in Clojure.

Custom try* macro is now available. See contribution guideline for documentation.