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://clojurecivitas.github.io/malli/elements_of_malli.html>
|
||||||
* <https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md>
|
* <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/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"
|
(defproject yohoho "0.42.0"
|
||||||
:description "Yo-Ho-Ho, a take home assignment for the brave"
|
:description "Yo-ho-ho, a take home assignment for the brave"
|
||||||
:url "https://git.dromaludaire.info/yohoho"
|
:url "https://git.dromaludaire.info/yohoho"
|
||||||
:license {:name "WTFPL – Do What the Fuck You Want to Public License"
|
:license {:name "WTFPL – Do What the Fuck You Want to Public License"
|
||||||
:url "https://www.wtfpl.net/about/"}
|
:url "https://www.wtfpl.net/about/"}
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
[ring/ring-jetty-adapter "1.15.3"]
|
[ring/ring-jetty-adapter "1.15.3"]
|
||||||
[metosin/reitit "0.10.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-malli "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"]
|
[metosin/muuntaja "0.6.11"]
|
||||||
[com.github.seancorfield/next.jdbc "1.3.1086"]
|
[com.github.seancorfield/next.jdbc "1.3.1086"]
|
||||||
[org.xerial/sqlite-jdbc "3.51.1.0"]]
|
[org.xerial/sqlite-jdbc "3.51.1.0"]]
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
[reitit.coercion.malli :as malli]
|
[reitit.coercion.malli :as malli]
|
||||||
[reitit.ring.coercion :as coercion]
|
[reitit.ring.coercion :as coercion]
|
||||||
|
[reitit.openapi :as openapi]
|
||||||
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
[next.jdbc :as jdbc]
|
[next.jdbc :as jdbc]
|
||||||
[next.jdbc.sql :as sql]
|
[next.jdbc.sql :as sql]
|
||||||
[next.jdbc.result-set :as rs])
|
[next.jdbc.result-set :as rs])
|
||||||
@@ -25,11 +27,38 @@
|
|||||||
|
|
||||||
(def Email [:re email-regexp])
|
(def Email [:re email-regexp])
|
||||||
|
|
||||||
(def Item
|
(def ItemInput
|
||||||
[:map
|
[:map
|
||||||
[:name :string]
|
[:name :string]
|
||||||
[:email Email]])
|
[: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 -------------------------------------------------
|
;; Database stuff -------------------------------------------------
|
||||||
(def db (:db config))
|
(def db (:db config))
|
||||||
@@ -124,12 +153,45 @@
|
|||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
[;; Health check
|
[;; 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
|
;; The real assignment: create and retrieve items
|
||||||
["/item/:id" {:get get-item-handler}]
|
["/item/:id" {:get {:summary "Get an item by id"
|
||||||
["/items" {:get get-items-handler
|
:openapi {:tags ["Items"]}
|
||||||
:post {:handler create-item-handler
|
:handler get-item-handler
|
||||||
:parameters {:body Item}}}]]
|
: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:
|
;; Middlewares:
|
||||||
;; - wrap-content-type-json: ensure POST routes are sent JSON payload
|
;; - wrap-content-type-json: ensure POST routes are sent JSON payload
|
||||||
;; - muuntaja middleware to automatically decode JSON
|
;; - muuntaja middleware to automatically decode JSON
|
||||||
@@ -141,10 +203,14 @@
|
|||||||
coercion/coerce-exceptions-middleware
|
coercion/coerce-exceptions-middleware
|
||||||
coercion/coerce-request-middleware
|
coercion/coerce-request-middleware
|
||||||
coercion/coerce-response-middleware]}})
|
coercion/coerce-response-middleware]}})
|
||||||
|
|
||||||
|
(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
|
;; Default route: anything not explicitely handled should give a 404
|
||||||
(ring/create-default-handler
|
(ring/create-default-handler
|
||||||
{:not-found (constantly {:status 404
|
{:not-found (constantly {:status 404
|
||||||
:body {:error "Not Found"}})})))
|
:body {:error "Not Found"}})}))))
|
||||||
|
|
||||||
|
|
||||||
;; Entry point ----------------------------------------------------
|
;; Entry point ----------------------------------------------------
|
||||||
@@ -156,4 +222,5 @@
|
|||||||
(println "Initializing SQLite database...")
|
(println "Initializing SQLite database...")
|
||||||
(init-db!)
|
(init-db!)
|
||||||
(println "Ahoy! Yo-ho-ho API starting on port" port)
|
(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})))
|
(http-server/run-jetty app {:port port})))
|
||||||
|
|||||||
Reference in New Issue
Block a user