DEV Community

Cover image for Clojure Is Awesome!!! [PART 12]
André Borba
André Borba

Posted on

Clojure Is Awesome!!! [PART 12]

(ns chain-of-responsibility
  (:require [clojure.pprint :as pp]
            [clojure.spec.alpha :as s]))

;; === Protocols and Utility Functions ===
(defprotocol RequestHandler
  (handle-request [this request])
  (set-next [this handler]))

(defprotocol ErrorHandler
  (error [this message]))

(extend-type clojure.lang.PersistentArrayMap
  ErrorHandler
  (error [_ message] {:error message}))

(defmacro defhandler [name fields & body]
  `(defrecord ~name ~fields
     RequestHandler
     ~@body))

;; === Validation Specifications ===
(s/def ::data (s/map-of keyword? string?))
(s/def ::request (s/keys :req-un [::data]))

;; === Handlers ===
(defhandler AuthenticationHandler [next-handler]
  (handle-request [this request]
    "Handle authentication by checking for a valid auth token."
    (if-let [auth-token (:auth-token request)]
      (if (= auth-token "valid-token")
        (if next-handler
          (handle-request next-handler (assoc request :authenticated true))
          (assoc request :authenticated true))
        (error {} "Invalid authentication token"))
      (error {} "Missing authentication token")))
  (set-next [_ handler]
    "Set the next handler in the chain."
    (->AuthenticationHandler handler)))

(defhandler AuthorizationHandler [next-handler]
  (handle-request [this request]
    "Handle authorization by checking user roles."
    (if (:authenticated request)
      (if (contains? (:roles request) :admin)
        (if next-handler
          (handle-request next-handler (assoc request :authorized true))
          (assoc request :authorized true))
        (error {} "Insufficient permissions"))
      (if next-handler
        (handle-request next-handler request)
        request)))
  (set-next [_ handler]
    "Set the next handler in the chain."
    (->AuthorizationHandler handler)))

(defhandler ValidationHandler [next-handler]
  (handle-request [this request]
    "Validate request data format."
    (if (s/valid? ::request request)
      (if next-handler
        (handle-request next-handler (assoc request :validated true))
        (assoc request :validated true))
      (error {} "Invalid request data format")))
  (set-next [_ handler]
    "Set the next handler in the chain."
    (->ValidationHandler handler)))

(defhandler LoggingHandler [next-handler]
  (handle-request [this request]
    "Log the request and response."
    (println "\nProcessing request:")
    (pp/pprint (dissoc request :handler))
    (let [response (if next-handler
                     (handle-request next-handler request)
                     request)]
      (println "\nResponse:")
      (pp/pprint response)
      response))
  (set-next [_ handler]
    "Set the next handler in the chain."
    (->LoggingHandler handler)))

(def request-cache (atom {}))

(defhandler CacheHandler [next-handler]
  (handle-request [this request]
    "Handle caching of requests."
    (if-let [cached (@request-cache (:id request))]
      (do
        (println "Cache hit for request:" (:id request))
        cached)
      (let [response (if next-handler
                       (handle-request next-handler request)
                       request)]
        (when (:id request)
          (swap! request-cache assoc (:id request) response))
        response)))
  (set-next [_ handler]
    "Set the next handler in the chain."
    (->CacheHandler handler)))

;; === Request Processing ===
(defn build-chain []
  (-> (->LoggingHandler nil)
      (set-next (->CacheHandler nil))
      (set-next (->AuthenticationHandler nil))
      (set-next (->AuthorizationHandler nil))
      (set-next (->ValidationHandler nil))))

;; === Example Usage ===
(defn run-examples []
  (let [chain (build-chain)]
    (println "\n=== Valid Admin Request ===")
    (handle-request chain
                   {:id "req-1"
                    :auth-token "valid-token"
                    :roles #{:admin}
                    :data {:name "Borba"
                           :action "read"}})

    (println "\n=== Invalid Token ===")
    (handle-request chain
                   {:id "req-2"
                    :auth-token "invalid-token"
                    :roles #{:admin}
                    :data {:name "John"}})

    (println "\n=== Missing Token ===")
    (handle-request chain
                   {:id "req-3"
                    :roles #{:admin}
                    :data {:name "Alice"}})

    (println "\n=== Insufficient Permissions ===")
    (handle-request chain
                   {:id "req-4"
                    :auth-token "valid-token"
                    :roles #{:user}
                    :data {:name "Olívia"}})

    (println "\n=== Invalid Data ===")
    (handle-request chain
                   {:id "req-5"
                    :auth-token "valid-token"
                    :roles #{:admin}
                    :data {:name 123}})

    (println "\n=== Cached Request ===")
    (handle-request chain
                   {:id "req-1"
                    :auth-token "valid-token"
                    :roles #{:admin}
                    :data {:name "Borba"
                           :action "read"}})))

(run-examples)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)