Skip to content

Commit

Permalink
add: wip start implement router with reiti
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusfrancisco committed May 12, 2024
1 parent ebd79fd commit 0b08afa
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 40 deletions.
14 changes: 12 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
selmer/selmer {:mvn/version "1.12.52"}
com.github.kbosompem/bb-excel {:mvn/version "0.0.9"}
clj-http/clj-http {:mvn/version "3.12.3"}
viesti/timbre-json-appender {:mvn/version "0.2.13"}}
viesti/timbre-json-appender {:mvn/version "0.2.13"}

metosin/reitit-pedestal {:mvn/version "0.7.0"}
metosin/reitit {:mvn/version "0.7.0"}}

:aliases {;; Run project
;; clj -M:run
:run {:main-opts ["-m" "babashka.cli.exec"]
Expand All @@ -42,7 +46,13 @@
:exec-fn cognitect.test-runner.api/test}

;; clj -M:nrepl
:nrepl {:extra-deps {cider/cider-nrepl {:mvn/version "0.30.0"}}
:nrepl {:extra-deps {cider/cider-nrepl {:mvn/version "0.30.0"}
io.github.cognitect-labs/test-runner {:git/tag "v0.5.1"
:git/sha "dfb30dd"}
clj-kondo/clj-kondo {:mvn/version "2023.10.20"}
cljfmt/cljfmt {:mvn/version "0.9.2"}}
:extra-paths ["test" "dev"]
:resource-paths ["test/com/moclojer/resources"]
:main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}

;; Lint the source
Expand Down
30 changes: 20 additions & 10 deletions src/com/moclojer/router.clj
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
(ns com.moclojer.router
(:require [clojure.data.json :as json]
[com.moclojer.log :as log]
[com.moclojer.specs.moclojer :as spec]
[com.moclojer.specs.openapi :as openapi]))
(:require
[clojure.data.json :as json]
[clojure.string :as string]
[com.moclojer.log :as log]
[com.moclojer.specs.moclojer :as spec]
[com.moclojer.specs.openapi :as openapi]))

(def home-endpoint
"initial/home endpoint URL: /"
Expand All @@ -16,10 +18,18 @@
(defn smart-router
"Identifies configuration type (moclojer or openapi spec)"
[{:keys [::config ::mocks]}]
(let [mode (if mocks :openapi :moclojer)]
(let [mode (if mocks :openapi :moclojer)
route-type (or (System/getenv "SWAGGER") false)
routes (->> (if mocks
(openapi/->moclojer config mocks)
config))]

(log/log :info :spec-mode :mode mode)
(->> (if mocks
(openapi/->moclojer config mocks)
config)
(cons home-endpoint)
(spec/->pedestal))))
(cond
(= (boolean route-type) true) (->> routes
(cons home-endpoint)
(spec/->reitit)
vec)
:else (->> routes
(cons home-endpoint)
(spec/->pedestal)))))
122 changes: 99 additions & 23 deletions src/com/moclojer/server.clj
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
(ns com.moclojer.server
(:require [clojure.data.json :as json]
[com.moclojer.adapters :as adapters]
[com.moclojer.config :as config]
[com.moclojer.io-utils :refer [open-file]]
[com.moclojer.log :as log]
[com.moclojer.watcher :refer [start-watch]]
[io.pedestal.http :as http]
[io.pedestal.http.body-params :as body-params]
[io.pedestal.http.jetty]
[io.pedestal.interceptor.error :refer [error-dispatch]])
(:import (org.eclipse.jetty.server.handler.gzip GzipHandler)
(org.eclipse.jetty.servlet ServletContextHandler)))
(:require
[clojure.data.json :as json]
[clojure.string :as string]
[com.moclojer.adapters :as adapters]
[com.moclojer.config :as config]
[com.moclojer.io-utils :refer [open-file]]
[com.moclojer.log :as log]
[com.moclojer.watcher :refer [start-watch]]
[io.pedestal.http :as http]
[io.pedestal.http.body-params :as body-params]
[io.pedestal.http.jetty]
[io.pedestal.interceptor.error :refer [error-dispatch]]
[muuntaja.core :as m]
[reitit.coercion.spec]
[reitit.dev.pretty :as pretty]
[reitit.http :as r-http]
[reitit.http.coercion :as coercion]
[reitit.http.interceptors.exception :as exception]
[reitit.http.interceptors.multipart :as multipart]
[reitit.http.interceptors.muuntaja :as muuntaja]
[reitit.http.interceptors.parameters :as parameters]
[reitit.pedestal :as pedestal]
[reitit.ring :as ring]
[reitit.swagger :as swagger]
[reitit.swagger-ui :as swagger-ui])
(:import
(org.eclipse.jetty.server.handler.gzip GzipHandler)
(org.eclipse.jetty.servlet ServletContextHandler)))

(defn context-configurator
"http container options, active gzip"
Expand Down Expand Up @@ -55,6 +71,42 @@
::http/host http-host
::http/port http-port})

(defn reitit-router [*router]
(-> (pedestal/routing-interceptor
(r-http/router
@*router
{;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
;;:validate spec/validate ;; enable spec validation for route data
;;:reitit.spec/wrap spell/closed ;; strict top-level validation
:exception pretty/exception
:data {:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:interceptors [;; swagger feature
swagger/swagger-feature
;; query-params & form-params
(parameters/parameters-interceptor)
;; content-negotiation
(muuntaja/format-negotiate-interceptor)
;; encoding response body
(muuntaja/format-response-interceptor)
;; exception handling
(exception/exception-interceptor)
;; decoding request body
(muuntaja/format-request-interceptor)
;; coercing response bodys
(coercion/coerce-response-interceptor)
;; coercing request parameters
(coercion/coerce-request-interceptor)
;; multipart
(multipart/multipart-interceptor)]}})
(ring/routes
(swagger-ui/create-swagger-ui-handler
{:path "/docs"
:config {:validatorUrl nil
:operationsSorter "alpha"}})
(ring/create-resource-handler)
(ring/create-default-handler)))))

(defn start-server!
"start moclojer server"
[*router & {:keys [start?
Expand All @@ -64,7 +116,8 @@
http-port (or (some-> (System/getenv "PORT")
Integer/parseInt)
8000)
http-start (if start? http/start ::http/service-fn)]
http-start (if start? http/start ::http/service-fn)
swagger? (or (System/getenv "SWAGGER") false)]
(log/log :info
:moclojer-start
"-> moclojer"
Expand All @@ -73,20 +126,43 @@
:port http-port
:url (str "http://" http-host ":" http-port)
:version config/version)
(->
*router
(build-config-map {:http-host http-host
:http-port http-port
:join? join?})
get-interceptors
http/create-server
http-start)))
(if swagger?
(let [router (reitit-router *router)]
(-> {:env config/moclojer-environment
::http/request-logger log/request
::http/routes []
::http/type :jetty
::http/join? join?
::http/container-options {:h2c? true
:context-configurator context-configurator}
;; allow serving the swagger-ui styles & scripts from self
::http/secure-headers {:content-security-policy-settings
{:default-src "'self'"
:style-src "'self' 'unsafe-inline'"
:script-src "'self' 'unsafe-inline'"}}
::http/host http-host
::http/port http-port}

(http/default-interceptors)
(pedestal/replace-last-interceptor router)

(http/dev-interceptors)
(http/create-server)
http-start))

(->
*router
(build-config-map {:http-host http-host
:http-port http-port
:join? join?})
get-interceptors
http/create-server
http-start))))

(defn create-watcher [*router & {:keys [config-path mocks-path]}]
(start-watch [{:file config-path
:event-types [:create :modify :delete]
:callback (fn [_event file]
(prn file config-path)
(when (and (= file config-path)
(not (nil? config-path)))
(log/log :info :moclojer-reload :router file :config config-path)
Expand All @@ -108,6 +184,6 @@
[{:keys [config-path mocks-path]}]
(let [*router (adapters/generate-routes (open-file config-path)
:mocks-path mocks-path)]

(clojure.pprint/pprint *router)
(create-watcher *router {:config-path config-path :mocks-path mocks-path})
(start-server! *router)))
57 changes: 52 additions & 5 deletions src/com/moclojer/specs/moclojer.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
(ns com.moclojer.specs.moclojer
(:require [clojure.string :as string]
[io.pedestal.http.route :as route]
[com.moclojer.external-body.core :as ext-body]
[com.moclojer.webhook :as webhook]
[selmer.parser :as selmer]))
(:require
[clojure.string :as string]
[com.moclojer.external-body.core :as ext-body]
[com.moclojer.webhook :as webhook]
[io.pedestal.http.route :as route]
[reitit.swagger :as swagger]
[selmer.parser :as selmer]
[com.moclojer.log :as log]
[clojure.data.json :as json]
[clojure.edn :as edn]))

(defn render-template
[template request]
Expand Down Expand Up @@ -55,6 +60,19 @@
[(name k) (str v)]))
(:headers response))}))

(defn generic-reitit-handler [response webhook-config]
(fn [{path :parameters}]
(log/log :info :request path)
(let [body (build-body response path)]
(log/log :info :body (json/read-str body :key-fn keyword))
{:body (json/read-str body :key-fn keyword)
:status (:status response)
:headers (into
{}
(map (fn [[k v]]
[(name k) (str v)]))
(:headers response))})))

(defn generate-route-name
[host path method]
(str method "-" (or host "localhost") "-" (string/replace (string/replace path "/" "") ":" "--")))
Expand All @@ -81,3 +99,32 @@
(generic-handler response webhook-config)
:route-name (keyword route-name)]})))
(mapcat identity)))

(defn make-parameters [url]
(let [parts (clojure.string/split url #"/")
param-specs (map (fn [part]
(when (clojure.string/starts-with? part ":")
(let [param-name (clojure.string/replace part ":" "")]
{:path {(keyword param-name) any?}}))) parts)]
(into {} (filter some? param-specs))))

(defn ->reitit
[spec]
(concat
[["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "moclojer-mock"
:description "my mock"}}
:handler (swagger/create-swagger-handler)}}]]
(for [[[host path method tag] endpoints]
(group-by (juxt :host :path :method)
(remove nil? (map :endpoint spec)))]
(let [method (generate-method method)
route-name (generate-route-name host path method)
response (:response (first endpoints))]
[path
{:swagger {:tags [(or tag route-name)]}
:parameters (make-parameters path)
:responses {(:status response) {:body {:hello string?}}}
(keyword method) {:summary (str "Generated from " route-name)
:handler (generic-reitit-handler response nil)}}]))))
30 changes: 30 additions & 0 deletions test/com/moclojer/router_reitit.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(ns com.moclojer.router-reitit
(:require
[clojure.test :refer [deftest is testing]]
[com.moclojer.specs.moclojer :refer [->reitit]]
[yaml.core :as yaml]))

(deftest spec->reitit
(testing "Converts a spec to a reitit router"
(let [spec (yaml/from-file "test/com/moclojer/resources/moclojer.yml")]
(is (= {} (->reitit spec))))))
;(#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"hello-v1\": \"hello world!\"\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"hello\": \"{{path-params.username}}!\"\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"path-params\": \"{{path-params.param1}}\",\n \"query-params\": \"{{query-params.param1}}\"\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"hello\": \"Hello, World!\"\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"hello-v1\": \"hello world!\"\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"hello-v1\": \"{{path-params.username}}!\",\n \"sufix\": true\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"username\": \"{{path-params.username}}\",\n \"age\": {{path-params.age}}\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"hello-v1\": \"{{path-params.username}}!\",\n \"sufix\": false\n}\n"])
;#ordered/map ([:status 200] [:headers #ordered/map ([:Content-Type "application/json"])] [:body "{\n \"project\": \"{{json-params.project}}\"\n}\n"]))
;
;(["/v1/hello/" {"get" {:summary "Generated from get-localhost-v1hello", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/hello/:username" {"get" {:summary "Generated from get-localhost-hello--username", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/with-params/:param1" {"get" {:summary "Generated from get-localhost-with-params--param1", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/hello-world" {"get" {:summary "Generated from get-localhost-hello-world", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/v1/hello" {"get" {:summary "Generated from get-localhost-v1hello", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/v1/hello/test/:username/with-sufix" {"get" {:summary "Generated from get-localhost-v1hellotest--usernamewith-sufix", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/multi-path-param/:username/more/:age" {"get" {:summary "Generated from get-localhost-multi-path-param--usernamemore--age", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/v1/hello/test/:username" {"get" {:summary "Generated from get-localhost-v1hellotest--username", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;["/first-post-route" {"post" {:summary "Generated from post-localhost-first-post-route", :handler #function[com.moclojer.specs.moclojer/generic-handler/fn--40648]}}]
;)

0 comments on commit 0b08afa

Please sign in to comment.