Search code examples
jsonrestparsingcommon-lispclos

How to catch parse errors in Lucerne (common lisp)


I'm trying to build a simple rest api in Lucerne, but the clack:call method fails if the json is malformed. So, I extended the bass-app class and added an around method:

(defclass tracker-app (base-app) ()
  (:documentation "An extension of lucerne's base app to control behavior"))

(defmethod clack:call :around ((app tracker-app) env)
  (handler-case (call-next-method)

    (fast-http:cb-message-complete (e)
      (vom:error "could not build message body: ~a" e)
      (respond nil :status 400))

    (:no-error (res) res))) 

(defapp server :class 'tracker-app)

(start server :server woo)

But the parse error continues to crash the server.

I don't know much about clos, so I'm worried I've misunderstood how to catch errors in this context.

stack trace

Edit: Added start information

Edit: Added stack trace


Solution

  • Assuming *features* does not contain :catch-any-error, here is a complete test case:

    (ql:quickload :lucerne)
    (defpackage :so.lucerne (:use :cl :lucerne))
    (in-package :so.lucerne)
    
    (defclass tracker-app (base-app) ()
      (:documentation "An extension of lucerne's base app to control behavior"))
    
    (defmethod clack:call :around ((app tracker-app) env)
      (handler-case (call-next-method)
        (fast-http:cb-message-complete (e)
          (warn "could not build message body: ~a" e)
          (respond nil :status 400))
        #+catch-any-error
        (error (e) (break "BREAK with ~a" e))
        (:no-error (res) res))) 
    
    (defmethod clack:call ((app tracker-app) env)
      (error "Oh No"))
    
    (defapp server :class 'tracker-app)
    (start server :server :woo)
    

    When I try to load localhost:8000, the following error is shown:

    Callback Error: the message-complete callback failed
      Oh No
       [Condition of type FAST-HTTP.ERROR:CB-MESSAGE-COMPLETE]
    

    Pressing Enter on [Condition of type FAST-HTTP.ERROR:CB-MESSAGE-COMPLETE] gives:

    #<FAST-HTTP.ERROR:CB-MESSAGE-COMPLETE {10048315C3}>
    --------------------
    The object is a CONDITION of type FAST-HTTP.ERROR:CB-MESSAGE-COMPLETE.
    FORMAT-CONTROL: NIL
    FORMAT-ARGUMENTS: NIL
    DESCRIPTION: "the message-complete callback failed"
    ERROR: #<SIMPLE-ERROR "Oh No" {1004831583}>
    

    The error wraps another error.

    Now if I (push :catch-any-error *features*) and recompile the above method, the same test makes the code reach the (break ...) statement, which is shown as BREAK with Oh No.

    Explanation

    No fast-http:cb-message-complete is caught, and in fact no such condition is signaled at this point; instead at this location we only can catch the specific error that was signaled. It is only higher up in the call stack that errors are wrapped inside fast-http:cb-message-complete errors.

    Solution

    In your case you can directly catch jonathan.error:<jonathan-error> (unusual naming convention, but ok), the base class of all errors in the jonathan library (you could catch the specific error type, but then you risk missing some other cases).