Skip to content

Instantly share code, notes, and snippets.

@skinkade
Last active May 20, 2023 19:21
Show Gist options
  • Save skinkade/fb89aa3772047f5dea660d6c74c7b418 to your computer and use it in GitHub Desktop.
Save skinkade/fb89aa3772047f5dea660d6c74c7b418 to your computer and use it in GitHub Desktop.
Clojure reitit REST API - Automatic camelCase conversion, including swagger support
;; Copyright 2023 Shawn Kinkade
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
(ns yourapp.middleware.field-names
(:require
[clojure.walk :refer [postwalk]]
[camel-snake-kebab.core :refer [->kebab-case-keyword ->camelCase]]
[camel-snake-kebab.extras :refer [transform-keys]]
[camel-snake-kebab.internals.alter-name :refer [AlterName]]))
;; avoids some conversion errors
(extend-protocol AlterName
Long
(alter-name [this _f]
this))
;; This is a bit hacky, but from some general testing seems to work alright
;; order middleware like so:
;; muuntaja/format-response-middleware
;; resp->camel
;; (exception/create-exception-middleware ...)
;; muuntaja/format-request-middleware
;; req->kebab
;; wrap swagger json handler like so:
;; ["/api/swagger.json" {:no-doc true
;; :get (swagger->camel (swagger/create-swagger-handler))}]
(defn req->kebab
[handler]
(fn [req]
(let [xf (partial transform-keys ->kebab-case-keyword)
updated (-> req
(update :body-params xf)
(update :query-params xf)
(update :body xf)
(update :params xf))]
(handler updated))))
(defn resp->camel
[handler]
(fn [req]
;; your swagger endpoint here
(if (= "/api/swagger.json" (:uri req))
(handler req)
(let [resp (handler req)]
(update resp
:body
(partial transform-keys ->camelCase))))))
;; swagger uses x-[value] for specific keys like x-nullable,
;; so we can't use use a general transformation without some breakage.
;; Additionally, we need to transform name and required property values.
(defn- swagger-walker
[[k v]]
(cond
(and (= :name k)
(keyword? v))
[k (->camelCase v)]
(and (= :required k)
(vector? v))
[k (mapv ->camelCase v)]
;; don't transform special keys
(and (keyword? k)
(= "x-" (-> k name (subs 0 2))))
[k v]
;; avoids some conversion errors
(not (or (string? k)
(keyword? k)))
[k v]
:else
[(->camelCase k) v]))
(defn swagger->camel
[handler]
(fn [req]
(let [resp (handler req)
updated (postwalk
(fn [x]
(if (map? x)
(into {} (map swagger-walker x))
x))
(:body resp))]
(assoc resp :body updated))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment