branch: externals/llm
commit 2426f827d7d46e4650a4e26c61612251d0d9c029
Author: Roman Scherer <ro...@burningswell.com>
Commit: Roman Scherer <ro...@burningswell.com>

    Add process filter error handling
---
 plz-media-type.el | 206 ++++++++++++++++++++++++++++--------------------------
 1 file changed, 106 insertions(+), 100 deletions(-)

diff --git a/plz-media-type.el b/plz-media-type.el
index 37c725c5f6..c20855487d 100644
--- a/plz-media-type.el
+++ b/plz-media-type.el
@@ -4,6 +4,10 @@
 
 ;; Author: r0man <ro...@burningswell.com>
 ;; Maintainer: r0man <ro...@burningswell.com>
+;; URL: https://github.com/r0man/plz-media-type.el
+;; Version: 0.1-pre
+;; Package-Requires: ((emacs "26.3"))
+;; Keywords: comm, network, http
 
 ;; This file is part of GNU Emacs.
 
@@ -32,34 +36,6 @@
 ;; JSON, XML, HTML, and binary data.  It allows for extensible
 ;; processing of additional types through subclassing.
 
-;;; Examples:
-
-;; Non-streaming requests
-;; ======================
-
-;; Make a syncrounous HTTP request and use the default media type
-;; association list to decode the body of the HTTP response.
-
-;; (plz-media-type-request
-;;   'get "https://httpbin.org/json";
-;;   :as `(media-types ,plz-media-types))
-
-
-;; Streaming requests
-;; ==================
-
-;; Make a syncrounous HTTP request to an endpoint that returns newline
-;; delimited JSON objects. The handler will be called 5 times, each
-;; time with the parse JSON object.
-
-;; (plz-media-type-request
-;;   'get "https://httpbin.org/stream/5";
-;;   :as `(media-types
-;;         ((application/json
-;;           . ,(plz-media-type:application/x-ndjson
-;;               :handler (lambda (object)
-;;                          (message "%s" object)))))))
-
 ;;; Code:
 
 ;;;; Requirements
@@ -68,6 +44,13 @@
 (require 'eieio)
 (require 'plz)
 
+(define-error 'plz-media-type-filter-error
+              "plz-media-type: Error in process filter"
+              'plz-error)
+
+(cl-defstruct (plz-media-type-filter-error (:include plz-error))
+  cause)
+
 (defclass plz-media-type ()
   ((type
     :documentation "The media type."
@@ -196,15 +179,18 @@ CHUNK is a part of the HTTP body."
                        (media-type (plz-media-type--of-response media-types 
response))
                        (coding-system (plz-media-type-coding-system 
media-type)))
                   (setq-local plz-media-type--current media-type)
+                  (setq-local plz-media-type--response
+                              (make-plz-response
+                               :headers (plz-response-headers response)
+                               :status (plz-response-status response)
+                               :version (plz-response-version response)))
                   (when-let (body (plz-response-body response))
                     (when (> (length body) 0)
                       (setf (plz-response-body response)
                             (decode-coding-string body coding-system))
                       (delete-region body-start (point-max))
                       (set-marker (process-mark process) (point))
-                      (plz-media-type-process media-type process response)))
-                  (setf (plz-response-body response) nil)
-                  (setq-local plz-media-type--response response))))))
+                      (plz-media-type-process media-type process 
response))))))))
         (when moving
           (goto-char (process-mark process)))))))
 
@@ -213,16 +199,16 @@ CHUNK is a part of the HTTP body."
 (defclass plz-media-type:application/octet-stream (plz-media-type)
   ((type :initform 'application)
    (subtype :initform 'octet-stream))
-  "A media type class that handles the processing of octet stream
+  "Media type class that handles the processing of octet stream
 HTTP responses.  The media type sets the body slot of the
-plz-response struct to the unmodified value of the HTTP response
+plz-response structure to the unmodified value of the HTTP response
 body.  It is used as the default media type processor.")
 
 (cl-defmethod plz-media-type-else ((media-type 
plz-media-type:application/octet-stream) error)
   "Transform the ERROR into a format suitable for MEDIA-TYPE."
-  (let ((response (plz-error-response error)))
-    (setf (plz-error-response error) (plz-media-type-then media-type response))
-    error))
+  (when-let (response (plz-error-response error))
+    (setf (plz-error-response error) (plz-media-type-then media-type 
response)))
+  error)
 
 (cl-defmethod plz-media-type-then ((media-type 
plz-media-type:application/octet-stream) response)
   "Transform the RESPONSE into a format suitable for MEDIA-TYPE."
@@ -264,10 +250,10 @@ be `hash-table', `alist' (the default) or `plist'."
     :initarg :object-type
     :initform 'alist
     :type symbol))
-  "A media type class that handles the processing of HTTP responses
+  "Media type class that handles the processing of HTTP responses
 in the JSON format.  The HTTP response is processed in a
 non-streaming way.  After the response has been received, the
-body of the plz-response struct is set to the result of parsing
+body of the plz-response structure is set to the result of parsing
 the HTTP response body with the `json-parse-buffer' function.
 The arguments to the `json-parse-buffer' can be customized by
 making an instance of this class and setting its slots
@@ -290,16 +276,16 @@ accordingly.")
 
 (defclass plz-media-type:application/json-array 
(plz-media-type:application/json)
   ((handler
-    :documentation "A function that will be called for each object in the JSON 
array."
+    :documentation "Function that will be called for each object in the JSON 
array."
     :initarg :handler
     :type (or function symbol)))
-  "A media type class that handles the processing of HTTP responses
+  "Media type class that handles the processing of HTTP responses
 in a JSON format that assumes that the object at the top level is
 an array.  The HTTP response is processed in a streaming way.
 Each object in the top level array will be parsed with the
 `json-parse-buffer' function.  The function in the :handler slot
 will be called each time a new object arrives.  The body slot of
-the plz-response struct passed to the THEN and ELSE callbacks
+the plz-response structure passed to the THEN and ELSE callbacks
 will always be set to nil.")
 
 (defun plz-media-type:application/json-array--parse-next (media-type)
@@ -364,17 +350,17 @@ will always be set to nil.")
 (defclass plz-media-type:application/x-ndjson (plz-media-type:application/json)
   ((subtype :initform 'x-ndjson)
    (handler
-    :documentation "A function that will be called for each line that contains 
a JSON object."
+    :documentation "Function that will be called for each line that contains a 
JSON object."
     :initarg :handler
     :initform nil
     :type (or function null symbol)))
-  "A media type class that handles the processing of HTTP responses
+  "Media type class that handles the processing of HTTP responses
 in a JSON format that assumes that the object at the top level is
 an array.  The HTTP response is processed in a streaming way.
 Each object in the top level array will be parsed with the
 `json-parse-buffer' function.  The function in the :handler slot
 will be called each time a new object arrives.  The body slot of
-the plz-response struct passed to the THEN and ELSE callbacks
+the plz-response structure passed to the THEN and ELSE callbacks
 will always be set to nil.")
 
 (defconst plz-media-type:application/x-ndjson--line-regexp
@@ -415,17 +401,18 @@ will always be set to nil.")
 
 (defclass plz-media-type:application/xml 
(plz-media-type:application/octet-stream)
   ((subtype :initform 'xml))
-  "A media type class that handles the processing of HTTP responses
+  "Media type class that handles the processing of HTTP responses
 in the XML format.  The HTTP response is processed in a
 non-streaming way.  After the response has been received, the
-body of the plz-response struct is set to the result of parsing
+body of the plz-response structure is set to the result of parsing
 the HTTP response body with the `libxml-parse-html-region'
 function.")
 
 (cl-defmethod plz-media-type-then ((media-type plz-media-type:application/xml) 
response)
   "Transform the RESPONSE into a format suitable for MEDIA-TYPE."
   (with-slots (array-type false-object null-object object-type) media-type
-    (setf (plz-response-body response) (libxml-parse-html-region))
+    (setf (plz-response-body response)
+          (libxml-parse-html-region (point-min) (point-max) nil))
     response))
 
 ;; Content Type: text/html
@@ -433,10 +420,10 @@ function.")
 (defclass plz-media-type:text/html (plz-media-type:application/xml)
   ((type :initform 'text)
    (subtype :initform 'xml))
-    "A media type class that handles the processing of HTTP responses
+  "Media type class that handles the processing of HTTP responses
 in the HTML format.  The HTTP response is processed in a
 non-streaming way.  After the response has been received, the
-body of the plz-response struct is set to the result of parsing
+body of the plz-response structure is set to the result of parsing
 the HTTP response body with the `libxml-parse-html-region'
 function.")
 
@@ -446,7 +433,7 @@ function.")
     (application/xml . ,(plz-media-type:application/xml))
     (text/html . ,(plz-media-type:text/html))
     (t . ,(plz-media-type:application/octet-stream)))
-  "Alist from media type to content type.")
+  "Association list from media type to content type.")
 
 (defun plz-media-type--handle-sync-http-error (error media-types)
   "Handle the synchronous HTTP ERROR using MEDIA-TYPES."
@@ -466,9 +453,15 @@ function.")
 
 (defun plz-media-type--handle-sync-error (error media-types)
   "Handle the synchronous ERROR using MEDIA-TYPES."
-  (if (eq 'plz-http-error (car error))
-      (plz-media-type--handle-sync-http-error error media-types)
-    (signal (car error) (cdr error))))
+  (cond
+   ((plz-media-type-filter-error-p error)
+    (signal 'plz-media-type-filter-error
+            (list (plz-media-type-filter-error-message error)
+                  (plz-media-type-filter-error-response error)
+                  (plz-media-type-filter-error-cause error))))
+   ((eq 'plz-http-error (car error))
+    (plz-media-type--handle-sync-http-error error media-types))
+   (t (signal (car error) (cdr error)))))
 
 (cl-defun plz-media-type-request
     (method
@@ -523,9 +516,9 @@ It may be:
   be used as a basis and is meant to be extended by users.  If no
   media type was found for a content type, it will be handled by
   the default octet stream media type.  When this option is used,
-  the THEN callback will always receive a plz-response struct as
-  argument, and the ELSE callback always a plz-error struct.  The
-  plz-response struct will always have the status and header
+  the THEN callback will always receive a plz-response structure as
+  argument, and the ELSE callback always a plz-error structure.  The
+  plz-response structure will always have the status and header
   slots set.  The body slot depends on the media type
   implementation.  In the case for JSON, HTML, XML it will
   contain the decoded response body.  When receiving JSON for
@@ -587,51 +580,64 @@ not.
   (if-let (media-types (pcase as
                          (`(media-types ,media-types)
                           media-types)))
-      (condition-case error
-          (let* ((buffer)
-                 (plz-curl-default-args (cons "--no-buffer" 
plz-curl-default-args))
-                 (result (plz method url
-                           :as 'buffer
-                           :body body
-                           :body-type body-type
-                           :connect-timeout connect-timeout
-                           :decode decode
-                           :else (lambda (error)
-                                   (setq buffer (current-buffer))
-                                   (when (or (functionp else) (symbolp else))
-                                     (funcall else (plz-media-type-else
-                                                    plz-media-type--current
-                                                    error))))
-                           :finally (lambda ()
-                                      (unwind-protect
-                                          (when (functionp finally)
-                                            (funcall finally))
-                                        (when (buffer-live-p buffer)
-                                          (kill-buffer buffer))))
-                           :headers headers
-                           :noquery noquery
-                           :process-filter (lambda (process chunk)
-                                             (plz-media-type-process-filter 
process media-types chunk))
-                           :timeout timeout
-                           :then (if (symbolp then)
-                                     then
-                                   (lambda (_)
+      (let ((buffer) (filter-error))
+        (condition-case error
+            (let* ((plz-curl-default-args (cons "--no-buffer" 
plz-curl-default-args))
+                   (result (plz method url
+                             :as 'buffer
+                             :body body
+                             :body-type body-type
+                             :connect-timeout connect-timeout
+                             :decode decode
+                             :else (lambda (error)
                                      (setq buffer (current-buffer))
-                                     (when (or (functionp then) (symbolp then))
-                                       (funcall then (plz-media-type-then
-                                                      plz-media-type--current
-                                                      
plz-media-type--response))))))))
-            (cond ((bufferp result)
-                   (unwind-protect
-                       (with-current-buffer result
-                         (plz-media-type-then plz-media-type--current 
plz-media-type--response))
-                     (when (buffer-live-p result)
-                       (kill-buffer result))))
-                  ((processp result)
-                   result)
-                  (t (user-error "Unexpected response: %s" result))))
-        ;; TODO: How to kill the buffer for sync requests that raise an error?
-        (plz-error (plz-media-type--handle-sync-error error media-types)))
+                                     (when (or (functionp else) (symbolp else))
+                                       (funcall else (or filter-error
+                                                         (plz-media-type-else
+                                                          
plz-media-type--current
+                                                          error)))))
+                             :finally (lambda ()
+                                        (unwind-protect
+                                            (when (functionp finally)
+                                              (funcall finally))
+                                          (when (buffer-live-p buffer)
+                                            (kill-buffer buffer))))
+                             :headers headers
+                             :noquery noquery
+                             :process-filter (lambda (process chunk)
+                                               (condition-case cause
+                                                   
(plz-media-type-process-filter process media-types chunk)
+                                                 (error
+                                                  (let ((buffer 
(process-buffer process)))
+                                                    (setq filter-error
+                                                          
(make-plz-media-type-filter-error
+                                                           :cause cause
+                                                           :message (format 
"error in process filter: %S" cause)
+                                                           :response (when 
(buffer-live-p buffer)
+                                                                       
(with-current-buffer buffer
+                                                                         
plz-media-type--response))))
+                                                    (delete-process 
process)))))
+                             :timeout timeout
+                             :then (if (symbolp then)
+                                       then
+                                     (lambda (_)
+                                       (setq buffer (current-buffer))
+                                       (when (or (functionp then) (symbolp 
then))
+                                         (funcall then (plz-media-type-then
+                                                        plz-media-type--current
+                                                        
plz-media-type--response))))))))
+              (cond ((bufferp result)
+                     (unwind-protect
+                         (with-current-buffer result
+                           (plz-media-type-then plz-media-type--current 
plz-media-type--response))
+                       (when (buffer-live-p result)
+                         (kill-buffer result))))
+                    ((processp result)
+                     result)
+                    (t (user-error "Unexpected response: %s" result))))
+          ;; TODO: How to kill the buffer for sync requests that raise an 
error?
+          (plz-error
+           (plz-media-type--handle-sync-error (or filter-error error) 
media-types))))
     (apply #'plz (append (list method url) rest))))
 
 ;;;; Footer

Reply via email to