mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
154 lines
4.8 KiB
Clojure
154 lines
4.8 KiB
Clojure
(ns reitit.spec
|
|
(:require [clojure.spec.alpha :as s]
|
|
[clojure.spec.gen.alpha :as gen]
|
|
[reitit.core :as r]
|
|
[reitit.exception :as exception]))
|
|
|
|
;;
|
|
;; routes
|
|
;;
|
|
|
|
(s/def ::path (s/with-gen string? #(gen/fmap (fn [s] (str "/" s)) (s/gen string?))))
|
|
|
|
(s/def ::arg (s/and some? (complement sequential?)))
|
|
(s/def ::data (s/map-of keyword? any?))
|
|
(s/def ::result any?)
|
|
|
|
(s/def ::raw-route
|
|
(s/nilable
|
|
(s/cat :path ::path
|
|
:arg (s/? ::arg)
|
|
:childs (s/* (s/nilable ::raw-routes)))))
|
|
|
|
(s/def ::raw-routes
|
|
(s/or :route ::raw-route
|
|
:routes (s/coll-of ::raw-routes :into [])))
|
|
|
|
(s/def ::route
|
|
(s/cat :path ::path
|
|
:data ::data
|
|
:result (s/? any?)))
|
|
|
|
(s/def ::routes
|
|
(s/or :route ::route
|
|
:routes (s/coll-of ::route :into [])))
|
|
|
|
;;
|
|
;; Default data
|
|
;;
|
|
|
|
(s/def ::name keyword?)
|
|
(s/def ::handler (s/or :fn fn? :var var?))
|
|
(s/def ::no-doc boolean?)
|
|
(s/def ::conflicting boolean?)
|
|
(s/def ::default-data
|
|
(s/keys :opt-un [::name ::handler ::no-doc ::conflicting]))
|
|
|
|
;;
|
|
;; router
|
|
;;
|
|
|
|
(s/def ::router r/router?)
|
|
(s/def :reitit.router/path ::path)
|
|
(s/def :reitit.router/routes ::routes)
|
|
(s/def :reitit.router/data ::data)
|
|
(s/def :reitit.router/expand fn?)
|
|
(s/def :reitit.router/coerce fn?)
|
|
(s/def :reitit.router/compile fn?)
|
|
(s/def :reitit.router/conflicts (s/nilable fn?))
|
|
(s/def :reitit.router/router fn?)
|
|
|
|
(s/def ::opts
|
|
(s/nilable
|
|
(s/keys :opt-un [:reitit.router/path
|
|
:reitit.router/routes
|
|
:reitit.router/data
|
|
:reitit.router/expand
|
|
:reitit.router/coerce
|
|
:reitit.router/compile
|
|
:reitit.router/conflicts
|
|
:reitit.router/router])))
|
|
|
|
(s/fdef r/router
|
|
:args (s/or :1arity (s/cat :data (s/spec ::raw-routes))
|
|
:2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts))
|
|
:ret ::router)
|
|
|
|
;;
|
|
;; coercion
|
|
;;
|
|
|
|
(s/def :reitit.core.coercion/coercion any?)
|
|
|
|
(s/def :reitit.core.coercion/model any?)
|
|
|
|
(s/def :reitit.core.coercion/schema any?)
|
|
(s/def :reitit.core.coercion/map-model (s/keys :opt-un [:reitit.core.coercion/schema]))
|
|
|
|
(s/def :reitit.core.coercion/content
|
|
(s/map-of (s/or :string string?, :default #{:default}) :reitit.core.coercion/map-model))
|
|
|
|
(s/def :reitit.core.coercion/query :reitit.core.coercion/model)
|
|
(s/def :reitit.core.coercion/body :reitit.core.coercion/model)
|
|
(s/def :reitit.core.coercion/request
|
|
(s/keys :opt-un [:reitit.core.coercion/content
|
|
:reitit.core.coercion/body]))
|
|
(s/def :reitit.core.coercion/form :reitit.core.coercion/model)
|
|
(s/def :reitit.core.coercion/header :reitit.core.coercion/model)
|
|
(s/def :reitit.core.coercion/path :reitit.core.coercion/model)
|
|
(s/def :reitit.core.coercion/parameters
|
|
(s/keys :opt-un [:reitit.core.coercion/query
|
|
:reitit.core.coercion/body
|
|
:reitit.core.coercion/request
|
|
:reitit.core.coercion/form
|
|
:reitit.core.coercion/header
|
|
:reitit.core.coercion/path]))
|
|
|
|
(s/def ::parameters
|
|
(s/keys :opt-un [:reitit.core.coercion/coercion
|
|
:reitit.core.coercion/parameters]))
|
|
|
|
(s/def :reitit.core.coercion/status
|
|
(s/or :number number? :default #{:default}))
|
|
(s/def :reitit.core.coercion/body any?)
|
|
(s/def :reitit.core.coercion/description string?)
|
|
(s/def :reitit.core.coercion/response
|
|
(s/keys :opt-un [:reitit.core.coercion/content
|
|
:reitit.core.coercion/body
|
|
:reitit.core.coercion/description]))
|
|
(s/def :reitit.core.coercion/responses
|
|
(s/map-of :reitit.core.coercion/status :reitit.core.coercion/response))
|
|
|
|
(s/def ::responses
|
|
(s/keys :opt-un [:reitit.core.coercion/coercion
|
|
:reitit.core.coercion/responses]))
|
|
|
|
;;
|
|
;; Route data validator
|
|
;;
|
|
|
|
(defrecord Problem [path scope data spec problems])
|
|
|
|
(defn validate-route-data [routes wrap spec]
|
|
(let [spec (wrap spec)
|
|
spec-explain (fn [[p d _]]
|
|
(when-let [problems (and spec (s/explain-data spec d))]
|
|
(->Problem p nil d spec problems)))
|
|
errors (into [] (keep spec-explain) routes)]
|
|
(when (pos? (count errors)) errors)))
|
|
|
|
(defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}]
|
|
(when-let [problems (validate-route-data routes wrap spec)]
|
|
(exception/fail!
|
|
::invalid-route-data
|
|
{:problems problems})))
|
|
|
|
(defmethod exception/format-exception :reitit.spec/invalid-route-data [_ _ {:keys [problems]}]
|
|
(apply str "Invalid route data:\n\n"
|
|
(mapv
|
|
(fn [{:keys [path scope data spec]}]
|
|
(str "-- On route -----------------------\n\n"
|
|
(pr-str path) (if scope (str " " (pr-str scope))) "\n\n"
|
|
(pr-str data) "\n\n"
|
|
(s/explain-str spec data) "\n"))
|
|
problems)))
|