Add API documentation using OpenAPI and SwaggerUI
This commit is contained in:
@@ -32,3 +32,5 @@ The following links proved more than useful when working on this assignment:
|
||||
* <https://clojurecivitas.github.io/malli/elements_of_malli.html>
|
||||
* <https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md>
|
||||
* <https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.1086/doc/getting-started>
|
||||
* <https://cljdoc.org/d/metosin/reitit/0.10.0/doc/ring/swagger-support>
|
||||
* <https://www.baeldung.com/clojure-ring>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(defproject yohoho "0.1.0-SNAPSHOT"
|
||||
:description "Yo-Ho-Ho, a take home assignment for the brave"
|
||||
(defproject yohoho "0.42.0"
|
||||
:description "Yo-ho-ho, a take home assignment for the brave"
|
||||
:url "https://git.dromaludaire.info/yohoho"
|
||||
:license {:name "WTFPL – Do What the Fuck You Want to Public License"
|
||||
:url "https://www.wtfpl.net/about/"}
|
||||
@@ -8,6 +8,8 @@
|
||||
[ring/ring-jetty-adapter "1.15.3"]
|
||||
[metosin/reitit "0.10.0"]
|
||||
[metosin/reitit-malli "0.10.0"]
|
||||
[fi.metosin/reitit-openapi "0.10.0"]
|
||||
[metosin/reitit-swagger-ui "0.10.0"]
|
||||
[metosin/muuntaja "0.6.11"]
|
||||
[com.github.seancorfield/next.jdbc "1.3.1086"]
|
||||
[org.xerial/sqlite-jdbc "3.51.1.0"]]
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
[muuntaja.core :as m]
|
||||
[reitit.coercion.malli :as malli]
|
||||
[reitit.ring.coercion :as coercion]
|
||||
[reitit.openapi :as openapi]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[next.jdbc :as jdbc]
|
||||
[next.jdbc.sql :as sql]
|
||||
[next.jdbc.result-set :as rs])
|
||||
@@ -25,11 +27,38 @@
|
||||
|
||||
(def Email [:re email-regexp])
|
||||
|
||||
(def Item
|
||||
(def ItemInput
|
||||
[:map
|
||||
[:name :string]
|
||||
[:email Email]])
|
||||
|
||||
(def Item
|
||||
[:map
|
||||
[:id :int]
|
||||
[:name :string]
|
||||
[:email Email]])
|
||||
|
||||
(def ItemResponse Item)
|
||||
|
||||
(def ItemsResponse
|
||||
[:map
|
||||
[:items [:vector Item]]])
|
||||
|
||||
(def ErrorResponse
|
||||
[:map
|
||||
[:error :string]
|
||||
[:message {:optional true} :string]])
|
||||
|
||||
;; Those are generated by Malli when validation fails
|
||||
;; TODO: find a way to produce lighter messages on validation error
|
||||
;; (maybe a custom middleware?)
|
||||
(def Error400Response
|
||||
[:map
|
||||
[:value :map]
|
||||
[:type :string]
|
||||
[:coercion :string]
|
||||
[:in [:vector :string]]
|
||||
[:humanized :map]])
|
||||
|
||||
;; Database stuff -------------------------------------------------
|
||||
(def db (:db config))
|
||||
@@ -124,12 +153,45 @@
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
[;; Health check
|
||||
["/ahoy" {:get ahoy-handler}]
|
||||
["/ahoy" {:get {:summary "Health check"
|
||||
:openapi {:tags ["Health"]}
|
||||
:handler ahoy-handler
|
||||
:responses {200 {:body :string
|
||||
:description "Server is alive"}}}}]
|
||||
;; The real assignment: create and retrieve items
|
||||
["/item/:id" {:get get-item-handler}]
|
||||
["/items" {:get get-items-handler
|
||||
:post {:handler create-item-handler
|
||||
:parameters {:body Item}}}]]
|
||||
["/item/:id" {:get {:summary "Get an item by id"
|
||||
:openapi {:tags ["Items"]}
|
||||
:handler get-item-handler
|
||||
:parameters {:path [:map [:id :int]]}
|
||||
:responses {200 {:body ItemResponse
|
||||
:description "Item found"}
|
||||
400 {:body Error400Response
|
||||
:description "Invalid input"}
|
||||
404 {:body ErrorResponse
|
||||
:description "Item not found"}}}}]
|
||||
["/items" {:get {:summary "Get all items"
|
||||
:openapi {:tags ["Items"]}
|
||||
:handler get-items-handler
|
||||
:responses {200 {:body ItemsResponse ;; TODO!
|
||||
:description "List of all items"}}}
|
||||
:post {:summary "Create an item"
|
||||
:openapi {:tags ["Items"]}
|
||||
:handler create-item-handler
|
||||
:parameters {:body ItemInput}
|
||||
:responses {201 {:body Item
|
||||
:description "Item created successfully"}
|
||||
400 {:body Error400Response
|
||||
:description "Invalid input"}
|
||||
415 {:body ErrorResponse
|
||||
:description "Unsupported media type"}}}}]
|
||||
;; OpenAPI routes
|
||||
["" {:no-doc true
|
||||
:openapi {:info {:title "Yo-ho-ho API"
|
||||
:description "A take-home assignment for HolidayPirates"
|
||||
:version "0.42.0"}}}
|
||||
["/openapi.json" {:get (openapi/create-openapi-handler)}]
|
||||
["/doc/*" {:get (swagger-ui/create-swagger-ui-handler {:url "/openapi.json"})}]]]
|
||||
|
||||
;; Middlewares:
|
||||
;; - wrap-content-type-json: ensure POST routes are sent JSON payload
|
||||
;; - muuntaja middleware to automatically decode JSON
|
||||
@@ -141,10 +203,14 @@
|
||||
coercion/coerce-exceptions-middleware
|
||||
coercion/coerce-request-middleware
|
||||
coercion/coerce-response-middleware]}})
|
||||
;; Default route: anything not explicitely handled should give a 404
|
||||
(ring/create-default-handler
|
||||
{:not-found (constantly {:status 404
|
||||
:body {:error "Not Found"}})})))
|
||||
|
||||
(ring/routes
|
||||
;; Add trailing slash when missing, eg. for /doc
|
||||
(ring/redirect-trailing-slash-handler {:method :add})
|
||||
;; Default route: anything not explicitely handled should give a 404
|
||||
(ring/create-default-handler
|
||||
{:not-found (constantly {:status 404
|
||||
:body {:error "Not Found"}})}))))
|
||||
|
||||
|
||||
;; Entry point ----------------------------------------------------
|
||||
@@ -156,4 +222,5 @@
|
||||
(println "Initializing SQLite database...")
|
||||
(init-db!)
|
||||
(println "Ahoy! Yo-ho-ho API starting on port" port)
|
||||
(println "API documentation on /openapi.json and on /doc (interactive UI)")
|
||||
(http-server/run-jetty app {:port port})))
|
||||
|
||||
Reference in New Issue
Block a user