Liking cljdoc? Tell your friends :D

navi Tests

A tiny library converting OpenAPI route definitions to Reitit routes.

Suitable for spec-first servers.

Features

  • Read OpenAPI 3 definitions as JSON or YAML
  • Remote and relative refs
  • Request and response coercions powered by Malli
  • requestBody coercion
  • Strings with uuid and pattern types
  • A large subset of OpenAPI types are currently supported, please raise an issue if something is unsupported

Currently unsupported:

  • Other coercion libs

Any contributions are much much welcome and appreciated!

Installation

Leiningen/Boot

[org.clojars.lispyclouds/navi "0.0.8"]

Clojure CLI/deps.edn

{org.clojars.lispyclouds/navi {:mvn/version "0.0.8"}}

Gradle

compile 'org.clojars.lispyclouds:navi:0.0.8'

Maven

<dependency>
  <groupId>org.clojars.lispyclouds</groupId>
  <artifactId>navi</artifactId>
  <version>0.0.8</version>
</dependency>

Usage

Given a api.yaml:

openapi: "3.0.0"

info:
  title: My calculator
  version: "0.1.0"
  description: My awesome calc!

paths:
  "/add/{n1}/{n2}":
    get:
      operationId: AddGet
      summary: Adds two numbers

      parameters:
        - name: n1
          required: true
          in: path
          description: The first number
          schema:
            type: integer
        - name: n2
          required: true
          in: path
          description: The second number
          schema:
            type: integer
    post:
      operationId: AddPost
      summary: Adds two numbers via POST

      requestBody:
        description: The numebers map
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NumbersMap"
  "/health":
    get:
      operationId: HealthCheck
      summary: Returns Ok if all is well

components:
  schemas:
    NumbersMap:
      type: object
      required:
        - n1
        - n2
      properties:
        n1:
          type: integer
          description: The first number
        n2:
          type: integer
          description: The second number

A clojure map of OperationId to handler fns:

(def handlers
  {"AddGet" (fn [{{{:keys [n1 n2]} :path} :parameters}]
              {:status 200
               :body (str (+ n1 n2))})
   "AddPost" (fn [{{{:keys [n1 n2]} :body} :parameters}]
               {:status 200
                :body (str (+ n1 n2))})
   "HealthCheck" (fn [_]
                   {:status 200
                    :body "Ok"})})

Generate the routes:

(require '[navi.core :as navi])

(navi/routes-from (slurp "api.yaml") handlers)
=>
[["/add/{n1}/{n2}"
  {:get
   {:handler #function[navi.core/fn--8260],
    :parameters
    {:path
     [:map
      [:n1 #function[clojure.core/int?]]
      [:n2 #function[clojure.core/int?]]]}},
   :post
   {:handler #function[navi.core/fn--8266],
    :parameters
    {:body
     [:map
      {:closed false}
      [:n1 #function[clojure.core/int?]]
      [:n2 #function[clojure.core/int?]]]}}}]
 ["/health" {:get {:handler #function[navi.core/fn--8271]}}]]

Bootstrapping a Jetty server:

(ns server.main
  (:require
   [muuntaja.core :as m]
   [navi.core :as navi]
   [reitit.coercion.malli :as malli]
   [reitit.http :as http]
   [reitit.http.coercion :as coercion]
   [reitit.http.interceptors.exception :as exception]
   [reitit.http.interceptors.muuntaja :as muuntaja]
   [reitit.http.interceptors.parameters :as parameters]
   [reitit.interceptor.sieppari :as sieppari]
   [reitit.ring :as ring]
   [ring.adapter.jetty :as jetty])
  (:gen-class))

(def server
  (http/ring-handler
   (http/router (-> "api.yaml"
                    slurp
                    (navi/routes-from handlers)) ; handlers is the map described before
                {:data {:coercion malli/coercion
                        :muuntaja m/instance
                        :interceptors [(parameters/parameters-interceptor)
                                       (muuntaja/format-negotiate-interceptor)
                                       (muuntaja/format-response-interceptor)
                                       (exception/exception-interceptor)
                                       (muuntaja/format-request-interceptor)
                                       (coercion/coerce-exceptions-interceptor)
                                       (coercion/coerce-response-interceptor)
                                       (coercion/coerce-request-interceptor)]}})
   (ring/routes
    (ring/create-default-handler
     {:not-found (constantly {:status 404
                              :headers {"Content-Type" "application/json"}
                              :body "{\"message\": \"Took a wrong turn?\"}"})}))
   {:executor sieppari/executor}))

(defn -main
  [& _]
  (jetty/run-jetty (var server)
                   {:host "0.0.0.0"
                    :port 7777
                    :join? false
                    :async? true}))

deps.edn used for this example:

{:deps {org.clojars.lispyclouds/navi {:mvn/version "0.0.8"}
        metosin/reitit-core {:mvn/version "0.6.0"}
        metosin/reitit-http {:mvn/version "0.6.0"}
        metosin/reitit-interceptors {:mvn/version "0.6.0"}
        metosin/reitit-malli {:mvn/version "0.6.0"}
        metosin/reitit-ring {:mvn/version "0.6.0"}
        metosin/reitit-sieppari {:mvn/version "0.6.0"}
        metosin/muuntaja {:mvn/version "0.6.8"}
        ring/ring-jetty-adapter {:mvn/version "1.9.6"}}}

Build Requirements

Running tests locally

  • clojure -M:test to run all tests

License

Copyright © 2020- Rahul De

Distributed under the MIT License. See LICENSE.

Can you improve this documentation? These fine people already did:
Rahul De & Dorab Patel
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close