<commit_before>;; Copyright © 2015, JUXT LTD.

(ns yada.swagger
  (:require
   [bidi.bidi :refer (Matched resolve-handler unresolve-handler route-seq succeed unmatch-pair unroll-route)]
   [bidi.ring :refer (Ring request make-handler)]
   [camel-snake-kebab :as csk]
   [cheshire.core :as json]
   [clj-time.coerce :refer (to-date)]
   [clj-time.core :refer (now)]
   [clojure.pprint :refer (pprint)]
   [clojure.tools.logging :refer :all]
   [hiccup.page :refer (html5)]
   [json-html.core :as jh]
   [ring.swagger.swagger2 :as rs]
   [ring.util.response :refer (redirect)]
   [schema.core :as s]
   [yada.methods :refer (Get get*)]
   [yada.mime :as mime]
   [yada.resource :refer (Resource ResourceRepresentations ResourceConstructor platform-charsets make-resource) :as res]
   [yada.yada :as yada])
  (:import (clojure.lang PersistentVector Keyword)))

(defprotocol SwaggerPath
  (encode [_] "Format route as a swagger path"))

(extend-protocol SwaggerPath
  String (encode [s] s)
  PersistentVector (encode [v] (apply str (map encode v)))
  Keyword (encode [k] (str "{" (name k) "}")))

(defn- to-path [route]
  (infof "to-path arg is %s" route)
  ;; TODO: Fix this really poorly derived data model with the 2 :handler keys
  (let [swagger (-> route :handler :handler :options :swagger)
        path (-> route :path)
        options (-> route :handler :options)
        resource (-> route :handler :handler :resource)
        methods (or (:methods options) (res/methods resource))]
    (infof "path is %s" (apply str (map encode path)))
    (infof "resource is %s" resource)
    (infof "options are %s" options)
    (infof "methods is %s" methods)
    [(apply str (map encode path))
     (merge-with merge swagger
                 (into {}
                       (for [method methods]
                         ;; TODO: Add parameters
                         ;; TODO: Add produces
                         ;; TODO: Add responses
                         {method {:description "another method"
                                  :produces ["text/plain"]}})))]))

(defrecord SwaggerSpec [spec created-at]
  Resource
  (methods [_] #{:get :head})
  (exists? [_ ctx] true)
  (last-modified [_ ctx] created-at)

  ResourceRepresentations
  (representations [_]
    [{:content-type #{"application/json" "text/html;q=0.9" "application/edn;q=0.8"}
      :charset platform-charsets}])

  Get
  (get* [_ ctx] (rs/swagger-json spec)))

(defrecord Swagger [spec route spec-handler]
  Matched
  (resolve-handler [this m]
    (cond (= (:remainder m) (str (or (:base-path spec) "") "/swagger.json"))
          ;; Return this, which satisfies Ring.
          ;; Truncate :remainder to ensure succeed actually succeeds.
          (succeed this (assoc m :remainder ""))

          ;; Redirect to swagger.json
          (= (:remainder m) (str (or (:base-path spec) "") "/"))
          (succeed (reify Ring (request [_ req _] (redirect (str (:uri req) "swagger.json"))))
                   (assoc m :remainder ""))

          ;; Otherwise
          :otherwise (resolve-handler [[(or (:base-path spec) "") [route]]]
                                      (merge m {::spec spec}))))

  (unresolve-handler [this m]
    (if (= this (:handler m))
      (or (:base-path spec) "")
      (unmatch-pair route m)))

  Ring
  (request [_ req match-context]
    ;; This yada resource has match-context in its lexical scope,
    ;; containing any yada/partial (or bidi/partial) entries.
    (spec-handler req))

  ;; So that we can use the Swagger record as a Ring handler
  clojure.lang.IFn
  (invoke [this req]
    (let [handler (make-handler ["" this])]
      (handler req))))

(defn swaggered [spec route]
  (infof "swaggered, route is %s, spec is %s" route spec)
  (let [spec (merge spec {:paths (into {} (map to-path (route-seq (unroll-route route))))})]
    (->Swagger spec route
               (yada/resource (->SwaggerSpec spec (to-date (now)))))))

#_(pprint
   (swaggered {:info {:title "Hello World!"
                      :version "0.0.1"
                      :description "Demonstrating yada + swagger"}
               :basePath "/hello-api"
               }
              ["/hello" (yada "Hello World!")]))<commit_msg>Slight name tweak<commit_after>;; Copyright © 2015, JUXT LTD.

(ns yada.swagger
  (:require
   [bidi.bidi :refer (Matched resolve-handler unresolve-handler route-seq succeed unmatch-pair unroll-route)]
   [bidi.ring :refer (Ring request make-handler)]
   [camel-snake-kebab :as csk]
   [cheshire.core :as json]
   [clj-time.coerce :refer (to-date)]
   [clj-time.core :refer (now)]
   [clojure.pprint :refer (pprint)]
   [clojure.tools.logging :refer :all]
   [hiccup.page :refer (html5)]
   [json-html.core :as jh]
   [ring.swagger.swagger2 :as rs]
   [ring.util.response :refer (redirect)]
   [schema.core :as s]
   [yada.methods :refer (Get get*)]
   [yada.mime :as mime]
   [yada.resource :refer (Resource ResourceRepresentations ResourceConstructor platform-charsets make-resource) :as res]
   [yada.yada :as yada])
  (:import (clojure.lang PersistentVector Keyword)))

(defprotocol SwaggerPath
  (encode [_] "Format route as a swagger path"))

(extend-protocol SwaggerPath
  String (encode [s] s)
  PersistentVector (encode [v] (apply str (map encode v)))
  Keyword (encode [k] (str "{" (name k) "}")))

(defn- to-path [route]
  (infof "to-path arg is %s" route)
  ;; TODO: Fix this really poorly derived data model with the 2 :handler keys
  (let [swagger (-> route :handler :handler :options :swagger)
        path (-> route :path)
        options (-> route :handler :options)
        resource (-> route :handler :handler :resource)
        methods (or (:methods options) (res/methods resource))]
    (infof "path is %s" (apply str (map encode path)))
    (infof "resource is %s" resource)
    (infof "options are %s" options)
    (infof "methods is %s" methods)
    [(apply str (map encode path))
     (merge-with merge swagger
                 (into {}
                       (for [method methods]
                         ;; TODO: Add parameters
                         ;; TODO: Add produces
                         ;; TODO: Add responses
                         {method {:description "another method"
                                  :produces ["text/plain"]}})))]))

(defrecord SwaggerSpec [spec created-at]
  Resource
  (methods [_] #{:get :head})
  (exists? [_ ctx] true)
  (last-modified [_ ctx] created-at)

  ResourceRepresentations
  (representations [_]
    [{:content-type #{"application/json" "text/html;q=0.9" "application/edn;q=0.8"}
      :charset platform-charsets}])

  Get
  (get* [_ ctx] (rs/swagger-json spec)))

(defrecord Swaggered [spec route spec-handler]
  Matched
  (resolve-handler [this m]
    (cond (= (:remainder m) (str (or (:base-path spec) "") "/swagger.json"))
          ;; Return this, which satisfies Ring.
          ;; Truncate :remainder to ensure succeed actually succeeds.
          (succeed this (assoc m :remainder ""))

          ;; Redirect to swagger.json
          (= (:remainder m) (str (or (:base-path spec) "") "/"))
          (succeed (reify Ring (request [_ req _] (redirect (str (:uri req) "swagger.json"))))
                   (assoc m :remainder ""))

          ;; Otherwise
          :otherwise (resolve-handler [[(or (:base-path spec) "") [route]]]
                                      (merge m {::spec spec}))))

  (unresolve-handler [this m]
    (if (= this (:handler m))
      (or (:base-path spec) "")
      (unmatch-pair route m)))

  Ring
  (request [_ req match-context]
    ;; This yada resource has match-context in its lexical scope,
    ;; containing any yada/partial (or bidi/partial) entries.
    (spec-handler req))

  ;; So that we can use the Swaggered record as a Ring handler
  clojure.lang.IFn
  (invoke [this req]
    (let [handler (make-handler ["" this])]
      (handler req))))

(defn swaggered [spec route]
  (infof "swaggered, route is %s, spec is %s" route spec)
  (let [spec (merge spec {:paths (into {} (map to-path (route-seq (unroll-route route))))})]
    (->Swaggered spec route
                 (yada/resource (->SwaggerSpec spec (to-date (now)))))))

#_(pprint
   (swaggered {:info {:title "Hello World!"
                      :version "0.0.1"
                      :description "Demonstrating yada + swagger"}
               :basePath "/hello-api"
               }
              ["/hello" (yada "Hello World!")]))
{'fraction_non_alphanumeric': 0.14557112324863453, 'fraction_numerical': 0.0033246259795772976, 'mean_word_length': 2.5227938101212883, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '879212', 'n_tokens_mistral': 2786, 'n_tokens_neox': 2654, 'n_words': 815}