aboutsummaryrefslogtreecommitdiff
path: root/wttrin.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-21 08:03:30 -0400
committerCraig Jennings <c@cjennings.net>2026-06-21 08:03:30 -0400
commit2f469404a4ef8bd0e8cdf776a5d25ba04b63febb (patch)
tree25574906791e0e79985541c7e532c2da4e846718 /wttrin.el
parent275f84076988ee32d976a578d79cf0b22dea6467 (diff)
downloademacs-wttrin-2f469404a4ef8bd0e8cdf776a5d25ba04b63febb.tar.gz
emacs-wttrin-2f469404a4ef8bd0e8cdf776a5d25ba04b63febb.zip
feat: add typed error hierarchy for fetch failures
Define a wttrin-error condition with children wttrin-invalid-input, wttrin-network-error, wttrin-not-found-error, wttrin-service-error, and wttrin-parse-error, so callers branch on the class of a failure instead of matching message text. Synchronous paths signal these directly: a nil query and an unknown geolocation provider now raise wttrin-invalid-input. The async fetch path can't signal across its callback, so it tags the error string with the class via a wttrin-error-type text property. The wttrin-error-message-type accessor reads it back, and two-arg callbacks are untouched. Retyping the classifier also closed two gaps: a missing status and a 2xx with an empty body used to go silent or get mislabeled "Unexpected HTTP status". Both are now parse errors. wttrin-geolocation.el now requires wttrin for the shared conditions. It's only ever loaded through wttrin, so the require is a no-op in practice and just makes the dependency explicit.
Diffstat (limited to 'wttrin.el')
-rw-r--r--wttrin.el60
1 files changed, 52 insertions, 8 deletions
diff --git a/wttrin.el b/wttrin.el
index 2fdf158..b6f59fb 100644
--- a/wttrin.el
+++ b/wttrin.el
@@ -349,10 +349,43 @@ Returns \"just now\" for <60s, \"X minutes ago\", \"X hours ago\", or \"X days a
(concat "?" wttrin-unit-system)
"?"))
+;;; Error Types
+
+;; A small condition hierarchy so callers can branch on the *class* of a
+;; failure instead of matching message text. `wttrin-error' is the parent.
+;; Synchronous code paths signal these directly; the async fetch path tags its
+;; human-readable error string with the class via the `wttrin-error-type' text
+;; property (see `wttrin--error-message'), so two-arg callbacks keep working
+;; while callers that care can read the class.
+
+(define-error 'wttrin-error "wttrin error")
+(define-error 'wttrin-invalid-input "Invalid input" 'wttrin-error)
+(define-error 'wttrin-network-error "Network error" 'wttrin-error)
+(define-error 'wttrin-not-found-error "Location not found" 'wttrin-error)
+(define-error 'wttrin-service-error "Weather service error" 'wttrin-error)
+(define-error 'wttrin-parse-error "Could not parse weather response" 'wttrin-error)
+
+(defun wttrin--error-message (type format-string &rest args)
+ "Format an error message of class TYPE.
+Return the string built from FORMAT-STRING and ARGS with TYPE stored in its
+`wttrin-error-type' text property. This lets the async fetch path hand a
+plain string to callbacks while still carrying the error class; read it back
+with `wttrin-error-message-type'."
+ (propertize (apply #'format format-string args) 'wttrin-error-type type))
+
+(defun wttrin-error-message-type (error-msg)
+ "Return the error-class symbol carried by ERROR-MSG, or nil.
+ERROR-MSG is a string produced by wttrin's async fetch path; its class is
+stored in the `wttrin-error-type' text property. A plain, empty, or nil
+ERROR-MSG has no class."
+ (and (stringp error-msg)
+ (> (length error-msg) 0)
+ (get-text-property 0 'wttrin-error-type error-msg)))
+
(defun wttrin--build-url (query)
"Build wttr.in URL for QUERY with configured parameters."
(when (null query)
- (error "Query cannot be nil"))
+ (signal 'wttrin-invalid-input '("Query cannot be nil")))
(concat "https://wttr.in/"
(url-hexify-string query)
(wttrin-additional-url-params)
@@ -407,22 +440,33 @@ description of what went wrong, or nil on success."
((plist-get status :error)
(wttrin--debug-log "wttrin--handle-fetch-callback: Network error - %s"
(cdr (plist-get status :error)))
- (setq error-msg "Network error — check your connection")
+ (setq error-msg (wttrin--error-message
+ 'wttrin-network-error
+ "Network error — check your connection"))
(message "wttrin: %s" error-msg))
;; HTTP response received — extract body (returns nil for non-2xx)
(t
(let ((http-status (wttrin--extract-http-status)))
(setq data (wttrin--extract-response-body))
- (when (and (not data) http-status)
+ (when (not data)
(setq error-msg
(cond
+ ((null http-status)
+ (wttrin--error-message
+ 'wttrin-parse-error "Could not read weather response"))
((and (>= http-status 400) (< http-status 500))
- (format "Location not found (HTTP %d)" http-status))
+ (wttrin--error-message
+ 'wttrin-not-found-error "Location not found (HTTP %d)" http-status))
((>= http-status 500)
- (format "Weather service error (HTTP %d)" http-status))
- (t (format "Unexpected HTTP status %d" http-status))))
- (when error-msg
- (message "wttrin: %s" error-msg))))))
+ (wttrin--error-message
+ 'wttrin-service-error "Weather service error (HTTP %d)" http-status))
+ ((< http-status 300)
+ (wttrin--error-message
+ 'wttrin-parse-error "Could not parse weather response (HTTP %d)" http-status))
+ (t
+ (wttrin--error-message
+ 'wttrin-error "Unexpected HTTP status %d" http-status))))
+ (message "wttrin: %s" error-msg)))))
(condition-case err
(progn
(wttrin--debug-log "wttrin--handle-fetch-callback: Calling user callback with %s"