├── version ├── resources ├── public │ ├── logo │ │ └── swagger.png │ ├── swagger-ui │ │ ├── fonts │ │ │ ├── DroidSans.ttf │ │ │ └── DroidSans-Bold.ttf │ │ ├── images │ │ │ ├── collapse.gif │ │ │ ├── expand.gif │ │ │ ├── favicon.ico │ │ │ ├── throbber.gif │ │ │ ├── logo_small.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── pet_store_api.png │ │ │ ├── wordnik_api.png │ │ │ └── explorer_icons.png │ │ ├── lib │ │ │ ├── jquery.slideto.min.js │ │ │ ├── jquery.wiggle.min.js │ │ │ ├── highlight.9.1.0.pack_extended.js │ │ │ ├── jquery.ba-bbq.min.js │ │ │ ├── highlight.9.1.0.pack.js │ │ │ └── swagger-oauth.js │ │ ├── css │ │ │ ├── typography.css │ │ │ ├── reset.css │ │ │ └── style.css │ │ ├── o2c.html │ │ └── lang │ │ │ ├── translator.js │ │ │ ├── zh-cn.js │ │ │ ├── ja.js │ │ │ ├── tr.js │ │ │ ├── pl.js │ │ │ ├── pt.js │ │ │ ├── en.js │ │ │ ├── ru.js │ │ │ ├── geo.js │ │ │ ├── it.js │ │ │ ├── es.js │ │ │ └── fr.js │ ├── bootstrap-3.3.6 │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── css │ │ │ └── bootstrap-theme.min.css.map │ ├── css │ │ └── swagger-search.css │ └── js │ │ └── list.min.js ├── swagger-ui.html └── swagger-search.html ├── dev ├── user.clj └── dev.clj ├── .gitignore ├── test ├── test_data │ ├── parser_v2_minimum_fields.json │ ├── parser_v1_working.json │ ├── parser_v2_no_operationId.json │ ├── parser_v2_working.json │ └── v2_big_example.json └── com │ └── ig │ └── swagger │ └── search │ ├── indexing_test.clj │ └── parser_test.clj ├── examples ├── api-example-1 │ ├── README.md │ ├── Dockerfile │ ├── src │ │ └── compojure │ │ │ └── api │ │ │ └── examples │ │ │ ├── main.clj │ │ │ ├── domain.clj │ │ │ └── handler.clj │ └── project.clj ├── consul │ ├── swagger-search │ │ ├── Dockerfile │ │ └── consul.swagger.config.edn │ └── docker-compose.yml ├── etcd │ ├── swagger-search │ │ ├── Dockerfile │ │ └── etcd.swagger.config.edn │ └── docker-compose.yml └── swagger.config.edn ├── Dockerfile ├── ci ├── build.sh └── deploy.sh ├── standalone-resources └── logback.xml ├── src └── com │ └── ig │ └── swagger │ └── search │ ├── discovery │ └── providers │ │ ├── file.clj │ │ ├── etcd.clj │ │ └── consul.clj │ ├── util │ └── ring_middleware.clj │ ├── proxy.clj │ ├── discovery.clj │ ├── core.clj │ ├── routes.clj │ ├── http_client │ └── core.clj │ ├── index.clj │ ├── parser.clj │ └── collector.clj ├── .travis.yml ├── standalone └── com │ └── ig │ └── swagger │ └── search │ └── standalone.clj ├── project.clj ├── README.md └── LICENSE /version: -------------------------------------------------------------------------------- 1 | 0.5.2 2 | -------------------------------------------------------------------------------- /resources/public/logo/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/logo/swagger.png -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user) 2 | 3 | (defn dev 4 | "Load and switch to the 'dev' namespace." 5 | [] 6 | (require 'dev) 7 | (in-ns 'dev) 8 | :loaded) -------------------------------------------------------------------------------- /resources/public/swagger-ui/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/collapse.gif -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/expand.gif -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/favicon.ico -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/throbber.gif -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/logo_small.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | war/logs 5 | logs 6 | .lein-failures 7 | .lein-repl-history 8 | pom.xml* 9 | .nrepl-port 10 | /swagger.config.edn -------------------------------------------------------------------------------- /resources/public/swagger-ui/fonts/DroidSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/fonts/DroidSans-Bold.ttf -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/favicon-16x16.png -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/favicon-32x32.png -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/pet_store_api.png -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/wordnik_api.png -------------------------------------------------------------------------------- /resources/public/swagger-ui/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/swagger-ui/images/explorer_icons.png -------------------------------------------------------------------------------- /test/test_data/parser_v2_minimum_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "paths": { 4 | "/ping/abc": { 5 | "GET": { 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /examples/api-example-1/README.md: -------------------------------------------------------------------------------- 1 | # Compojure-api-examples 2 | 3 | An example of a Swagger V2 service 4 | 5 | Copied from https://github.com/metosin/compojure-api-examples -------------------------------------------------------------------------------- /examples/consul/swagger-search/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM clojure:lein-2.7.1-alpine 2 | 3 | WORKDIR /app 4 | 5 | CMD ["lein", "with-profile", "+not-lib", "do", "clean,", "repl", ":headless" ] 6 | -------------------------------------------------------------------------------- /examples/etcd/swagger-search/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM clojure:lein-2.7.1-alpine 2 | 3 | WORKDIR /app 4 | 5 | CMD ["lein", "with-profile", "+not-lib", "do", "clean,", "repl", ":headless" ] 6 | -------------------------------------------------------------------------------- /resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IG-Group/swagger-search/HEAD/resources/public/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-alpine 2 | 3 | ENV SWAGGER_HOME=/config 4 | WORKDIR /app 5 | COPY target/swagger-search-*standalone.jar /app/swagger-search.jar 6 | 7 | CMD ["java", "-jar", "swagger-search.jar"] -------------------------------------------------------------------------------- /ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | BRANCH_NAME="${TRAVIS_BRANCH:=unknown}" 6 | 7 | if [ -z "$TRAVIS_COMMIT" ]; then 8 | export TRAVIS_COMMIT=local 9 | fi 10 | 11 | lein test -------------------------------------------------------------------------------- /examples/api-example-1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM clojure:lein-2.7.1-alpine 2 | 3 | COPY project.clj /download-deps/project.clj 4 | WORKDIR /download-deps 5 | RUN lein deps 6 | ADD . /app 7 | 8 | WORKDIR /app 9 | CMD ["lein", "run"] 10 | -------------------------------------------------------------------------------- /examples/api-example-1/src/compojure/api/examples/main.clj: -------------------------------------------------------------------------------- 1 | (ns compojure.api.examples.main 2 | (:use [ring.util.response]) 3 | (:require [ring.adapter.jetty :as jetty] 4 | [compojure.api.examples.handler :as handler])) 5 | 6 | (defn start [& args] 7 | (jetty/run-jetty (var handler/app) {:port 3000 8 | :join? true})) -------------------------------------------------------------------------------- /resources/public/swagger-ui/lib/jquery.slideto.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery); 2 | -------------------------------------------------------------------------------- /examples/api-example-1/project.clj: -------------------------------------------------------------------------------- 1 | (defproject metosin/compojure-api-examples "1.0.1" 2 | :description "Compojure-api-examples" 3 | :dependencies [[org.clojure/clojure "1.8.0"] 4 | [metosin/compojure-api "1.1.11"] 5 | [javax.servlet/javax.servlet-api "3.1.0"] 6 | [ring/ring-jetty-adapter "1.6.1"]] 7 | :main compojure.api.examples.main/start) 8 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/css/typography.css: -------------------------------------------------------------------------------- 1 | /* Google Font's Droid Sans */ 2 | @font-face { 3 | font-family: 'Droid Sans'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf') format('truetype'); 7 | } 8 | /* Google Font's Droid Sans Bold */ 9 | @font-face { 10 | font-family: 'Droid Sans'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf') format('truetype'); 14 | } 15 | -------------------------------------------------------------------------------- /standalone-resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/discovery/providers/file.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.discovery.providers.file 2 | (:require [clojure.java.io :as io])) 3 | 4 | (defn from-uri-or-file 5 | [{:keys [uri-or-file]}] 6 | (fn [] 7 | (clojure.string/split-lines 8 | (slurp uri-or-file)))) 9 | 10 | (defn from-classpath 11 | [{:keys [classpath-file]}] 12 | (fn [] 13 | (clojure.string/split-lines 14 | (slurp (io/file (io/resource classpath-file)))))) 15 | 16 | (defn server-list 17 | [{:keys [server-list]}] 18 | (fn [] 19 | (map name server-list))) -------------------------------------------------------------------------------- /src/com/ig/swagger/search/discovery/providers/etcd.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.discovery.providers.etcd 2 | (:require [etcd-clojure.core :as etcd-clj] 3 | [clojure.tools.logging :as log])) 4 | 5 | (defn create-etcd [{:keys [etcd]}] 6 | (etcd-clj/connect! (:host etcd) (:port etcd)) 7 | (fn [] 8 | (log/info "etcd provider is using" etcd) 9 | (->> 10 | (etcd-clj/list (:prefix etcd)) 11 | (map :key) 12 | (map etcd-clj/list) 13 | (map first) 14 | (map :value) 15 | (map (partial str "http://")) 16 | set 17 | seq))) -------------------------------------------------------------------------------- /test/test_data/parser_v1_working.json: -------------------------------------------------------------------------------- 1 | { 2 | "swaggerVersion": "1.2", 3 | "controller-url" : "http://ip1/a-tomcat-service", 4 | "swagger-path": "/abc/default", 5 | "basePath": "/a-tomcat-service", 6 | "resourcePath": "/somebasepath", 7 | "apis": [ 8 | { 9 | "path": "/ping/abc", 10 | "description": "operationPing", 11 | "operations": [ 12 | { 13 | "method": "GET", 14 | "summary": "ping pong" 15 | } 16 | ] 17 | }, 18 | { 19 | "path": "/search", 20 | "description": "operationSearch", 21 | "operations": [ 22 | { 23 | "method": "GET", 24 | "summary": "Search for API" 25 | } 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /dev/dev.clj: -------------------------------------------------------------------------------- 1 | (ns dev 2 | (:require 3 | [clojure.tools.namespace.repl :refer [disable-reload! refresh refresh-all]] 4 | [com.ig.swagger.search.standalone :as standalone] 5 | clojure.java.shell)) 6 | 7 | (defonce system (atom nil)) 8 | 9 | (defn start 10 | "Starts the current development system." 11 | [] 12 | (standalone/start system (standalone/find-config-file))) 13 | 14 | (defn stop 15 | "Shuts down and destroys the current development system." 16 | [] 17 | (standalone/stop system)) 18 | 19 | (defn go 20 | "Initializes the current development system and starts it running." 21 | [] 22 | (start) 23 | :ok) 24 | 25 | (defn reset [] 26 | (stop) 27 | (refresh :after 'dev/go)) 28 | 29 | (defn run-index [] 30 | ((:index-fn (:scheduled-indexer @system)))) -------------------------------------------------------------------------------- /src/com/ig/swagger/search/util/ring_middleware.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.util.ring-middleware) 2 | 3 | (defn remove-context 4 | "Removes the deployed servlet context from a URI when running as a 5 | deployed web application" 6 | [handler] 7 | (fn [request] 8 | (if-let [context (:servlet-context-path request)] 9 | (let [uri (:uri request)] 10 | (if (.startsWith uri context) 11 | (handler (assoc request :uri 12 | (.substring uri (.length context)))) 13 | (handler request))) 14 | (handler request)))) 15 | 16 | (defn wrap-with-additional-keys-in-req 17 | "Adds to all requests the key value pairs" 18 | [handler & additional-keys-in-req] 19 | (fn [req] 20 | (handler (apply assoc req additional-keys-in-req)))) 21 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lib/jquery.wiggle.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Wiggle 3 | Author: WonderGroup, Jordan Thomas 4 | URL: http://labs.wondergroup.com/demos/mini-ui/index.html 5 | License: MIT (http://en.wikipedia.org/wiki/MIT_License) 6 | */ 7 | jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('
').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);} 8 | if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});}; -------------------------------------------------------------------------------- /examples/consul/swagger-search/consul.swagger.config.edn: -------------------------------------------------------------------------------- 1 | {:proxy-client {:connection-timeout 10000 2 | :request-timeout 10000 3 | :max-connections 2000} 4 | :collector {:http-config {:connection-timeout 10000 5 | :request-timeout 10000 6 | :max-connections 2000} 7 | :possible-swagger-ui-suffix ["/swagger/ui/index.html" "/swagger-ui.html" "/swagger/index.html" "/swagger/ui/swagger-ui.html"] 8 | :possible-swagger-doc-suffix ["/swagger.json" "/v2/api-docs/" "/swagger/swagger.json" "/api-docs"]} 9 | 10 | :consul {:host "consul" 11 | :port 8500} 12 | } -------------------------------------------------------------------------------- /examples/etcd/swagger-search/etcd.swagger.config.edn: -------------------------------------------------------------------------------- 1 | {:proxy-client {:connection-timeout 10000 2 | :request-timeout 10000 3 | :max-connections 2000} 4 | :collector {:http-config {:connection-timeout 10000 5 | :request-timeout 10000 6 | :max-connections 2000} 7 | :possible-swagger-ui-suffix ["/swagger/ui/index.html" "/swagger-ui.html" "/swagger/index.html" "/swagger/ui/swagger-ui.html"] 8 | :possible-swagger-doc-suffix ["/swagger.json" "/v2/api-docs/" "/swagger/swagger.json" "/api-docs"]} 9 | 10 | :etcd {:host "etcd" 11 | :port 4001 12 | :prefix "/services"} 13 | } -------------------------------------------------------------------------------- /resources/public/css/swagger-search.css: -------------------------------------------------------------------------------- 1 | /* Navbar Style */ 2 | .navbar-default { 3 | background-color: #89bf04; 4 | border-color: #618704; 5 | } 6 | 7 | .navbar-default .navbar-brand { 8 | color: #ffffff; 9 | } 10 | 11 | .navbar-default .navbar-brand:hover, 12 | .navbar-default .navbar-brand:focus { 13 | color: #ffffff; 14 | } 15 | 16 | .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { 17 | margin-left: -15px; 18 | font-weight: bold; 19 | font-size: 22px; 20 | } 21 | 22 | a.navbar-brand img { 23 | margin-top: -5px; 24 | margin-right: 5px; 25 | } 26 | 27 | .search-title a { 28 | font-size: 16px; 29 | font-weight: bold; 30 | text-align: center; 31 | } 32 | 33 | .search-input { 34 | margin-bottom: 25px; 35 | } 36 | 37 | .search-box { 38 | background-color: #89bf04; 39 | } 40 | 41 | #renderingMessage { 42 | margin-top: 50px; 43 | } 44 | 45 | .result-container { 46 | max-height: 720px; 47 | overflow-y: auto; 48 | overflow-x: hidden; 49 | } -------------------------------------------------------------------------------- /examples/consul/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | consul: 4 | image: consul:0.9.0 5 | ports: 6 | - "8500:8500" 7 | api-sample-1: 8 | build: ../api-example-1 9 | ports: 10 | - "8501:3000" 11 | environment: 12 | - SERVICE_TAGS=master,backups 13 | - SERVICE_NAME=api1-example 14 | api-sample-2: 15 | build: ../api-example-1 16 | ports: 17 | - "8502:3000" 18 | environment: 19 | - SERVICE_TAGS=master,backups 20 | - SERVICE_NAME=api1-example 21 | swagger-search: 22 | build: swagger-search 23 | ports: 24 | - "8503:8503" 25 | - "8504:3000" 26 | volumes: 27 | - ../../:/app 28 | - ./swagger-search:/swagger-config 29 | - ~/.m2:/root/.m2 30 | - ~/.lein:/root/.lein 31 | environment: 32 | - SWAGGER_CONF=/swagger-config/consul.swagger.config.edn 33 | registrator: 34 | command: -internal=true consul://consul:8500 35 | image: gliderlabs/registrator:v7 36 | links: 37 | - consul 38 | volumes: 39 | - /var/run/docker.sock:/tmp/docker.sock 40 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/proxy.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.proxy 2 | (:require 3 | [ring.util.http-response :refer :all] 4 | [compojure.api.sweet :refer :all] 5 | [selmer.parser :refer [render-file]] 6 | [ring.util.response :refer [redirect]])) 7 | 8 | (defn- build-request [{:keys [request-method headers body]} endpoint] 9 | (let [proxy-request {:url endpoint 10 | :method request-method 11 | :headers (dissoc headers "host" "connection")}] 12 | (if (#{:get :head} request-method) 13 | proxy-request 14 | (assoc proxy-request 15 | :body (slurp body))))) 16 | 17 | (defn- stringify-header [[header-key header-value]] 18 | [(name header-key) header-value]) 19 | 20 | (defn- create-response-headers [headers] 21 | (into {} (map (partial stringify-header) 22 | (select-keys headers [:content-type :date])))) 23 | 24 | (defn handle-proxy-request [client request endpoint] 25 | (-> (client (build-request request endpoint)) 26 | (update :status :code) 27 | (update :headers create-response-headers))) -------------------------------------------------------------------------------- /examples/etcd/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | etcd: 4 | image: quay.io/coreos/etcd:v2.2.0 5 | command: > 6 | -listen-client-urls http://0.0.0.0:4001 7 | -advertise-client-urls http://etcd:4001 8 | ports: 9 | - 4001 10 | api-sample-1: 11 | build: ../api-example-1 12 | ports: 13 | - "8501:3000" 14 | environment: 15 | - SERVICE_TAGS=master,backups 16 | - SERVICE_NAME=api1-example 17 | api-sample-2: 18 | build: ../api-example-1 19 | ports: 20 | - "8502:3000" 21 | environment: 22 | - SERVICE_TAGS=master,backups 23 | - SERVICE_NAME=api1-example 24 | swagger-search: 25 | build: swagger-search 26 | ports: 27 | - "8503:8503" 28 | - "8504:3000" 29 | volumes: 30 | - ../../:/app 31 | - ./swagger-search:/swagger-config 32 | - ~/.m2:/root/.m2 33 | - ~/.lein:/root/.lein 34 | environment: 35 | - SWAGGER_CONF=/swagger-config/etcd.swagger.config.edn 36 | registrator: 37 | command: -internal=true etcd://etcd:4001/services 38 | image: gliderlabs/registrator:v7 39 | links: 40 | - etcd 41 | volumes: 42 | - /var/run/docker.sock:/tmp/docker.sock 43 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lib/highlight.9.1.0.pack_extended.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | var configure, highlightBlock; 5 | 6 | configure = hljs.configure; 7 | // "extending" hljs.configure method 8 | hljs.configure = function _configure (options) { 9 | var size = options.highlightSizeThreshold; 10 | 11 | // added highlightSizeThreshold option to set maximum size 12 | // of processed string. Set to null if not a number 13 | hljs.highlightSizeThreshold = size === +size ? size : null; 14 | 15 | configure.call(this, options); 16 | }; 17 | 18 | highlightBlock = hljs.highlightBlock; 19 | 20 | // "extending" hljs.highlightBlock method 21 | hljs.highlightBlock = function _highlightBlock (el) { 22 | var innerHTML = el.innerHTML; 23 | var size = hljs.highlightSizeThreshold; 24 | 25 | // check if highlightSizeThreshold is not set or element innerHTML 26 | // is less than set option highlightSizeThreshold 27 | if (size == null || size > innerHTML.length) { 28 | // proceed with hljs.highlightBlock 29 | highlightBlock.call(hljs, el); 30 | } 31 | }; 32 | 33 | })(); 34 | 35 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/discovery/providers/consul.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.discovery.providers.consul 2 | (:require [consul.core :as consul] 3 | [clojure.tools.logging :as log])) 4 | 5 | (defn consul-index 6 | [conn method endpoint params] 7 | (let [{:keys [body headers]} (consul/consul conn method endpoint params)] 8 | (assoc (consul/headers->index headers) :body body))) 9 | 10 | (defn catalog-services 11 | ([conn] 12 | (catalog-services conn {})) 13 | ([conn params] 14 | (:body (consul-index conn :get [:catalog :services] {:query-params params})))) 15 | 16 | (defn key-not-blank [key] 17 | (comp (complement clojure.string/blank?) key)) 18 | 19 | (defn create-consul [{:keys [consul]}] 20 | (let [config {:server-name (:host consul) 21 | :server-port (:port consul)}] 22 | (fn [] 23 | (log/info "consul provider is using" config) 24 | (->> 25 | (catalog-services config) 26 | keys 27 | (map #(consul/catalog-service config %)) 28 | (map first) 29 | (filter (key-not-blank :service-address)) 30 | (filter :service-port) 31 | (map (fn [{:keys [service-address service-port]}] (str "http://" service-address ":" service-port))) 32 | distinct)))) -------------------------------------------------------------------------------- /test/test_data/parser_v2_no_operationId.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "ip1", 3 | "swagger": "2.0", 4 | "basePath": "/a-tomcat-service", 5 | "paths": { 6 | "/ping/abc": { 7 | "GET": { 8 | "tags": [ 9 | "controller" 10 | ], 11 | "summary": "ping pong", 12 | "parameters": [ 13 | { 14 | "in": "query", 15 | "name": "who", 16 | "description": "", 17 | "required": true, 18 | "type": "string" 19 | } 20 | ], 21 | "responses": { 22 | "200": { 23 | "schema": { 24 | "$ref": "#/definitions/PingResponse" 25 | } 26 | } 27 | } 28 | } 29 | }, 30 | "/search": { 31 | "GET": { 32 | "tags": [ 33 | "controller" 34 | ], 35 | "summary": "Search for API", 36 | "parameters": [ 37 | { 38 | "in": "query", 39 | "name": "q", 40 | "description": "", 41 | "required": true, 42 | "type": "string" 43 | } 44 | ], 45 | "responses": { 46 | "200": { 47 | "schema": { 48 | "$ref": "#/definitions/SearchResponse" 49 | }, 50 | "description": "" 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /test/test_data/parser_v2_working.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "ip1", 3 | "swagger": "2.0", 4 | "basePath": "/a-tomcat-service", 5 | "paths": { 6 | "/ping/abc": { 7 | "GET": { 8 | "tags": [ 9 | "controller" 10 | ], 11 | "operationId": "operationId", 12 | "summary": "ping pong", 13 | "parameters": [ 14 | { 15 | "in": "query", 16 | "name": "who", 17 | "description": "", 18 | "required": true, 19 | "type": "string" 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "schema": { 25 | "$ref": "#/definitions/PingResponse" 26 | } 27 | } 28 | } 29 | } 30 | }, 31 | "/search": { 32 | "GET": { 33 | "tags": [ 34 | "controller" 35 | ], 36 | "operationId": "operationId", 37 | "summary": "Search for API", 38 | "parameters": [ 39 | { 40 | "in": "query", 41 | "name": "q", 42 | "description": "", 43 | "required": true, 44 | "type": "string" 45 | } 46 | ], 47 | "responses": { 48 | "200": { 49 | "schema": { 50 | "$ref": "#/definitions/SearchResponse" 51 | }, 52 | "description": "" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /examples/api-example-1/src/compojure/api/examples/domain.clj: -------------------------------------------------------------------------------- 1 | (ns compojure.api.examples.domain 2 | (:require [schema.core :as s] 3 | [ring.swagger.schema :refer [coerce!]])) 4 | 5 | ;; Domain 6 | 7 | (s/defschema Total {:total Long}) 8 | 9 | (def Topping (s/enum :cheese :olives :ham :pepperoni :habanero)) 10 | 11 | (s/defschema Pizza {:id Long 12 | :name String 13 | :price Double 14 | :hot Boolean 15 | (s/optional-key :description) String 16 | :toppings #{Topping}}) 17 | 18 | (s/defschema NewPizza (dissoc Pizza :id)) 19 | 20 | (s/defschema NewSingleToppingPizza (assoc NewPizza :toppings Topping)) 21 | 22 | ;; Repository 23 | 24 | (defonce id-seq (atom 0)) 25 | (defonce pizzas (atom (array-map))) 26 | 27 | (defn get-pizza [id] (@pizzas id)) 28 | (defn get-pizzas [] (-> pizzas deref vals reverse)) 29 | (defn delete! [id] (swap! pizzas dissoc id) nil) 30 | 31 | (defn add! [new-pizza] 32 | (let [id (swap! id-seq inc) 33 | pizza (coerce! Pizza (assoc new-pizza :id id))] 34 | (swap! pizzas assoc id pizza) 35 | pizza)) 36 | 37 | (defn update! [pizza] 38 | (let [pizza (coerce! Pizza pizza)] 39 | (swap! pizzas assoc (:id pizza) pizza) 40 | (get-pizza (:id pizza)))) 41 | 42 | ;; Data 43 | 44 | (when (empty? @pizzas) 45 | (add! {:name "Frutti" :price 9.50 :hot false :toppings #{:cheese :olives}}) 46 | (add! {:name "Il Diablo" :price 12 :hot true :toppings #{:ham :habanero}})) 47 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/translator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Translator for documentation pages. 5 | * 6 | * To enable translation you should include one of language-files in your index.html 7 | * after . 8 | * For example - 9 | * 10 | * If you wish to translate some new texsts you should do two things: 11 | * 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too. 12 | * 2. Mark that text it templates this way New Phrase or . 13 | * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate. 14 | * 15 | */ 16 | window.SwaggerTranslator = { 17 | 18 | _words:[], 19 | 20 | translate: function(sel) { 21 | var $this = this; 22 | sel = sel || '[data-sw-translate]'; 23 | 24 | $(sel).each(function() { 25 | $(this).html($this._tryTranslate($(this).html())); 26 | 27 | $(this).val($this._tryTranslate($(this).val())); 28 | $(this).attr('title', $this._tryTranslate($(this).attr('title'))); 29 | }); 30 | }, 31 | 32 | _tryTranslate: function(word) { 33 | return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; 34 | }, 35 | 36 | learn: function(wordsMap) { 37 | this._words = wordsMap; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/discovery.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.discovery) 2 | 3 | (defn resolve-and-create [config f-name] 4 | (try 5 | (require (symbol (namespace f-name))) 6 | (catch Exception e (throw (RuntimeException. (str "Could not load namespace " (namespace f-name)) e)))) 7 | (if-let [f (resolve f-name)] 8 | (f config) 9 | (throw (RuntimeException. (str "Could not find function " f-name))))) 10 | 11 | (defn create-if-symbol [config f-or-fname] 12 | (if (fn? f-or-fname) 13 | f-or-fname 14 | (resolve-and-create config f-or-fname))) 15 | 16 | (defn provider [{:keys [discovery-providers] :as config}] 17 | (let [build-in-providers (keep identity 18 | [(if (:uri-or-file config) 19 | 'com.ig.swagger.search.discovery.providers.file/from-uri-or-file) 20 | (if (:classpath-file config) 21 | 'com.ig.swagger.search.discovery.providers.file/from-classpath) 22 | (if (:server-list config) 23 | 'com.ig.swagger.search.discovery.providers.file/server-list) 24 | (if (:etcd config) 25 | 'com.ig.swagger.search.discovery.providers.etcd/create-etcd) 26 | (if (:consul config) 27 | 'com.ig.swagger.search.discovery.providers.consul/create-consul)]) 28 | sources (map (partial create-if-symbol config) 29 | (concat discovery-providers 30 | build-in-providers))] 31 | (assert (seq sources) "No discovery providers configured. Please check your configuration") 32 | (fn [] 33 | (distinct (mapcat #(%) sources))))) -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/zh-cn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告:已过时", 6 | "Implementation Notes":"实现备注", 7 | "Response Class":"响应类", 8 | "Status":"状态", 9 | "Parameters":"参数", 10 | "Parameter":"参数", 11 | "Value":"值", 12 | "Description":"描述", 13 | "Parameter Type":"参数类型", 14 | "Data Type":"数据类型", 15 | "Response Messages":"响应消息", 16 | "HTTP Status Code":"HTTP状态码", 17 | "Reason":"原因", 18 | "Response Model":"响应模型", 19 | "Request URL":"请求URL", 20 | "Response Body":"响应体", 21 | "Response Code":"响应码", 22 | "Response Headers":"响应头", 23 | "Hide Response":"隐藏响应", 24 | "Headers":"头", 25 | "Try it out!":"试一下!", 26 | "Show/Hide":"显示/隐藏", 27 | "List Operations":"显示操作", 28 | "Expand Operations":"展开操作", 29 | "Raw":"原始", 30 | "can't parse JSON. Raw result":"无法解析JSON. 原始结果", 31 | "Model Schema":"模型架构", 32 | "Model":"模型", 33 | "apply":"应用", 34 | "Username":"用户名", 35 | "Password":"密码", 36 | "Terms of service":"服务条款", 37 | "Created by":"创建者", 38 | "See more at":"查看更多:", 39 | "Contact the developer":"联系开发者", 40 | "api version":"api版本", 41 | "Response Content Type":"响应Content Type", 42 | "fetching resource":"正在获取资源", 43 | "fetching resource list":"正在获取资源列表", 44 | "Explore":"浏览", 45 | "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", 47 | "Please specify the protocol for":"请指定协议:", 48 | "Can't read swagger JSON from":"无法读取swagger JSON于", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", 50 | "Unable to read api":"无法读取api", 51 | "from path":"从路径", 52 | "server returned":"服务器返回" 53 | }); 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | services: 7 | - docker 8 | 9 | cache: 10 | directories: 11 | - $HOME/.m2 12 | 13 | script: 14 | - ./ci/build.sh 15 | 16 | after_success: 17 | - ./ci/deploy.sh 18 | 19 | env: 20 | global: 21 | secure: CHhXiCTixhcH+GtLh4WYe6u1qZJdpDKmIGk4+VuIs6HtIzHl9I+dca8z0gzZ+KUD2SvZHRqIN9PAQVxMszFAuJwK0yW6KVV71YPohR05DWDciG5JBNknKz6EIsTYNqNw5aw5tfUK89WyVtC1C1H9CJJRlM0Pd25n6/neINcUh9yX3MLJPFelOpV6MpTD0ag8XcOskimM4n6E5G9f9hKXNV7Z9ENsWvXKh9Iym3x4maz/Cc6O0GM0yMMp9NACZWIbI1qrYribOUbqAzOJi9cpCnKFJ4G6tQhAkbczU9X86VyCYyIQaj/xfpAkk8Qk3eXZaeXr3Rpqs+hceQ7XPA10kUTXcHjNIZ7qwkQSSU11uS+auZTCxvbuc9riPkq+RtAc5alUcowNAWf1VOPH7+3acAlNxdIRAQobNqS+HEB63aHPhBGVwsLUHLSLKGWRNp+ycssYBmSjfdoSAUmat0rn0fTb2x1ukHcX+747gPfItZYRmY7cxETag3Ivbe7b4nwSlzebolFzXirQJ3jJv1XSON3M0RzybuePdh5Cefi6sCiZegiKyO0ig0HGmu3o7uZJKaPVDwWBHcoPbUnBn41hfJsh2Loa2Q2Ir+mfD8/uvXGhFGeZm4lgw2KQIqmP7WV9ObgJ5lfd4QhBODp0rC2Ll7jd8VWMIjBA9OsO8p1e85E= 22 | 23 | before_deploy: 24 | - ls 25 | - ls target 26 | - pwd 27 | - export RELEASE_PKG_FILE=$(ls target/*standalone*) 28 | - echo "deploying $RELEASE_PKG_FILE to GitHub releases" 29 | 30 | deploy: 31 | provider: releases 32 | skip_cleanup: true 33 | api_key: 34 | secure: HL4KEMnnnR+g0pVYNnaOo/fSx7ktVB5KmnZzx6LyiSa8jGebmvPQPwvHyb/NO9b+bCL6F7wrxXGJa4+IokPqzKitbFU4ob0iLe/pXPioP9zbsJg9Rk7/DE/U3EzIvug7k7v2Vyfi4Fc7zxWvnWbB0sNar6LukioRZVkT0EH+iQoawWBChQT6FBQW2ATNKlvUbxC4qjflCP+N44q5VOnWSc211RNfQOH1Icd9xYnW62GmYUk6Wbwz79y9dqKzpi1Y4j8rWkbDELSi8kBuEzbMHjM0avwwwY/yJmGwxn/SdNTv1DrfmVpdmG9VbWEHUwOl4IIqoIaiaVG5kt/2DCuwOzDbanXmAUiPvI/sz1xBH5DTWtrY7zoFBMD51o13AuWAZoPXyKBU3t+brG+64IuR79MFS9Ms0NM0dpMbJLW5mkdqlAMbqKXjm+yF/ao48BjAyoddJ3uY3LPfSKkz8O79gFEOvd0b/7p6McMom1P7h6/WqVT5rudy+zvCFFbhs95cVDUexil5AfJVRoIccJuzsEhOpoNtBss+HOWAPsQfuIb4oyyk6BRxa2/Iuy7MFPxraxEDtrjLxDWnvmybYybGWj87c7GL9VQowNDb3rAiUresSGgSUxUcoaNlzZ6Rms9s2GgEch2pPqrwYGHjRrUexLh9S8rCXFmfbutzt+OxKxU= 35 | file: "${RELEASE_PKG_FILE}" 36 | on: 37 | repo: IG-Group/swagger-search 38 | tags: true -------------------------------------------------------------------------------- /src/com/ig/swagger/search/core.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.core 2 | "Namespace to bootstrap the application. Equivalent to the spring.xml" 3 | (:use [ring.util.response] 4 | [clojure.tools.logging :only [info warn error]]) 5 | (:require [com.ig.swagger.search 6 | [routes :as routes] 7 | [index :as index] 8 | [collector :as collector] 9 | [discovery :as discovery]] 10 | [com.ig.swagger.search.util.ring-middleware :as ring-middleware] 11 | [com.ig.swagger.search.http-client.core :as http])) 12 | 13 | (defn system-start 14 | "Given a config, it knows how to create and start a new system" 15 | [app-config] 16 | (info "Application starting up now ...") 17 | 18 | (let [index (index/create) 19 | provider (discovery/provider app-config) 20 | proxy (http/create-client (:proxy-client app-config)) 21 | routes (-> (var routes/http-api) 22 | ring-middleware/remove-context 23 | (ring-middleware/wrap-with-additional-keys-in-req :index index 24 | :proxy-client proxy)) 25 | scheduled-indexer (collector/schedule-indexing provider 26 | (:collector app-config) 27 | (partial index/replace index)) 28 | system {:app-config app-config 29 | :routes routes 30 | :fetching-fn provider 31 | :index index 32 | :proxy proxy 33 | :scheduled-indexer scheduled-indexer}] 34 | (info "Application started") 35 | system)) 36 | 37 | (defn system-stop 38 | "Given a system created by system-start, it knows how to stop it" 39 | [system] 40 | (info "Application stopping ...") 41 | (collector/destroy-component (:scheduled-indexer system)) 42 | (http/destroy (:proxy system)) 43 | (info "Application stopped")) -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/ja.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告: 廃止予定", 6 | "Implementation Notes":"実装メモ", 7 | "Response Class":"レスポンスクラス", 8 | "Status":"ステータス", 9 | "Parameters":"パラメータ群", 10 | "Parameter":"パラメータ", 11 | "Value":"値", 12 | "Description":"説明", 13 | "Parameter Type":"パラメータタイプ", 14 | "Data Type":"データタイプ", 15 | "Response Messages":"レスポンスメッセージ", 16 | "HTTP Status Code":"HTTPステータスコード", 17 | "Reason":"理由", 18 | "Response Model":"レスポンスモデル", 19 | "Request URL":"リクエストURL", 20 | "Response Body":"レスポンスボディ", 21 | "Response Code":"レスポンスコード", 22 | "Response Headers":"レスポンスヘッダ", 23 | "Hide Response":"レスポンスを隠す", 24 | "Headers":"ヘッダ", 25 | "Try it out!":"実際に実行!", 26 | "Show/Hide":"表示/非表示", 27 | "List Operations":"操作一覧", 28 | "Expand Operations":"操作の展開", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果", 31 | "Model Schema":"モデルスキーマ", 32 | "Model":"モデル", 33 | "apply":"実行", 34 | "Username":"ユーザ名", 35 | "Password":"パスワード", 36 | "Terms of service":"サービス利用規約", 37 | "Created by":"Created by", 38 | "See more at":"See more at", 39 | "Contact the developer":"開発者に連絡", 40 | "api version":"APIバージョン", 41 | "Response Content Type":"レスポンス コンテンツタイプ", 42 | "fetching resource":"リソースの取得", 43 | "fetching resource list":"リソース一覧の取得", 44 | "Explore":"Explore", 45 | "Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.", 47 | "Please specify the protocol for":"プロトコルを指定してください", 48 | "Can't read swagger JSON from":"次からswagger JSONを読み込めません", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています", 50 | "Unable to read api":"APIを読み込めません", 51 | "from path":"次のパスから", 52 | "server returned":"サーバからの返答" 53 | }); 54 | -------------------------------------------------------------------------------- /ci/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | increment_version () 6 | { 7 | declare -a part=( ${1//\./ } ) 8 | declare new 9 | declare -i carry=1 10 | 11 | for (( CNTR=${#part[@]}-1; CNTR>=0; CNTR-=1 )); do 12 | new=$((part[CNTR]+carry)) 13 | carry=0 14 | part[CNTR]=${new} 15 | done 16 | new="${part[*]}" 17 | echo -e "${new// /.}" 18 | } 19 | 20 | echo "Branch is $TRAVIS_BRANCH" 21 | 22 | if ! [[ "${TRAVIS_BRANCH}" == "master" || "${TRAVIS_BRANCH}" =~ ^v[0-9] ]]; then 23 | exit 0 24 | fi 25 | 26 | if [[ "${TRAVIS_PULL_REQUEST}" != "false" ]]; then 27 | exit 0 28 | fi 29 | 30 | AUTOMATED_AUTHOR_EMAIL=builds@travis-ci.com 31 | LAST_COMMIT_AUTHOR_EMAIL=$(git --no-pager show -s --format='%ae' HEAD) 32 | echo "last commit ID: $LAST_COMMIT_AUTHOR_EMAIL" 33 | VERSION=`cat version` 34 | 35 | if [ $LAST_COMMIT_AUTHOR_EMAIL != $AUTOMATED_AUTHOR_EMAIL ]; then 36 | 37 | # CREATE GIT TAG 38 | git config --global user.email $AUTOMATED_AUTHOR_EMAIL 39 | git config --global user.name "Travis CI" 40 | git checkout "$TRAVIS_BRANCH" 41 | 42 | increment_version $VERSION > version 43 | echo "Current version $VERSION, next version `cat version`" 44 | export GIT_TAG=v`cat version` 45 | git commit -m "Set release build VERSION number" version 46 | git tag $GIT_TAG -a -m "Generated tag from TravisCI build $TRAVIS_BUILD_NUMBER" 47 | echo "Tag done" 48 | git push --tags --quiet https://$GITHUBKEY@github.com/IG-Group/swagger-search master > /dev/null 2>&1 49 | echo "Pushing" 50 | 51 | else 52 | if [[ "${TRAVIS_BRANCH}" =~ ^v[0-9] ]]; then 53 | echo "Deploying version $VERSION" 54 | lein with-profile +set-version set-version $VERSION 55 | lein deploy releases 56 | lein with-profile +not-lib uberjar 57 | 58 | IMAGE_NAME="danlebrero/swagger-search:${VERSION}" 59 | docker build -t ${IMAGE_NAME} -t danlebrero/swagger-search:latest . 60 | # Pushing images 61 | docker login -u="${DOCKERHUB_USERNAME}" -p="${DOCKERHUB_PASSWORD}" 62 | docker push "$IMAGE_NAME" 63 | docker push danlebrero/swagger-search:latest 64 | else 65 | exit 0 66 | fi 67 | fi -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/tr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uyarı: Deprecated", 6 | "Implementation Notes":"Gerçekleştirim Notları", 7 | "Response Class":"Dönen Sınıf", 8 | "Status":"Statü", 9 | "Parameters":"Parametreler", 10 | "Parameter":"Parametre", 11 | "Value":"Değer", 12 | "Description":"Açıklama", 13 | "Parameter Type":"Parametre Tipi", 14 | "Data Type":"Veri Tipi", 15 | "Response Messages":"Dönüş Mesajı", 16 | "HTTP Status Code":"HTTP Statü Kodu", 17 | "Reason":"Gerekçe", 18 | "Response Model":"Dönüş Modeli", 19 | "Request URL":"İstek URL", 20 | "Response Body":"Dönüş İçeriği", 21 | "Response Code":"Dönüş Kodu", 22 | "Response Headers":"Dönüş Üst Bilgileri", 23 | "Hide Response":"Dönüşü Gizle", 24 | "Headers":"Üst Bilgiler", 25 | "Try it out!":"Dene!", 26 | "Show/Hide":"Göster/Gizle", 27 | "List Operations":"Operasyonları Listele", 28 | "Expand Operations":"Operasyonları Aç", 29 | "Raw":"Ham", 30 | "can't parse JSON. Raw result":"JSON çözümlenemiyor. Ham sonuç", 31 | "Model Schema":"Model Şema", 32 | "Model":"Model", 33 | "apply":"uygula", 34 | "Username":"Kullanıcı Adı", 35 | "Password":"Parola", 36 | "Terms of service":"Servis şartları", 37 | "Created by":"Oluşturan", 38 | "See more at":"Daha fazlası için", 39 | "Contact the developer":"Geliştirici ile İletişime Geçin", 40 | "api version":"api versiyon", 41 | "Response Content Type":"Dönüş İçerik Tipi", 42 | "fetching resource":"kaynak getiriliyor", 43 | "fetching resource list":"kaynak listesi getiriliyor", 44 | "Explore":"Keşfet", 45 | "Show Swagger Petstore Example Apis":"Swagger Petstore Örnek Api'yi Gör", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.", 47 | "Please specify the protocol for":"Lütfen istenen adres için protokol belirtiniz", 48 | "Can't read swagger JSON from":"Swagger JSON bu kaynaktan okunamıyor", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor", 50 | "Unable to read api":"api okunamadı", 51 | "from path":"yoldan", 52 | "server returned":"sunucuya dönüldü" 53 | }); 54 | -------------------------------------------------------------------------------- /standalone/com/ig/swagger/search/standalone.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.standalone 2 | (:require [com.ig.swagger.search.core :as search] 3 | [ring.adapter.jetty :as jetty] 4 | [clojure.java.io :as io] 5 | [dynapath.util :as dp] 6 | [clojure.tools.logging :as log] 7 | [clojure.edn :as edn]) 8 | (:gen-class)) 9 | 10 | (defonce the-system (atom nil)) 11 | 12 | (defn start [system-atom config] 13 | (let [system (search/system-start config)] 14 | (reset! system-atom (assoc system 15 | :jetty (jetty/run-jetty (:routes system) 16 | {:port (get-in config [:standalone :port] 3000) 17 | :host (get-in config [:standalone :host] "0.0.0.0") 18 | :join? false}))))) 19 | 20 | (defn stop [system-atom] 21 | (when-let [system @system-atom] 22 | (.stop (:jetty system)) 23 | (search/system-stop system))) 24 | 25 | (defn add-to-classpath [file-or-url] 26 | (log/info "Adding" (.getAbsolutePath (io/as-file file-or-url)) "to classpath") 27 | (dp/add-classpath-url (.getContextClassLoader (Thread/currentThread)) (io/as-url file-or-url))) 28 | 29 | (defn add-libs-to-classpath [home-dir] 30 | (let [lib-dir (io/file (io/as-file home-dir) "libs")] 31 | (doseq [file (file-seq lib-dir)] 32 | (when (or (.isDirectory file) 33 | (.endsWith (.getName file) "jar")) 34 | (add-to-classpath file))))) 35 | 36 | (defn find-config-file [] 37 | (or 38 | (some-> 39 | (or (System/getenv "SWAGGER_CONF") 40 | (System/getProperty "SWAGGER_CONF") 41 | (io/resource "swagger.config.edn")) 42 | io/file 43 | slurp 44 | edn/read-string) 45 | (throw (RuntimeException. "No swagger.config.edn file at $SWAGGER_HOME or SWAGGER_CONF not specified")))) 46 | 47 | (defn -main [& args] 48 | (let [home-dir (or 49 | (cond-> (or (System/getenv "SWAGGER_HOME") (System/getProperty "SWAGGER_HOME") (first args)) 50 | some? io/file 51 | #(.isDirectory %) io/as-url) 52 | (throw (RuntimeException. "No SWAGGER_HOME environment set. Please see documentation"))) 53 | _ (add-to-classpath home-dir) 54 | 55 | config (find-config-file)] 56 | (add-libs-to-classpath home-dir) 57 | (start the-system config))) -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/pl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uwaga: Wycofane", 6 | "Implementation Notes":"Uwagi Implementacji", 7 | "Response Class":"Klasa Odpowiedzi", 8 | "Status":"Status", 9 | "Parameters":"Parametry", 10 | "Parameter":"Parametr", 11 | "Value":"Wartość", 12 | "Description":"Opis", 13 | "Parameter Type":"Typ Parametru", 14 | "Data Type":"Typ Danych", 15 | "Response Messages":"Wiadomości Odpowiedzi", 16 | "HTTP Status Code":"Kod Statusu HTTP", 17 | "Reason":"Przyczyna", 18 | "Response Model":"Model Odpowiedzi", 19 | "Request URL":"URL Wywołania", 20 | "Response Body":"Treść Odpowiedzi", 21 | "Response Code":"Kod Odpowiedzi", 22 | "Response Headers":"Nagłówki Odpowiedzi", 23 | "Hide Response":"Ukryj Odpowiedź", 24 | "Headers":"Nagłówki", 25 | "Try it out!":"Wypróbuj!", 26 | "Show/Hide":"Pokaż/Ukryj", 27 | "List Operations":"Lista Operacji", 28 | "Expand Operations":"Rozwiń Operacje", 29 | "Raw":"Nieprzetworzone", 30 | "can't parse JSON. Raw result":"nie można przetworzyć pliku JSON. Nieprzetworzone dane", 31 | "Model Schema":"Schemat Modelu", 32 | "Model":"Model", 33 | "apply":"użyj", 34 | "Username":"Nazwa użytkownika", 35 | "Password":"Hasło", 36 | "Terms of service":"Warunki używania", 37 | "Created by":"Utworzone przez", 38 | "See more at":"Zobacz więcej na", 39 | "Contact the developer":"Kontakt z deweloperem", 40 | "api version":"wersja api", 41 | "Response Content Type":"Typ Zasobu Odpowiedzi", 42 | "fetching resource":"ładowanie zasobu", 43 | "fetching resource list":"ładowanie listy zasobów", 44 | "Explore":"Eksploruj", 45 | "Show Swagger Petstore Example Apis":"Pokaż Przykładowe Api Swagger Petstore", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.", 47 | "Please specify the protocol for":"Proszę podać protokół dla", 48 | "Can't read swagger JSON from":"Nie można odczytać swagger JSON z", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI", 50 | "Unable to read api":"Nie można odczytać api", 51 | "from path":"ze ścieżki", 52 | "server returned":"serwer zwrócił" 53 | }); 54 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/pt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Aviso: Depreciado", 6 | "Implementation Notes":"Notas de Implementação", 7 | "Response Class":"Classe de resposta", 8 | "Status":"Status", 9 | "Parameters":"Parâmetros", 10 | "Parameter":"Parâmetro", 11 | "Value":"Valor", 12 | "Description":"Descrição", 13 | "Parameter Type":"Tipo de parâmetro", 14 | "Data Type":"Tipo de dados", 15 | "Response Messages":"Mensagens de resposta", 16 | "HTTP Status Code":"Código de status HTTP", 17 | "Reason":"Razão", 18 | "Response Model":"Modelo resposta", 19 | "Request URL":"URL requisição", 20 | "Response Body":"Corpo da resposta", 21 | "Response Code":"Código da resposta", 22 | "Response Headers":"Cabeçalho da resposta", 23 | "Headers":"Cabeçalhos", 24 | "Hide Response":"Esconder resposta", 25 | "Try it out!":"Tente agora!", 26 | "Show/Hide":"Mostrar/Esconder", 27 | "List Operations":"Listar operações", 28 | "Expand Operations":"Expandir operações", 29 | "Raw":"Cru", 30 | "can't parse JSON. Raw result":"Falha ao analisar JSON. Resulto cru", 31 | "Model Schema":"Modelo esquema", 32 | "Model":"Modelo", 33 | "apply":"Aplicar", 34 | "Username":"Usuário", 35 | "Password":"Senha", 36 | "Terms of service":"Termos do serviço", 37 | "Created by":"Criado por", 38 | "See more at":"Veja mais em", 39 | "Contact the developer":"Contate o desenvolvedor", 40 | "api version":"Versão api", 41 | "Response Content Type":"Tipo de conteúdo da resposta", 42 | "fetching resource":"busca recurso", 43 | "fetching resource list":"buscando lista de recursos", 44 | "Explore":"Explorar", 45 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin", 47 | "Please specify the protocol for":"Por favor especifique o protocolo", 48 | "Can't read swagger JSON from":"Não é possível ler o JSON Swagger de", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Carregar informação de recurso finalizada. Renderizando Swagger UI", 50 | "Unable to read api":"Não foi possível ler api", 51 | "from path":"do caminho", 52 | "server returned":"servidor retornou" 53 | }); 54 | -------------------------------------------------------------------------------- /examples/swagger.config.edn: -------------------------------------------------------------------------------- 1 | { 2 | :standalone { 3 | ;; On which host and port to listen 4 | :host "0.0.0.0" 5 | :port 3000} 6 | ;; Http client used by the proxy endpoint 7 | :proxy-client {:connection-timeout 10000 8 | :request-timeout 10000 9 | :max-connections 200} 10 | :collector { 11 | ;; Http client used to collect the swagger docs 12 | :http-config {:connection-timeout 10000 13 | :request-timeout 10000 14 | :max-connections 200} 15 | ; For each service, Swagger Search will try each of the following paths. 16 | ; The first one that returns a 200 with JSON will be assumed to be the swagger doc 17 | ; If no suffix required, specify the empty string [""] 18 | :possible-swagger-doc-suffix [ "/swagger/swagger.json" ""] 19 | 20 | ; Same as possible-swagger-doc-suffix but for the Swagger UI. 21 | ; To disable, comment out. 22 | :possible-swagger-ui-suffix ["/swagger/ui/index.html" "/swagger-ui.html" "/swagger/index.html" "/swagger/ui/swagger-ui.html"] 23 | } 24 | 25 | ;; At least one service discovery mechanism must be specified. 26 | 27 | ;; A harcoded list of endpoints. To refresh the list, Swagger Search will need to be restarted 28 | ;:server-list ["https://watson-api-explorer.mybluemix.net/listings/conversation-v1.json" 29 | ; "http://devdocs.magento.com/swagger/schemas/latest-2.1.schema.json" 30 | ; "https://api.apis.guru/v2/specs/googleapis.com/youtube/v3/swagger.json"] 31 | 32 | ;; A path to a local file or a url. 33 | ;; One line per service. 34 | ;:uri-or-file "http://your-custom-service-discovery/a-list" 35 | 36 | ;; A file in the classpath 37 | ;; One line per service. 38 | ;:classpath-file "server-list" 39 | 40 | ;; Consul service discovery 41 | ;:consul {:host "consul" 42 | ; :port 8500} 43 | 44 | ;; Etcd service discovery 45 | ;:etcd {:host "etcd" 46 | ; :port 4001 47 | ; :prefix "/services"} 48 | 49 | ; For custom sevice discovery mechanism, specify here a function or functions to create it. 50 | ; See com.ig.swagger.search.discovery.providers.etcd/create-etcd as an example. 51 | ;:discovery-providers [your.custom.service.discovery/create-service-discovery] 52 | 53 | } -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/en.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Warning: Deprecated", 6 | "Implementation Notes":"Implementation Notes", 7 | "Response Class":"Response Class", 8 | "Status":"Status", 9 | "Parameters":"Parameters", 10 | "Parameter":"Parameter", 11 | "Value":"Value", 12 | "Description":"Description", 13 | "Parameter Type":"Parameter Type", 14 | "Data Type":"Data Type", 15 | "Response Messages":"Response Messages", 16 | "HTTP Status Code":"HTTP Status Code", 17 | "Reason":"Reason", 18 | "Response Model":"Response Model", 19 | "Request URL":"Request URL", 20 | "Response Body":"Response Body", 21 | "Response Code":"Response Code", 22 | "Response Headers":"Response Headers", 23 | "Hide Response":"Hide Response", 24 | "Headers":"Headers", 25 | "Try it out!":"Try it out!", 26 | "Show/Hide":"Show/Hide", 27 | "List Operations":"List Operations", 28 | "Expand Operations":"Expand Operations", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"can't parse JSON. Raw result", 31 | "Example Value":"Example Value", 32 | "Model Schema":"Model Schema", 33 | "Model":"Model", 34 | "Click to set as parameter value":"Click to set as parameter value", 35 | "apply":"apply", 36 | "Username":"Username", 37 | "Password":"Password", 38 | "Terms of service":"Terms of service", 39 | "Created by":"Created by", 40 | "See more at":"See more at", 41 | "Contact the developer":"Contact the developer", 42 | "api version":"api version", 43 | "Response Content Type":"Response Content Type", 44 | "Parameter content type:":"Parameter content type:", 45 | "fetching resource":"fetching resource", 46 | "fetching resource list":"fetching resource list", 47 | "Explore":"Explore", 48 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 49 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.", 50 | "Please specify the protocol for":"Please specify the protocol for", 51 | "Can't read swagger JSON from":"Can't read swagger JSON from", 52 | "Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI", 53 | "Unable to read api":"Unable to read api", 54 | "from path":"from path", 55 | "server returned":"server returned" 56 | }); 57 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/ru.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Предупреждение: Устарело", 6 | "Implementation Notes":"Заметки", 7 | "Response Class":"Пример ответа", 8 | "Status":"Статус", 9 | "Parameters":"Параметры", 10 | "Parameter":"Параметр", 11 | "Value":"Значение", 12 | "Description":"Описание", 13 | "Parameter Type":"Тип параметра", 14 | "Data Type":"Тип данных", 15 | "HTTP Status Code":"HTTP код", 16 | "Reason":"Причина", 17 | "Response Model":"Структура ответа", 18 | "Request URL":"URL запроса", 19 | "Response Body":"Тело ответа", 20 | "Response Code":"HTTP код ответа", 21 | "Response Headers":"Заголовки ответа", 22 | "Hide Response":"Спрятать ответ", 23 | "Headers":"Заголовки", 24 | "Response Messages":"Что может прийти в ответ", 25 | "Try it out!":"Попробовать!", 26 | "Show/Hide":"Показать/Скрыть", 27 | "List Operations":"Операции кратко", 28 | "Expand Operations":"Операции подробно", 29 | "Raw":"В сыром виде", 30 | "can't parse JSON. Raw result":"Не удается распарсить ответ:", 31 | "Example Value":"Пример", 32 | "Model Schema":"Структура", 33 | "Model":"Описание", 34 | "Click to set as parameter value":"Нажмите, чтобы испльзовать в качестве значения параметра", 35 | "apply":"применить", 36 | "Username":"Имя пользователя", 37 | "Password":"Пароль", 38 | "Terms of service":"Условия использования", 39 | "Created by":"Разработано", 40 | "See more at":"Еще тут", 41 | "Contact the developer":"Связаться с разработчиком", 42 | "api version":"Версия API", 43 | "Response Content Type":"Content Type ответа", 44 | "Parameter content type:":"Content Type параметра:", 45 | "fetching resource":"Получение ресурса", 46 | "fetching resource list":"Получение ресурсов", 47 | "Explore":"Показать", 48 | "Show Swagger Petstore Example Apis":"Показать примеры АПИ", 49 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа", 50 | "Please specify the protocol for":"Пожалуйста, укажите протокол для", 51 | "Can't read swagger JSON from":"Не получается прочитать swagger json из", 52 | "Finished Loading Resource Information. Rendering Swagger UI":"Загрузка информации о ресурсах завершена. Рендерим", 53 | "Unable to read api":"Не удалось прочитать api", 54 | "from path":"по адресу", 55 | "server returned":"сервер сказал" 56 | }); 57 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/geo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"ყურადღება: აღარ გამოიყენება", 6 | "Implementation Notes":"იმპლემენტაციის აღწერა", 7 | "Response Class":"რესპონს კლასი", 8 | "Status":"სტატუსი", 9 | "Parameters":"პარამეტრები", 10 | "Parameter":"პარამეტრი", 11 | "Value":"მნიშვნელობა", 12 | "Description":"აღწერა", 13 | "Parameter Type":"პარამეტრის ტიპი", 14 | "Data Type":"მონაცემის ტიპი", 15 | "Response Messages":"პასუხი", 16 | "HTTP Status Code":"HTTP სტატუსი", 17 | "Reason":"მიზეზი", 18 | "Response Model":"რესპონს მოდელი", 19 | "Request URL":"მოთხოვნის URL", 20 | "Response Body":"პასუხის სხეული", 21 | "Response Code":"პასუხის კოდი", 22 | "Response Headers":"პასუხის ჰედერები", 23 | "Hide Response":"დამალე პასუხი", 24 | "Headers":"ჰედერები", 25 | "Try it out!":"ცადე !", 26 | "Show/Hide":"გამოჩენა/დამალვა", 27 | "List Operations":"ოპერაციების სია", 28 | "Expand Operations":"ოპერაციები ვრცლად", 29 | "Raw":"ნედლი", 30 | "can't parse JSON. Raw result":"JSON-ის დამუშავება ვერ მოხერხდა. ნედლი პასუხი", 31 | "Example Value":"მაგალითი", 32 | "Model Schema":"მოდელის სტრუქტურა", 33 | "Model":"მოდელი", 34 | "Click to set as parameter value":"პარამეტრისთვის მნიშვნელობის მისანიჭებლად, დააკლიკე", 35 | "apply":"გამოყენება", 36 | "Username":"მოხმარებელი", 37 | "Password":"პაროლი", 38 | "Terms of service":"მომსახურების პირობები", 39 | "Created by":"შექმნა", 40 | "See more at":"ნახე ვრცლად", 41 | "Contact the developer":"დაუკავშირდი დეველოპერს", 42 | "api version":"api ვერსია", 43 | "Response Content Type":"პასუხის კონტენტის ტიპი", 44 | "Parameter content type:":"პარამეტრის კონტენტის ტიპი:", 45 | "fetching resource":"რესურსების მიღება", 46 | "fetching resource list":"რესურსების სიის მიღება", 47 | "Explore":"ნახვა", 48 | "Show Swagger Petstore Example Apis":"ნახე Swagger Petstore სამაგალითო Api", 49 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"სერვერთან დაკავშირება ვერ ხერხდება. შეამოწმეთ access-control-origin.", 50 | "Please specify the protocol for":"მიუთითეთ პროტოკოლი", 51 | "Can't read swagger JSON from":"swagger JSON წაკითხვა ვერ მოხერხდა", 52 | "Finished Loading Resource Information. Rendering Swagger UI":"რესურსების ჩატვირთვა სრულდება. Swagger UI რენდერდება", 53 | "Unable to read api":"api წაკითხვა ვერ მოხერხდა", 54 | "from path":"მისამართიდან", 55 | "server returned":"სერვერმა დააბრუნა" 56 | }); 57 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/it.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Attenzione: Deprecato", 6 | "Implementation Notes":"Note di implementazione", 7 | "Response Class":"Classe della risposta", 8 | "Status":"Stato", 9 | "Parameters":"Parametri", 10 | "Parameter":"Parametro", 11 | "Value":"Valore", 12 | "Description":"Descrizione", 13 | "Parameter Type":"Tipo di parametro", 14 | "Data Type":"Tipo di dato", 15 | "Response Messages":"Messaggi della risposta", 16 | "HTTP Status Code":"Codice stato HTTP", 17 | "Reason":"Motivo", 18 | "Response Model":"Modello di risposta", 19 | "Request URL":"URL della richiesta", 20 | "Response Body":"Corpo della risposta", 21 | "Response Code":"Oggetto della risposta", 22 | "Response Headers":"Intestazioni della risposta", 23 | "Hide Response":"Nascondi risposta", 24 | "Try it out!":"Provalo!", 25 | "Show/Hide":"Mostra/Nascondi", 26 | "List Operations":"Mostra operazioni", 27 | "Expand Operations":"Espandi operazioni", 28 | "Raw":"Grezzo (raw)", 29 | "can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).", 30 | "Model Schema":"Schema del modello", 31 | "Model":"Modello", 32 | "apply":"applica", 33 | "Username":"Nome utente", 34 | "Password":"Password", 35 | "Terms of service":"Condizioni del servizio", 36 | "Created by":"Creato da", 37 | "See more at":"Informazioni aggiuntive:", 38 | "Contact the developer":"Contatta lo sviluppatore", 39 | "api version":"versione api", 40 | "Response Content Type":"Tipo di contenuto (content type) della risposta", 41 | "fetching resource":"recuperando la risorsa", 42 | "fetching resource list":"recuperando lista risorse", 43 | "Explore":"Esplora", 44 | "Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore", 45 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.", 46 | "Please specify the protocol for":"Si prega di specificare il protocollo per", 47 | "Can't read swagger JSON from":"Impossibile leggere JSON swagger da:", 48 | "Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata", 49 | "Unable to read api":"Impossibile leggere la api", 50 | "from path":"da cartella", 51 | "server returned":"il server ha restituito" 52 | }); 53 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/es.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Advertencia: Obsoleto", 6 | "Implementation Notes":"Notas de implementación", 7 | "Response Class":"Clase de la Respuesta", 8 | "Status":"Status", 9 | "Parameters":"Parámetros", 10 | "Parameter":"Parámetro", 11 | "Value":"Valor", 12 | "Description":"Descripción", 13 | "Parameter Type":"Tipo del Parámetro", 14 | "Data Type":"Tipo del Dato", 15 | "Response Messages":"Mensajes de la Respuesta", 16 | "HTTP Status Code":"Código de Status HTTP", 17 | "Reason":"Razón", 18 | "Response Model":"Modelo de la Respuesta", 19 | "Request URL":"URL de la Solicitud", 20 | "Response Body":"Cuerpo de la Respuesta", 21 | "Response Code":"Código de la Respuesta", 22 | "Response Headers":"Encabezados de la Respuesta", 23 | "Hide Response":"Ocultar Respuesta", 24 | "Try it out!":"Pruébalo!", 25 | "Show/Hide":"Mostrar/Ocultar", 26 | "List Operations":"Listar Operaciones", 27 | "Expand Operations":"Expandir Operaciones", 28 | "Raw":"Crudo", 29 | "can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo", 30 | "Example Value":"Valor de Ejemplo", 31 | "Model Schema":"Esquema del Modelo", 32 | "Model":"Modelo", 33 | "apply":"aplicar", 34 | "Username":"Nombre de usuario", 35 | "Password":"Contraseña", 36 | "Terms of service":"Términos de Servicio", 37 | "Created by":"Creado por", 38 | "See more at":"Ver más en", 39 | "Contact the developer":"Contactar al desarrollador", 40 | "api version":"versión de la api", 41 | "Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta", 42 | "fetching resource":"buscando recurso", 43 | "fetching resource list":"buscando lista del recurso", 44 | "Explore":"Explorar", 45 | "Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.", 47 | "Please specify the protocol for":"Por favor, especificar el protocola para", 48 | "Can't read swagger JSON from":"No se puede leer el JSON de swagger desde", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI", 50 | "Unable to read api":"No se puede leer la api", 51 | "from path":"desde ruta", 52 | "server returned":"el servidor retornó" 53 | }); 54 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lang/fr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Avertissement : Obsolète", 6 | "Implementation Notes":"Notes d'implémentation", 7 | "Response Class":"Classe de la réponse", 8 | "Status":"Statut", 9 | "Parameters":"Paramètres", 10 | "Parameter":"Paramètre", 11 | "Value":"Valeur", 12 | "Description":"Description", 13 | "Parameter Type":"Type du paramètre", 14 | "Data Type":"Type de données", 15 | "Response Messages":"Messages de la réponse", 16 | "HTTP Status Code":"Code de statut HTTP", 17 | "Reason":"Raison", 18 | "Response Model":"Modèle de réponse", 19 | "Request URL":"URL appelée", 20 | "Response Body":"Corps de la réponse", 21 | "Response Code":"Code de la réponse", 22 | "Response Headers":"En-têtes de la réponse", 23 | "Hide Response":"Cacher la réponse", 24 | "Headers":"En-têtes", 25 | "Try it out!":"Testez !", 26 | "Show/Hide":"Afficher/Masquer", 27 | "List Operations":"Liste des opérations", 28 | "Expand Operations":"Développer les opérations", 29 | "Raw":"Brut", 30 | "can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut", 31 | "Example Value":"Exemple la valeur", 32 | "Model Schema":"Définition du modèle", 33 | "Model":"Modèle", 34 | "apply":"appliquer", 35 | "Username":"Nom d'utilisateur", 36 | "Password":"Mot de passe", 37 | "Terms of service":"Conditions de service", 38 | "Created by":"Créé par", 39 | "See more at":"Voir plus sur", 40 | "Contact the developer":"Contacter le développeur", 41 | "api version":"version de l'api", 42 | "Response Content Type":"Content Type de la réponse", 43 | "fetching resource":"récupération de la ressource", 44 | "fetching resource list":"récupération de la liste de ressources", 45 | "Explore":"Explorer", 46 | "Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger", 47 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.", 48 | "Please specify the protocol for":"Veuillez spécifier un protocole pour", 49 | "Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de", 50 | "Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI", 51 | "Unable to read api":"Impossible de lire l'api", 52 | "from path":"à partir du chemin", 53 | "server returned":"réponse du serveur" 54 | }); 55 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject ig/swagger-search "0.1.5-SNAPSHOT" 2 | :description "An application that collects and indexes swagger docs from your microservices architecture" 3 | :url "https://github.com/IG-Group/swagger-search" 4 | :license {:name "Apache License 2.0" 5 | :url "http://www.apache.org/licenses/LICENSE-2.0"} 6 | :dependencies [[org.clojure/tools.logging "0.3.1"] 7 | [org.clojure/clojure "1.8.0"] 8 | [zclucy "0.9.2"] 9 | [medley "0.7.0"] 10 | [http.async.client "1.1.0"] 11 | [metosin/compojure-api "1.0.1" :exclusions [org.eclipse.jetty/jetty-server]] 12 | [org.clojure/data.xml "0.0.8"] 13 | [org.clojure/data.zip "0.1.1"] 14 | [org.clojure/tools.namespace "0.2.11"] 15 | [ring/ring-servlet "1.4.0" :exclusions [javax.servlet/servlet-api]] 16 | [ring/ring-core "1.4.0"] 17 | [metosin/ring-swagger-ui "2.1.8-M1"] 18 | [selmer "1.0.4"] 19 | [schejulure "1.0.1"] 20 | [org.tcrawley/dynapath "0.2.4"]] 21 | :profiles {:dev {:dependencies [[ring/ring-mock "0.3.0"] 22 | [midje "1.8.3"]] 23 | :source-paths ["dev"] 24 | :repl-options {:init-ns dev 25 | :init (do 26 | (println "Starting ...") 27 | (go)) 28 | :host "0.0.0.0" 29 | :port 8503}} 30 | :uberjar {:aot :all 31 | :omit-source true 32 | :main com.ig.swagger.search.standalone} 33 | :set-version {:plugins [[lein-set-version "0.4.1"]]} 34 | :not-lib {:dependencies [[ch.qos.logback/logback-classic "1.2.3"] 35 | [org.slf4j/log4j-over-slf4j "1.7.25"] 36 | [org.slf4j/jcl-over-slf4j "1.7.25"] 37 | [ring/ring-jetty-adapter "1.5.0"] 38 | [consul-clojure "0.7.1"] 39 | [etcd-clojure "0.2.4"]] 40 | :resource-paths ["standalone-resources"] 41 | :source-paths ["standalone"]}} 42 | :repositories [["releases" {:url "https://clojars.org/repo" 43 | :sign-releases false 44 | :username :env/clojars_username 45 | :password :env/clojars_password}]]) -------------------------------------------------------------------------------- /examples/api-example-1/src/compojure/api/examples/handler.clj: -------------------------------------------------------------------------------- 1 | (ns compojure.api.examples.handler 2 | (:require [compojure.api.sweet :refer :all] 3 | [ring.util.http-response :refer :all] 4 | [compojure.api.examples.domain :refer :all] 5 | [schema.core :as s] 6 | [compojure.route :as route])) 7 | 8 | (def app 9 | (routes 10 | (api 11 | {:swagger 12 | {:ui "/" 13 | :spec "/swagger.json" 14 | :data {:info {:title "Sample Api" 15 | :description "Compojure Api sample application"} 16 | :tags [{:name "math", :description "math with parameters"} 17 | {:name "echo", :description "request echoes"} 18 | {:name "pizza", :description "pizza Api it is."}]}}} 19 | 20 | (context "/math" [] 21 | :tags ["math"] 22 | 23 | (GET "/plus" [] 24 | :return Total 25 | :query-params [x :- Long, y :- Long] 26 | :summary "x+y with query-parameters" 27 | (ok {:total (+ x y)})) 28 | 29 | (POST "/minus" [] 30 | :return Total 31 | :body-params [x :- Long, y :- Long] 32 | :summary "x-y with body-parameters" 33 | (ok {:total (- x y)})) 34 | 35 | (GET "/times/:x/:y" [] 36 | :return Total 37 | :path-params [x :- Long, y :- Long] 38 | :summary "x*y with path-parameters" 39 | (ok {:total (* x y)})) 40 | 41 | (GET "/power" [] 42 | :return Total 43 | :header-params [x :- Long, y :- Long] 44 | :summary "x^y with header-parameters" 45 | (ok {:total (long (Math/pow x y))}))) 46 | 47 | (context "/echo" [] 48 | :tags ["echo"] 49 | 50 | (GET "/request" req 51 | (ok (dissoc req :body))) 52 | 53 | (GET "/pizza" [] 54 | :return NewSingleToppingPizza 55 | :query [pizza NewSingleToppingPizza] 56 | :summary "get echo of a pizza" 57 | (ok pizza)) 58 | 59 | (PUT "/anonymous" [] 60 | :return [{:secret Boolean s/Keyword s/Any}] 61 | :body [body [{:secret Boolean s/Keyword s/Any}]] 62 | (ok body)) 63 | 64 | (GET "/hello" [] 65 | :return String 66 | :query-params [name :- String] 67 | (ok (str "Hello, " name))) 68 | 69 | (POST "/pizza" [] 70 | :return NewSingleToppingPizza 71 | :body [pizza NewSingleToppingPizza] 72 | :summary "post echo of a pizza" 73 | (ok pizza))) 74 | 75 | (context "/pizzas" [] 76 | :tags ["pizza"] 77 | 78 | (GET "/" [] 79 | :return [Pizza] 80 | :summary "Gets all Pizzas" 81 | (ok (get-pizzas))) 82 | 83 | (POST "/" [] 84 | :return Pizza 85 | :body [pizza NewPizza {:description "new pizza"}] 86 | :summary "Adds a pizza" 87 | (ok (add! pizza))) 88 | 89 | (PUT "/" [] 90 | :return Pizza 91 | :body [pizza Pizza] 92 | :summary "Updates a pizza" 93 | (ok (update! pizza))) 94 | 95 | (GET "/:id" [] 96 | :return Pizza 97 | :path-params [id :- Long] 98 | :summary "Gets a pizza" 99 | (ok (get-pizza id))) 100 | 101 | (DELETE "/:id" [] 102 | :path-params [id :- Long] 103 | :summary "Deletes a Pizza" 104 | (ok (delete! id))))) 105 | (route/not-found "404 Not Found"))) 106 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/routes.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.routes 2 | "Contains all the http endpoints, to be splitted if it becomes too big" 3 | (:require 4 | [ring.util.http-response :refer :all] 5 | [compojure.api.sweet :refer :all] 6 | [compojure.route :as route] 7 | [com.ig.swagger.search.index :as index] 8 | [com.ig.swagger.search.proxy :as proxy] 9 | [clojure.string :as str] 10 | [selmer.parser :refer [render-file]] 11 | [ring.util.response :refer [redirect]]) 12 | (:import (org.apache.lucene.queryparser.classic ParseException) 13 | (org.apache.lucene.index IndexNotFoundException))) 14 | 15 | ;;; 16 | ;;; Http API 17 | ;;; 18 | 19 | (defn sanitize-query [q] 20 | (let [alpha-numeric (str/replace q "/" "\\/") 21 | forward-slash (str/replace alpha-numeric "[^a-zA-Z 0-9]" "")] 22 | forward-slash)) 23 | 24 | (defn get-query-result [index query] 25 | (when query 26 | (merge {:query query} 27 | (try 28 | {:query-results (index/search index (sanitize-query query))} 29 | (catch ParseException _ 30 | {:error "Invalid query submitted"}) 31 | (catch IndexNotFoundException _ 32 | {:error "Index is not available"}))))) 33 | 34 | (def http-api 35 | (routes 36 | (api 37 | (swagger-routes {:ui "/swagger/ui/" 38 | :spec "/swagger/swagger.json" 39 | :data {:info {:title "Swagger Search API" :description "An API for finding swagger APIs across IG services"}} 40 | :tags [{:name "API search", :description "operations for searching for swagger APIs"}]}) 41 | 42 | (GET "/" {context :servlet-context-path} 43 | :no-doc true 44 | (redirect (str context "/search"))) 45 | 46 | (GET "/search" {index :index context :servlet-context-path} 47 | :no-doc true 48 | :query-params [{query :- String nil}] 49 | (ok (render-file "swagger-search.html" (assoc (get-query-result index query) 50 | :servlet-context context 51 | :swagger-services (index/get-swagger-services index))))) 52 | 53 | (GET "/render" {context :servlet-context-path} 54 | :no-doc true 55 | :query-params [swagger-proxy-url :- String] 56 | (ok (render-file "swagger-ui.html" {:spec-url swagger-proxy-url 57 | :proxy-prefix (str context "/api/proxy")}))) 58 | 59 | (GET "/api/search" {index :index} 60 | :tags ["API search"] 61 | :summary "Search for swagger API" 62 | :query-params [query :- String] 63 | (try 64 | (ok (index/search index (sanitize-query query))) 65 | (catch ParseException _ 66 | (bad-request "Invalid query submitted")) 67 | (catch IndexNotFoundException _ 68 | (service-unavailable "Index is not available")))) 69 | 70 | (GET "/parsing-result" {index :index} 71 | :tags ["debug"] 72 | :summary "Returns the error details for a particular service" 73 | :query-params [service-url :- String] 74 | (ok (index/parsing-result-for index service-url))) 75 | 76 | (ANY "/api/proxy" {client :proxy-client :as request} 77 | :no-doc true 78 | :header-params [x-swagger-proxy-url :- String] 79 | (select-keys (proxy/handle-proxy-request client request x-swagger-proxy-url) [:status :body :headers]))) 80 | (route/resources "/") 81 | (route/not-found "

Page not found

"))) 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/http_client/core.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.http-client.core 2 | (:require [http.async.client :as http] 3 | [http.async.client.request :as http-req] 4 | [clojure.tools.logging :refer [debug info]] 5 | [cheshire.core :as json]) 6 | (:import (com.ning.http.client Request))) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | (defn ->json 11 | "Adds appropriate JSON headers and parses the body to a keyword keyed edn" 12 | [handler] 13 | (fn [req] 14 | (let [resp (handler 15 | (-> req 16 | (update-in [:headers] merge {"Accept" "application/json" 17 | "Content-Type" "application/json"}) 18 | (update-in [:body] #(when % (json/generate-string %)))))] 19 | (try 20 | (update-in resp [:body] json/parse-string true) 21 | (catch Exception e (str "Error in parsing the JSON file")))))) 22 | 23 | (defn ->simpler-api 24 | [handler] 25 | (fn 26 | ([req] (handler req)) 27 | ([method url & {:as params}] 28 | (let [request {:method method 29 | :url url 30 | (if (= :get method) :query :body) (dissoc params :q :b :h)} 31 | assoc-if (fn [m src dest] 32 | (if-let [to-assoc (get params src)] 33 | (assoc m dest to-assoc) 34 | m))] 35 | (:body 36 | (handler (-> request 37 | (assoc-if :h :headers) 38 | (assoc-if :q :query) 39 | (assoc-if :b :body)))))))) 40 | 41 | (defn logger [handler] 42 | (fn [request] 43 | (let [response (handler request)] ; http-fn gets executed here 44 | (debug "request=" request ", response=" response) 45 | response))) 46 | 47 | (defn- http-fn [client] 48 | (fn [{:keys [method url] :as req}] 49 | (let [request ^Request (apply http-req/prepare-request method url (apply concat (dissoc req :method :url))) 50 | _ (info (str "requestUrl=" (.getUrl request))) 51 | response (http/await (http-req/execute-request client request))] 52 | (assoc response 53 | :status (http/status response) 54 | :body (http/string response) 55 | :error (http/error response) 56 | :headers (http/headers response))))) 57 | 58 | (defn create-client [{:keys [connection-timeout request-timeout max-connections] :as config}] 59 | {:pre [connection-timeout request-timeout max-connections]} 60 | (let [client (http/create-client 61 | :connection-timeout connection-timeout :request-timeout request-timeout 62 | :max-conns-per-host max-connections :max-conns-total max-connections 63 | :idle-in-pool-timeout 60000)] 64 | (with-meta (logger (http-fn client)) 65 | {:client client}))) 66 | 67 | (defn create [config] 68 | (let [base (create-client (select-keys config [:connection-timeout :request-timeout :max-connections])) 69 | configured base 70 | configured (case (:as config) 71 | :json (->json configured) 72 | configured)] 73 | (with-meta 74 | (->simpler-api configured) 75 | (merge config {:base base})))) 76 | 77 | (defn- base-destroy [client] 78 | (http/close (:client (meta client)))) 79 | 80 | (defn destroy [http-client] 81 | (base-destroy (:base (meta http-client)))) 82 | 83 | (defmacro with-client [[client-name config] & body] 84 | `(let [~client-name (create ~config)] 85 | (try 86 | ~@body 87 | (finally (destroy ~client-name))))) 88 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lib/jquery.ba-bbq.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 3 | * http://benalman.com/projects/jquery-bbq-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); -------------------------------------------------------------------------------- /test/com/ig/swagger/search/indexing_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.indexing-test 2 | (:require [com.ig.swagger.search.parser-test :as test-util] 3 | [com.ig.swagger.search.index :as index]) 4 | (:use 5 | [clojure.test :only [deftest]] 6 | [midje.sweet])) 7 | 8 | 9 | (defn parse-and-index [file] 10 | (let [endpoints (test-util/parse-v2 file)] 11 | (index/build-index endpoints))) 12 | 13 | (let [index (parse-and-index "v2_big_example.json")] 14 | (deftest index-and-search [] 15 | (facts "ui path is not indexed but it is stored" 16 | (index/search* index "post_minus" 10) => [] 17 | 18 | (index/search* index "minus" 10) 19 | => 20 | (just [(contains {:ui-api-path "/math/post_minus"})])) 21 | 22 | (facts "can search on the parameter names" 23 | (index/search* index "parameters:NewSingleToppingPizza" 10) 24 | => 25 | (just [(contains {:path "/pizza" :method "post"})]) 26 | 27 | (index/search* index "NewSingleToppingPizza" 10) 28 | => 29 | (just [(contains {:path "/pizza" :method "post"}) 30 | (contains {:path "/pizza" :method "get"})])) 31 | 32 | (facts "can search on the property names of the params" 33 | 34 | (fact "inlined object definition" 35 | (index/search* index "propertyInsideParamDef" 10) 36 | => 37 | (just [(contains {:path "/minus" :method "post"})])) 38 | 39 | (fact "reference" 40 | (index/search* index "toppings" 10) 41 | => 42 | (contains [(contains {:path "/pizza" :method "post"})])) 43 | 44 | (fact "property within array" 45 | (index/search* index "propertyInsideObjectInsideArray" 10) 46 | => 47 | (contains [(contains {:path "/anonymous" :method "put"})])) 48 | 49 | (fact "property within an object within an object" 50 | (index/search* index "innerInnerField" 10) 51 | => 52 | (just [(contains {:path "/pizzas" :method "post"})]))) 53 | 54 | (facts "can search on the responses" 55 | 56 | (index/search* index "total" 10) 57 | => 58 | (contains [(contains {:path "/minus" :method "post"})]) 59 | 60 | (index/search* index "responses:total" 10) 61 | => 62 | (contains [(contains {:path "/minus" :method "post"})])) 63 | ) 64 | ) 65 | 66 | (let [index (parse-and-index "youtube.json")] 67 | (deftest youtube [] 68 | (facts "can search types" 69 | (index/search* index "types:FanFundingEventSnippet" 10) =not=> [] 70 | (index/search* index "FanFundingEventSnippet" 10) =not=> []) 71 | 72 | (facts "path item parameters object" 73 | (index/search* index "method:parameters" 10) => [] 74 | 75 | (index/search* index "prettyPrint" 10) =not=> [] 76 | 77 | (index/search* index "fan funding events" 10) =not=> [] 78 | 79 | (index/search* index "fan funding event" 10) =not=> [] 80 | 81 | ))) 82 | 83 | 84 | (let [index (parse-and-index "swagger-describing-json-schema.json")] 85 | (deftest schema-in-schema [] 86 | (facts "it works" 87 | (index/search* index "responses:ref" 10) =not=> [] 88 | (index/search* index "types:JsonSchema" 10) =not=> []))) 89 | 90 | (comment 91 | 92 | (youtube) 93 | (index-and-search) 94 | (->> 95 | (index/search* 96 | (parse-and-index "youtube.json") 97 | "types:FanFundingEventSnippet" 10) 98 | ) 99 | 100 | (index/search* 101 | (parse-and-index "youtube.json") 102 | "method:put The part parameter specifies a comma-separated list of commentThread resource properties that " 10) 103 | 104 | (index/search* 105 | (parse-and-index "magento.json") 106 | "catalog-data-category-tree-interface" 10) 107 | 108 | (->> 109 | (index/search* 110 | (parse-and-index "v2_big_example.json") 111 | "innerInnerField" 10) 112 | )) -------------------------------------------------------------------------------- /src/com/ig/swagger/search/index.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.index 2 | (:refer-clojure :exclude [replace]) 3 | (:require [clucy.core :as clucy] 4 | clojure.string 5 | cheshire.generate 6 | medley.core) 7 | (:import org.apache.lucene.analysis.en.EnglishAnalyzer)) 8 | 9 | (def ^{:private true} lucene-keys 10 | (medley.core/map-vals #(assoc % :type "string") 11 | {:method {} 12 | :path {} 13 | :basePath {:indexed false} 14 | :swagger-version {:indexed false} 15 | :servlet-context {:indexed false} 16 | :ui-api-path {:indexed false} 17 | :ui-base-path {:indexed false} 18 | :parameters {:stored false} 19 | :responses {:stored false} 20 | :summary-and-description {:stored false} 21 | :types {:stored false} 22 | :service-name {} 23 | :all-content {:stored false}})) 24 | 25 | (defn- empty-index [] 26 | (clucy.core/memory-index 27 | (assoc lucene-keys 28 | :*doc-with-meta?* false 29 | :*content* false 30 | :_id [:id {:type "string"}]))) 31 | 32 | (defn- details [endpoints] 33 | (set (map #(select-keys % [:swagger-version :servlet-context :ui-base-path :spec-path :service-name :service-version]) endpoints))) 34 | 35 | 36 | (defn- map-stored [map-in] 37 | (merge {} 38 | (filter (complement nil?) 39 | (map (fn [item] 40 | (if (or (= nil (meta map-in)) 41 | (not= false 42 | (:indexed ((first item) (meta map-in))))) 43 | item)) map-in)))) 44 | 45 | (defn- concat-values [map-in] 46 | (apply str (interpose " " (vals (map-stored map-in))))) 47 | 48 | (defn- with-all-content 49 | "Like clucy :_content field, but using only the fields that are indexed. Clucy uses the fields that are stored." 50 | [m] 51 | (assoc m :all-content (concat-values (with-meta m lucene-keys)))) 52 | 53 | (defn- flattern-field [k] 54 | (fn [m] 55 | (update m k 56 | (fn [params] 57 | (clojure.string/join " " (mapcat vals params)))))) 58 | 59 | (defn- types-to-str [m] 60 | (update m :types #(clojure.string/join " " %))) 61 | 62 | (def analyzer (EnglishAnalyzer. clucy.core/*version*)) 63 | 64 | (defn build-index [endpoints] 65 | (binding [clucy.core/*analyzer* analyzer] 66 | (let [index (empty-index)] 67 | (some->> endpoints 68 | (map (flattern-field :parameters)) 69 | (map (flattern-field :responses)) 70 | (map types-to-str) 71 | (map with-all-content) 72 | (apply clucy.core/add index)) 73 | index))) 74 | 75 | (defn build-state [{:keys [index-data all-data]}] 76 | {:index (build-index index-data) 77 | :details (details index-data) 78 | :all-data all-data}) 79 | 80 | (defn create [] 81 | (atom (build-state nil))) 82 | 83 | (defn replace [state collect-result] 84 | (reset! state (build-state collect-result))) 85 | 86 | (defn search* [index query max-results] 87 | (binding [clucy.core/*analyzer* analyzer] 88 | (clucy/search index query max-results :default-field :all-content :default-operator :and))) 89 | 90 | (defn search [state q] 91 | (search* (:index @state) q 100)) 92 | 93 | (defn get-swagger-services [state] 94 | (:details @state)) 95 | 96 | (cheshire.generate/add-encoder java.lang.Throwable 97 | ;; TODO: serialize stacktrace? 98 | (fn [c jsonGenerator] 99 | (.writeString jsonGenerator (str c)))) 100 | 101 | (cheshire.generate/add-encoder clojure.lang.IDeref 102 | (fn [c jsonGenerator] 103 | (.writeString jsonGenerator (str c)))) 104 | 105 | (defn parsing-result-for [state service-url] 106 | (medley.core/find-first (comp (partial = service-url) :service-url) 107 | (:all-data @state))) -------------------------------------------------------------------------------- /resources/swagger-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swagger UI 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 77 | 78 | 79 | 80 | 90 | 91 |
 
92 |
93 |

Swagger Search - Swagger UI

94 |

The application's Swagger UI was not specified or could not be found so we are providing it with one. 95 |
UI does not provide authentication mechanism 96 |

97 |
98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /resources/public/bootstrap-3.3.6/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swagger Search 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/ig/swagger-search.svg)](https://clojars.org/ig/swagger-search) 4 | 5 | Swagger Search is an application that aggregates the [Swagger](https://swagger.io/)/[OpenAPI](https://www.openapis.org/) 6 | documentation of your microservice architecture in one place, where your can browse it or search endpoints based on 7 | their url, documentation, the parameters it accepts or their types. 8 | 9 | Swagger Search also provides a Swagger UI for those microservices that haven't their own Swagger UI embedded. 10 | 11 | ## Features 12 | 13 | #### Service discovery 14 | 15 | Integrated with [Etcd](https://github.com/coreos/etcd) and [Consul](https://www.consul.io/) for service discovery, or 16 | your can plug your own. 17 | 18 | #### Search 19 | 20 | The search is powered by [Lucene](https://lucene.apache.org/core/), so you can use the [Lucene's query syntax](http://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Terms). 21 | An English analyzer is used. 22 | 23 | The available fields are: 24 | 25 | 1. service-name: the name specified on the info->title of the swagger doc or the basePath 26 | 1. path: of the endpoint 27 | 1. method: GET/POST/... 28 | 1. summary-and-description 29 | 1. parameters: name of the query/form/path/header parameter, or in the case of a body param, the name of any of the properties of the object or nested objects. 30 | 1. responses: the name of any of the properties of the object or nested objects of any of the responses 31 | 1. types: name of any $ref in parameters or responses 32 | 33 | If no field is specified, all of them will be used. 34 | 35 | #### Swagger UI and proxy 36 | 37 | For each service, Swagger Search will try to find if that service comes with its own Swagger UI. 38 | 39 | If it does, Swagger Search will use that UI when you click on a search result for that particular service. 40 | 41 | If it does not, Swagger Search will use its own Swagger UI and a proxy to forward the requests. 42 | 43 | Note that this proxy is very naive, so do not expose it outside your trusted network as it does not do any validations. 44 | 45 | ## Run it 46 | 47 | First you need to create a configuration file. Look at the [example](examples/swagger.config.edn), which contains all the 48 | configuration options. 49 | 50 | #### Docker 51 | 52 | Copy or make the swagger.config.edn avaible in */config* inside the container, something like: 53 | 54 | ``` 55 | docker run -p 7878:3000 -v `pwd`/examples:/config danlebrero/swagger-search 56 | ``` 57 | 58 | Open a browser at http://localhost:7878/ 59 | 60 | See [DockerHub](https://hub.docker.com/r/danlebrero/swagger-search/) for the latest version. 61 | 62 | #### Uberjar 63 | 64 | Instead of Docker, you can just run the application manually as a JVM application: 65 | 66 | 1. Install Java 8 67 | 1. Download the [latest release jar](https://github.com/IG-Group/swagger-search/releases) 68 | 1. Specify the *SWAGGER_HOME* directory as a environment variable 69 | 1. Place the configuration file in the *SWAGGER_HOME* directory. It must be called *swagger.config.edn* 70 | 1. Run: 71 | ``` 72 | java -jar swaggersearch-vXXXX.jar 73 | ``` 74 | 75 | Open a browser at http://localhost:$whatever-port-you-specified-in-the-config-file/ 76 | 77 | ## Custom service discovery 78 | 79 | Swagger Search needs to find out what services you are running. The built-in options are: 80 | 81 | 1. Hardcoded list 82 | 1. Read a local file 83 | 1. Read a url 84 | 1. Etcd 85 | 1. Consul 86 | 87 | The configuration example file has more details about each of them. 88 | 89 | It is quite possible that none of these suits you, in which case next are the possible options to user your own 90 | service discovery 91 | 92 | #### I dont know Clojure! 93 | 94 | Your best option is to learn Clojure. It will do you a lot of good :stuck_out_tongue_winking_eye:. 95 | 96 | Failing that, use the *:uri-or-file* provider and have a process that updates the file or write some HTTP server that is 97 | able to return the list of services. 98 | 99 | #### Plugin a new provider 100 | 101 | To create your own service discovery mechanism: 102 | 103 | 1. Create a function like the [Etcd one](src/com/ig/swagger/search/discovery/providers/etcd.clj) 104 | 1. Package it 105 | 1. Either add the package to the classpath or copy it to $SWAGGER_HOME/libs 106 | 1. Add any additional jar dependencies to the classpath or to $SWAGGER_HOME/libs 107 | 108 | #### Swagger Search as a library 109 | 110 | If you don't want to run Docker or Uberjar, Swagger Search is also available as a library 111 | in [Clojars](https://clojars.org/ig/swagger-search). 112 | 113 | See the [uberjar code](standalone/com/ig/swagger/search/standalone.clj) as guide about how you can use it as a library. 114 | 115 | ## Known limitations 116 | 117 | 1. No support for OpenAPI 3.0. 118 | 1. Swagger 1.2 and below just indexes the url. 119 | 1. Index is stored in memory. 120 | 121 | ## Development 122 | 123 | Go to examples/etcd and run: 124 | 125 | ``` 126 | docker-compose up 127 | ``` 128 | 129 | This should start a whole developer environment, including an application with a sample Swagger API. 130 | 131 | Swagger Search will be available at [http://localhost:8504/](http://localhost:8504/) and a Clojure REPL at localhost:8503 132 | 133 | ## License 134 | 135 | Copyright © 2017 IG Group 136 | 137 | Distributed under Apache Licence 2.0. -------------------------------------------------------------------------------- /resources/public/swagger-ui/css/style.css: -------------------------------------------------------------------------------- 1 | .swagger-section #header a#logo { 2 | font-size: 1.5em; 3 | font-weight: bold; 4 | text-decoration: none; 5 | background: transparent url(../images/logo.png) no-repeat left center; 6 | padding: 20px 0 20px 40px; 7 | } 8 | #text-head { 9 | font-size: 80px; 10 | font-family: 'Roboto', sans-serif; 11 | color: #ffffff; 12 | float: right; 13 | margin-right: 20%; 14 | } 15 | .navbar-fixed-top .navbar-nav { 16 | height: auto; 17 | } 18 | .navbar-fixed-top .navbar-brand { 19 | height: auto; 20 | } 21 | .navbar-header { 22 | height: auto; 23 | } 24 | .navbar-inverse { 25 | background-color: #000; 26 | border-color: #000; 27 | } 28 | #navbar-brand { 29 | margin-left: 20%; 30 | } 31 | .navtext { 32 | font-size: 10px; 33 | } 34 | .h1, 35 | h1 { 36 | font-size: 60px; 37 | } 38 | .navbar-default .navbar-header .navbar-brand { 39 | color: #a2dfee; 40 | } 41 | /* tag titles */ 42 | .swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { 43 | color: #393939; 44 | font-family: 'Arvo', serif; 45 | font-size: 1.5em; 46 | } 47 | .swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { 48 | color: black; 49 | } 50 | .swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { 51 | color: #525252; 52 | padding-left: 0px; 53 | display: block; 54 | clear: none; 55 | float: left; 56 | font-family: 'Arvo', serif; 57 | font-weight: bold; 58 | } 59 | .navbar-default .navbar-collapse, 60 | .navbar-default .navbar-form { 61 | border-color: #0A0A0A; 62 | } 63 | .container1 { 64 | width: 1500px; 65 | margin: auto; 66 | margin-top: 0; 67 | background-image: url('../images/shield.png'); 68 | background-repeat: no-repeat; 69 | background-position: -40px -20px; 70 | margin-bottom: 210px; 71 | } 72 | .container-inner { 73 | width: 1200px; 74 | margin: auto; 75 | background-color: rgba(223, 227, 228, 0.75); 76 | padding-bottom: 40px; 77 | padding-top: 40px; 78 | border-radius: 15px; 79 | } 80 | .header-content { 81 | padding: 0; 82 | width: 1000px; 83 | } 84 | .title1 { 85 | font-size: 80px; 86 | font-family: 'Vollkorn', serif; 87 | color: #404040; 88 | text-align: center; 89 | padding-top: 40px; 90 | padding-bottom: 100px; 91 | } 92 | #icon { 93 | margin-top: -18px; 94 | } 95 | .subtext { 96 | font-size: 25px; 97 | font-style: italic; 98 | color: #08b; 99 | text-align: right; 100 | padding-right: 250px; 101 | } 102 | .bg-primary { 103 | background-color: #00468b; 104 | } 105 | .navbar-default .nav > li > a, 106 | .navbar-default .nav > li > a:focus { 107 | color: #08b; 108 | } 109 | .navbar-default .nav > li > a, 110 | .navbar-default .nav > li > a:hover { 111 | color: #08b; 112 | } 113 | .navbar-default .nav > li > a, 114 | .navbar-default .nav > li > a:focus:hover { 115 | color: #08b; 116 | } 117 | .text-faded { 118 | font-size: 25px; 119 | font-family: 'Vollkorn', serif; 120 | } 121 | .section-heading { 122 | font-family: 'Vollkorn', serif; 123 | font-size: 45px; 124 | padding-bottom: 10px; 125 | } 126 | hr { 127 | border-color: #00468b; 128 | padding-bottom: 10px; 129 | } 130 | .description { 131 | margin-top: 20px; 132 | padding-bottom: 200px; 133 | } 134 | .description li { 135 | font-family: 'Vollkorn', serif; 136 | font-size: 25px; 137 | color: #525252; 138 | margin-left: 28%; 139 | padding-top: 5px; 140 | } 141 | .gap { 142 | margin-top: 200px; 143 | } 144 | .troubleshootingtext { 145 | color: rgba(255, 255, 255, 0.7); 146 | padding-left: 30%; 147 | } 148 | .troubleshootingtext li { 149 | list-style-type: circle; 150 | font-size: 25px; 151 | padding-bottom: 5px; 152 | } 153 | .overlay { 154 | position: absolute; 155 | top: 0; 156 | left: 0; 157 | width: 100%; 158 | height: 100%; 159 | z-index: 1000; 160 | } 161 | .block.response_body.json:hover { 162 | cursor: pointer; 163 | } 164 | .backdrop { 165 | color: blue; 166 | } 167 | #myModal { 168 | height: 100%; 169 | } 170 | .modal-backdrop { 171 | bottom: 0; 172 | position: fixed; 173 | } 174 | .curl { 175 | padding: 10px; 176 | font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; 177 | font-size: 0.9em; 178 | max-height: 400px; 179 | margin-top: 5px; 180 | overflow-y: auto; 181 | background-color: #fcf6db; 182 | border: 1px solid #e5e0c6; 183 | border-radius: 4px; 184 | } 185 | .curl_title { 186 | font-size: 1.1em; 187 | margin: 0; 188 | padding: 15px 0 5px; 189 | font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif; 190 | font-weight: 500; 191 | line-height: 1.1; 192 | } 193 | .footer { 194 | display: none; 195 | } 196 | .swagger-section .swagger-ui-wrap h2 { 197 | padding: 0; 198 | } 199 | h2 { 200 | margin: 0; 201 | margin-bottom: 5px; 202 | } 203 | .markdown p { 204 | font-size: 15px; 205 | font-family: 'Arvo', serif; 206 | } 207 | .swagger-section .swagger-ui-wrap .code { 208 | font-size: 15px; 209 | font-family: 'Arvo', serif; 210 | } 211 | .swagger-section .swagger-ui-wrap b { 212 | font-family: 'Arvo', serif; 213 | } 214 | #signin:hover { 215 | cursor: pointer; 216 | } 217 | .dropdown-menu { 218 | padding: 15px; 219 | } 220 | .navbar-right .dropdown-menu { 221 | left: 0; 222 | right: auto; 223 | } 224 | #signinbutton { 225 | width: 100%; 226 | height: 32px; 227 | font-size: 13px; 228 | font-weight: bold; 229 | color: #08b; 230 | } 231 | .navbar-default .nav > li .details { 232 | color: #000000; 233 | text-transform: none; 234 | font-size: 15px; 235 | font-weight: normal; 236 | font-family: 'Open Sans', sans-serif; 237 | font-style: italic; 238 | line-height: 20px; 239 | top: -2px; 240 | } 241 | .navbar-default .nav > li .details:hover { 242 | color: black; 243 | } 244 | #signout { 245 | width: 100%; 246 | height: 32px; 247 | font-size: 13px; 248 | font-weight: bold; 249 | color: #08b; 250 | } 251 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/parser.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.parser 2 | (:use [clojure.tools.logging :only [error info]]) 3 | (:require [clojure.set] 4 | clojure.walk 5 | medley.core 6 | [ring.util.codec :as codec] 7 | [clojure.string :as string] 8 | [clojure.string :as str])) 9 | 10 | (defn encode-ui-path 11 | [path operation-id] 12 | (str "/" 13 | (codec/url-encode (if (string/starts-with? path "/") 14 | (.substring path 1) 15 | path)) 16 | "/" 17 | (-> operation-id 18 | (string/replace #"[/-]" "_") 19 | (string/replace #"[\{\}]" "")))) 20 | 21 | (defn base-path-to-service-name [path] 22 | (when path 23 | (str/capitalize (str/replace path "/" "")))) 24 | 25 | ;;; 26 | ;;; V2 parsing 27 | ;;; 28 | 29 | (defn stringify [key] 30 | (if key (name key))) 31 | 32 | (defn- ref->keyword-path [ref] 33 | (when (string? ref) 34 | (map keyword (rest (string/split ref #"/"))))) 35 | 36 | (defn fields [schema] 37 | (cond 38 | (= "object" (:type schema)) (vec (concat (keys (:properties schema)) 39 | [(fields (:additionalProperties schema))] 40 | (mapcat fields (vals (:properties schema))))) 41 | (= "array" (:type schema)) (fields (:items schema)) 42 | :default nil)) 43 | 44 | (defn- param-data [param] 45 | (let [schema (:schema param)] 46 | (assoc 47 | (select-keys param [:name :description]) 48 | :field-names 49 | (fields schema)))) 50 | 51 | (defn find-types [param] 52 | (let [schema (:schema param param) 53 | param-type (:type schema) 54 | type (if (and (:$ref schema) 55 | (= "object" param-type)) 56 | (stringify (last (ref->keyword-path (:$ref schema)))))] 57 | (cons type 58 | (cond 59 | (= "object" param-type) (vec (mapcat find-types (cons 60 | (:additionalProperties schema) 61 | (vals (:properties schema))))) 62 | (= "array" param-type) (find-types (:items schema)) 63 | :default nil)))) 64 | 65 | (defn get-controller-data 66 | [global-params path [method operation]] 67 | (let [api-path (encode-ui-path (or (first (:tags operation)) "default") 68 | (or (:operationId operation) 69 | (str (name method) "_" (name path))))] 70 | {:method (name method) 71 | :summary-and-description (str (:summary operation) (:description operation)) 72 | :parameters (mapv param-data (concat global-params (:parameters operation))) 73 | :responses (mapv param-data (vals (:responses operation))) 74 | :types (vec (distinct (filter some? (mapcat find-types (concat global-params (:parameters operation) (vals (:responses operation))))))) 75 | :ui-api-path api-path})) 76 | 77 | (defn get-controller-methods [[path path-item]] 78 | (let [path-methods (dissoc path-item :parameters) 79 | global-params (:parameters path-item)] 80 | (mapv (comp 81 | (fn [index-data] (assoc index-data :path (str "/" (stringify path)))) 82 | (partial get-controller-data global-params path)) path-methods))) 83 | 84 | (defn get-controller-paths [swagger-paths] 85 | (vec (mapcat get-controller-methods swagger-paths))) 86 | 87 | (defn- find-ref [swagger-doc ref] 88 | (let [path (ref->keyword-path ref)] 89 | (get-in swagger-doc path))) 90 | 91 | (defn resolve-refs 92 | ([swagger-doc] (resolve-refs swagger-doc #{} swagger-doc)) 93 | ([swagger-doc visited-refs form] 94 | (if (and (map? form) 95 | (:$ref form) 96 | (not (visited-refs (:$ref form)))) 97 | (let [next-level (partial resolve-refs swagger-doc (conj visited-refs (:$ref form))) 98 | resolved-ref (merge form 99 | (find-ref swagger-doc (:$ref form)))] 100 | (medley.core/map-vals next-level resolved-ref)) 101 | (let [next-level (partial resolve-refs swagger-doc visited-refs)] 102 | (cond 103 | (list? form) (apply list (map next-level form)) 104 | (instance? clojure.lang.IMapEntry form) (vec (map next-level form)) 105 | (seq? form) (doall (map next-level form)) 106 | (coll? form) (into (empty form) (map next-level form)) 107 | :else form))))) 108 | 109 | 110 | (defn index-data-for-v2 [{:keys [swagger-doc]}] 111 | (let [swagger-doc (resolve-refs swagger-doc) 112 | {:keys [paths swagger]} swagger-doc 113 | more-index-data (fn [controller-path] 114 | (assoc controller-path 115 | :servlet-context (:basePath swagger-doc) 116 | :service-name (or (-> swagger-doc :info :title) 117 | (base-path-to-service-name (:basePath swagger-doc))) 118 | :service-version (-> swagger-doc :info :version) 119 | :swagger-version swagger))] 120 | {:index-data (mapv more-index-data (get-controller-paths paths))})) 121 | 122 | ;;; 123 | ;;; V1 124 | ;;; 125 | 126 | (defn parse-swagger-controller [{:keys [basePath apis swaggerVersion resourcePath]}] 127 | (let [build-controller-fn (fn [controller-api operation] 128 | (assoc (select-keys operation [:method :summary]) 129 | :path (:path controller-api) 130 | :servlet-context basePath 131 | :service-name (base-path-to-service-name basePath) ;; TODO: find if v1 has a service name 132 | :swagger-version swaggerVersion 133 | :ui-api-path (encode-ui-path resourcePath ;; this does not really work. we need a better solution (something in the UI?) to get the operation expanded 134 | (:nickname operation (:description controller-api)))))] 135 | (mapcat (fn [controller-api] 136 | (map (partial build-controller-fn controller-api) (:operations controller-api))) 137 | apis))) 138 | 139 | (defn index-data-for-v1 [{:keys [v1-api-docs]}] 140 | (let [controllers (vals v1-api-docs)] 141 | {:index-data 142 | (mapcat parse-swagger-controller controllers)})) -------------------------------------------------------------------------------- /test/com/ig/swagger/search/parser_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.parser-test 2 | (:require [com.ig.swagger.search.parser :as parser] 3 | [cheshire.core :as json]) 4 | (:use 5 | [clojure.test :only [deftest]] 6 | [midje.sweet])) 7 | 8 | (def file-dir "test/test_data/") 9 | 10 | (defn get-json-document [file-name] 11 | (json/parse-string (slurp (clojure.java.io/file (str file-dir file-name))) true)) 12 | 13 | (defn parse-v2 [file] 14 | (:index-data (parser/index-data-for-v2 {:swagger-doc (get-json-document file)}))) 15 | 16 | (defn parse-v1 [file] 17 | (:index-data (parser/index-data-for-v1 {:v1-api-docs {"any" (get-json-document file)}}))) 18 | 19 | (deftest parse-swagger-response-v2 20 | (facts "parsing the swagger v2 response produces a result ready for lucene" 21 | (parse-v2 "parser_v2_working.json") => 22 | (just [(contains {:ui-api-path "/controller/operationId" 23 | :path "/ping/abc" 24 | :swagger-version "2.0" 25 | :summary-and-description "ping pong" 26 | :method "GET" 27 | :service-version nil 28 | :service-name "A-tomcat-service" 29 | :servlet-context "/a-tomcat-service"}) 30 | (contains {:ui-api-path "/controller/operationId" 31 | :path "/search" 32 | :swagger-version "2.0" 33 | :summary-and-description "Search for API" 34 | :method "GET" 35 | :service-version nil 36 | :service-name "A-tomcat-service" 37 | :servlet-context "/a-tomcat-service"})] 38 | :in-any-order))) 39 | 40 | (deftest parse-swagger-response-v2-api-path 41 | (facts "parsing the swagger v2 response without operationId to make sure the api-path is still correct" 42 | (parse-v2 "parser_v2_no_operationId.json") => 43 | (just [(contains {:ui-api-path "/controller/GET_ping_abc" 44 | :path "/ping/abc" 45 | :swagger-version "2.0" 46 | :summary-and-description "ping pong" 47 | :method "GET" 48 | :service-version nil 49 | :service-name "A-tomcat-service" 50 | :servlet-context "/a-tomcat-service"}) 51 | (contains {:ui-api-path "/controller/GET_search" 52 | :path "/search" 53 | :swagger-version "2.0" 54 | :summary-and-description "Search for API" 55 | :method "GET" 56 | :service-version nil 57 | :service-name "A-tomcat-service" 58 | :servlet-context "/a-tomcat-service"})] 59 | :in-any-order))) 60 | 61 | (deftest parse-swagger-response-v2-api-path 62 | (facts "parsing the swagger v2 response with only path and method" 63 | (parse-v2 "parser_v2_minimum_fields.json") => 64 | (just [(contains {:path "/ping/abc" 65 | :method "GET" 66 | :service-name nil, 67 | :service-version nil, 68 | :swagger-version "2.0" 69 | :servlet-context nil 70 | :summary-and-description "" 71 | :responses [] 72 | :parameters [] 73 | :ui-api-path "/default/GET_ping_abc"})] 74 | :in-any-order))) 75 | 76 | (deftest parse-swagger-response-v1 77 | (facts "parsing the swagger v1 response produces a result ready for lucene" 78 | (parse-v1 "parser_v1_working.json") => 79 | (just [{:path "/ping/abc" 80 | :swagger-version "1.2" 81 | :summary "ping pong" 82 | :method "GET" 83 | :ui-api-path "/somebasepath/operationPing" 84 | :service-name "A-tomcat-service", 85 | :servlet-context "/a-tomcat-service"} 86 | {:path "/search" 87 | :swagger-version "1.2" 88 | :summary "Search for API" 89 | :method "GET" 90 | :ui-api-path "/somebasepath/operationSearch" 91 | :service-name "A-tomcat-service", 92 | :servlet-context "/a-tomcat-service"}] 93 | :in-any-order))) 94 | 95 | (deftest circular-refs 96 | (facts "works" 97 | (-> 98 | {:definitions 99 | {:a 100 | {:type "object", 101 | :description "a", 102 | :properties {:child {:$ref "#/definitions/b"}}} 103 | :b 104 | {:type "object", 105 | :properties {:child {:$ref "#/definitions/c"}}} 106 | :c 107 | {:type "object", 108 | :properties {:child {:$ref "#/definitions/a"}}}}} 109 | parser/resolve-refs 110 | (get-in [:definitions :a 111 | :properties :child 112 | :properties :child 113 | :properties :child 114 | :description])) 115 | => "a" 116 | 117 | (-> 118 | {:definitions 119 | {:a 120 | {:type "object", 121 | :description "a", 122 | :properties {:id {:type "integer"}, 123 | :name {:type "string", :description "Category name"}, 124 | :children {:type "array", 125 | :items {:$ref "#/definitions/b"}} 126 | :parent {:$ref "#/definitions/a"}}} 127 | :b 128 | {:type "object", 129 | :description "b", 130 | :properties {:id {:type "integer"}, 131 | :name {:type "string", :description "the other name"}, 132 | :children {:type "array", 133 | :items {:$ref "#/definitions/a"}}}}}} 134 | parser/resolve-refs 135 | (get-in [:definitions :a 136 | :properties :children :items 137 | :properties :children :items 138 | :description 139 | ])) 140 | => "a" 141 | )) 142 | -------------------------------------------------------------------------------- /src/com/ig/swagger/search/collector.clj: -------------------------------------------------------------------------------- 1 | (ns com.ig.swagger.search.collector 2 | (:require [com.ig.swagger.search.parser :as parser] 3 | [schejulure.core :as schedule] 4 | [com.ig.swagger.search.http-client.core :as http]) 5 | (:use [clojure.tools.logging :only [info warn error]])) 6 | 7 | (comment 8 | 9 | {:service-url "http://bip1.test.iggroup.local/bonusmanagement" 10 | :possible-swagger-doc-suffix ["/v2/api-docs/" "/swagger/swagger.json" "/api-docs"] 11 | :possible-swagger-ui-suffix ["/swagger/ui/index.html" "/swagger-ui.html" "/swagger/index.html" "/swagger/ui/swagger-ui.html"] 12 | :swagger-doc-url "http://bip1.test.iggroup.local/bonusmanagement/api-docs" 13 | :swagger-ui-url "http://bip1.test.iggroup.local/bonusmanagement/swagger/index.html" 14 | :swagger-doc {,,,,} 15 | :v1-api-docs {"url" {,,,}} 16 | :index-data [{:path "/search" 17 | :spec-path "http://ip1/a-tomcat-service/api-docs" ;; swagger-doc-url 18 | :swagger-version "1.2" 19 | :service-name "SwaggerSearch" 20 | :service-version "0.0.1" 21 | :summary-and-description "Search for API" 22 | :method "GET" 23 | :ui-api-path "http://ip1/a-tomcat-service/swagger/index.html#!/abc/operationSearch" 24 | :ui-base-path "http://ip1/a-tomcat-service/swagger/index.html#!" ;; swagger-ui-url 25 | :servlet-context "/a-tomcat-service"}] 26 | :error ["..."]}) 27 | 28 | ;;; 29 | ;;; general 30 | ;;; 31 | 32 | (defn ok? [result] 33 | (and (not (:error result)) 34 | (= 200 (-> result :status :code)))) 35 | 36 | (defn do-multiple-requests 37 | [http-client {:keys [base-url suffixes]}] 38 | (let [responses (map #(http-client {:method :get :url (str base-url %)}) suffixes)] 39 | [(filter ok? responses) (remove ok? responses)])) 40 | 41 | (defn first-ok [responses] 42 | (if-let [first-ok (ffirst responses)] 43 | (select-keys first-ok [:body :url]) 44 | {:error [{:type :http 45 | :context (fnext responses)}]})) 46 | 47 | ;;; 48 | ;;; Swagger docs 49 | ;;; 50 | 51 | (defn find-swagger-docs [http-client {:keys [service-url possible-swagger-doc-suffix]}] 52 | (-> (do-multiple-requests http-client {:base-url service-url 53 | :suffixes possible-swagger-doc-suffix}) 54 | first-ok 55 | (clojure.set/rename-keys {:body :swagger-doc 56 | :url :swagger-doc-url}))) 57 | 58 | (defn collect-additional-controllers-for-v1 [http-client {:keys [swagger-doc-url swagger-doc]}] 59 | (let [controllers (keep :path (:apis swagger-doc)) 60 | [docs errors] (do-multiple-requests http-client {:base-url swagger-doc-url 61 | :suffixes controllers})] 62 | (if (seq errors) 63 | {:error errors} 64 | {:v1-api-docs (zipmap controllers 65 | (map :body docs))}))) 66 | 67 | ;;; 68 | ;;; Swagger UI related data 69 | ;;; 70 | 71 | (defn find-swagger-ui [http-client {:keys [service-url possible-swagger-ui-suffix]}] 72 | (-> (do-multiple-requests http-client {:base-url service-url 73 | :suffixes possible-swagger-ui-suffix}) 74 | first-ok 75 | (dissoc :body) 76 | (clojure.set/rename-keys {:url :swagger-ui-url 77 | :error :swagger-ui-error}))) 78 | 79 | (defn attach-ui [{:keys [swagger-ui-url swagger-doc-url index-data]}] 80 | (if swagger-ui-url 81 | {:index-data (map (fn [data] 82 | (assoc data 83 | :spec-path swagger-doc-url 84 | :ui-base-path (str swagger-ui-url "#!") ;; TODO: move all this logic to FE?? 85 | :ui-api-path (str swagger-ui-url "#!" (:ui-api-path data)))) 86 | index-data)} 87 | {:index-data (map (fn [data] 88 | (assoc data 89 | :spec-path swagger-doc-url 90 | :ui-base-path (str "render?swagger-proxy-url=" swagger-doc-url "#!") ;; TODO: move all this logic to FE?? 91 | :ui-api-path (str "render?swagger-proxy-url=" swagger-doc-url "#!" (:ui-api-path data)))) 92 | index-data)})) 93 | 94 | ;;; 95 | ;;; Gluing everything togheter 96 | ;;; 97 | (defn preserve-service-url-in-case-of-exception [f data] 98 | (try 99 | (f data) 100 | (catch Throwable e 101 | (throw (Exception. 102 | (str "Unexpected exception " (:service-url data)) 103 | e))))) 104 | 105 | 106 | (defn pipe [& fns] 107 | (fn [data] 108 | (reduce (fn [data f] 109 | (let [new-data (merge data 110 | (preserve-service-url-in-case-of-exception f data))] 111 | (if (:error new-data) 112 | (reduced new-data) 113 | new-data))) 114 | data 115 | fns))) 116 | 117 | 118 | (defn if-version [f-v1 f-v2] 119 | (fn [{swagger-doc :swagger-doc :as data}] 120 | (if (= 2 (some-> swagger-doc :swagger Double/parseDouble Math/floor int)) 121 | (f-v2 data) 122 | (f-v1 data)))) 123 | 124 | (defn collect-swagger-data [json-client http-client pipeline-config services] 125 | (let [pipeline (pipe 126 | (partial find-swagger-docs json-client) 127 | (if-version (partial collect-additional-controllers-for-v1 json-client) 128 | (constantly nil)) 129 | (if-version parser/index-data-for-v1 130 | parser/index-data-for-v2) 131 | (partial find-swagger-ui http-client) 132 | attach-ui)] 133 | (pmap (comp pipeline 134 | #(assoc pipeline-config :service-url %)) 135 | services))) 136 | 137 | (defn glue [json-client http-client pipeline-config services] 138 | (let [swagger-data (collect-swagger-data json-client http-client pipeline-config services)] 139 | {:index-data (mapcat :index-data (remove :error swagger-data)) 140 | :all-data swagger-data})) 141 | 142 | (defn log-unhandled-exceptions [f] 143 | (fn [& args] 144 | (try 145 | (apply f args) 146 | (catch Exception e (error e))))) ;; TODO: make error handling configurable 147 | 148 | (defn schedule-indexing [fetching-function {:keys [http-config] :as config} indexer] 149 | (let [json-client (http/create (assoc http-config :as :json)) 150 | http-client (http/create http-config) 151 | pipeline-config (select-keys config [:possible-swagger-doc-suffix :possible-swagger-ui-suffix]) 152 | collect-data (partial glue json-client http-client pipeline-config) 153 | run-indexing (log-unhandled-exceptions (comp indexer collect-data fetching-function))] 154 | {:json-client json-client 155 | :http-client http-client 156 | :initial-load-task (future (run-indexing)) 157 | :index-fn run-indexing 158 | :scheduled-task (schedule/schedule ;; TODO: make schedule configurable 159 | {:minute (range 0 60 15)} run-indexing)})) 160 | 161 | (defn destroy-component [{:keys [scheduled-task json-client http-client initial-load-task]}] 162 | (when scheduled-task 163 | (future-cancel scheduled-task) 164 | (future-cancel initial-load-task) 165 | (http/destroy json-client) 166 | (http/destroy http-client))) -------------------------------------------------------------------------------- /resources/public/swagger-ui/lib/highlight.9.1.0.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.1.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){"undefined"!=typeof exports?e(exports):(self.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return self.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return E(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(E(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else"start"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(M);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(M);r;){e+=n(M.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(M)}return e+n(M.substr(t))}function d(){var e="string"==typeof L.sL;if(e&&!R[L.sL])return n(M);var t=e?l(L.sL,M,!0,y[L.sL]):f(M,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(y[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,M=""):e.eB?(k+=n(t)+r,M=""):(k+=r,M=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(M+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(M+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),M="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return M+=t,t.length||1}var N=E(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,L=i||N,y={},k="";for(w=L;w!=N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var M="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),w=L;w.parent;w=w.parent)w.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function f(e,t){t=t||x.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(E(n)){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return x.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,x.tabReplace)})),x.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?w[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;x.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?l(n,r,!0):f(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){x=o(x,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){w[e]=n})}function N(){return Object.keys(R)}function E(e){return e=(e||"").toLowerCase(),R[e]||R[w[e]]}var x={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},R={},w={};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=E,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:i,k:t},s={b:"{",e:"}",c:[{cN:"attr",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:r}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return i.splice(i.length,0,s,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("xml",function(s){var t="[A-Za-z0-9\\._:-]+",e={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php"},r={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[r],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},e,{cN:"meta",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},r]}]}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\s*\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}}); -------------------------------------------------------------------------------- /resources/swagger-search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swagger Search 6 | 7 | 8 | 9 | {% style "/bootstrap-3.3.6/css/bootstrap.min.css" %} 10 | {% style "/css/swagger-search.css" %} 11 | {% script "/js/jquery-1.12.3.min.js" %} 12 | {% script "/js/list.min.js" %} 13 | {% script "/bootstrap-3.3.6/js/bootstrap.min.js" %} 14 | 15 | 16 | 24 | 25 |
26 |
27 |
28 |
29 |
30 | 37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 |
47 |
48 |
49 |
50 |
51 | 53 |
54 |
55 |
56 |
    57 | {% for service in swagger-services|sort-by:service-name %} 58 |
  • 59 | 66 |
  • 67 | {% endfor %} 68 |
69 |
70 |
71 | 72 | 73 | 74 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | 118 |
119 |
120 |

Rendering...

121 |
122 |
123 |
124 |
125 | http:// 126 | 128 | 129 | 133 | 134 |
135 |
136 |
137 | 138 |
139 |
140 |
141 | 142 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 IG Group 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /resources/public/swagger-ui/lib/swagger-oauth.js: -------------------------------------------------------------------------------- 1 | var appName; 2 | var popupMask; 3 | var popupDialog; 4 | var clientId; 5 | var realm; 6 | var redirect_uri; 7 | var clientSecret; 8 | var scopeSeparator; 9 | var additionalQueryStringParams; 10 | 11 | function handleLogin() { 12 | var scopes = []; 13 | 14 | var auths = window.swaggerUi.api.authSchemes || window.swaggerUi.api.securityDefinitions; 15 | if(auths) { 16 | var key; 17 | var defs = auths; 18 | for(key in defs) { 19 | var auth = defs[key]; 20 | if(auth.type === 'oauth2' && auth.scopes) { 21 | var scope; 22 | if(Array.isArray(auth.scopes)) { 23 | // 1.2 support 24 | var i; 25 | for(i = 0; i < auth.scopes.length; i++) { 26 | scopes.push(auth.scopes[i]); 27 | } 28 | } 29 | else { 30 | // 2.0 support 31 | for(scope in auth.scopes) { 32 | scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key}); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | if(window.swaggerUi.api 40 | && window.swaggerUi.api.info) { 41 | appName = window.swaggerUi.api.info.title; 42 | } 43 | 44 | $('.api-popup-dialog').remove(); 45 | popupDialog = $( 46 | [ 47 | '
', 48 | '
Select OAuth2.0 Scopes
', 49 | '
', 50 | '

Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.', 51 | 'Learn how to use', 52 | '

', 53 | '

' + appName + ' API requires the following scopes. Select which ones you want to grant to Swagger UI.

', 54 | '
    ', 55 | '
', 56 | '

', 57 | '
', 58 | '
', 59 | '
'].join('')); 60 | $(document.body).append(popupDialog); 61 | 62 | //TODO: only display applicable scopes (will need to pass them into handleLogin) 63 | popup = popupDialog.find('ul.api-popup-scopes').empty(); 64 | for (i = 0; i < scopes.length; i ++) { 65 | scope = scopes[i]; 66 | str = '
  • ' + '
  • '; 74 | popup.append(str); 75 | } 76 | 77 | var $win = $(window), 78 | dw = $win.width(), 79 | dh = $win.height(), 80 | st = $win.scrollTop(), 81 | dlgWd = popupDialog.outerWidth(), 82 | dlgHt = popupDialog.outerHeight(), 83 | top = (dh -dlgHt)/2 + st, 84 | left = (dw - dlgWd)/2; 85 | 86 | popupDialog.css({ 87 | top: (top < 0? 0 : top) + 'px', 88 | left: (left < 0? 0 : left) + 'px' 89 | }); 90 | 91 | popupDialog.find('button.api-popup-cancel').click(function() { 92 | popupMask.hide(); 93 | popupDialog.hide(); 94 | popupDialog.empty(); 95 | popupDialog = []; 96 | }); 97 | 98 | $('button.api-popup-authbtn').unbind(); 99 | popupDialog.find('button.api-popup-authbtn').click(function() { 100 | popupMask.hide(); 101 | popupDialog.hide(); 102 | 103 | var authSchemes = window.swaggerUi.api.authSchemes; 104 | var host = window.location; 105 | var pathname = location.pathname.substring(0, location.pathname.lastIndexOf("/")); 106 | var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html'; 107 | var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl; 108 | var url = null; 109 | var scopes = [] 110 | var o = popup.find('input:checked'); 111 | var OAuthSchemeKeys = []; 112 | var state; 113 | for(k =0; k < o.length; k++) { 114 | var scope = $(o[k]).attr('scope'); 115 | if (scopes.indexOf(scope) === -1) 116 | scopes.push(scope); 117 | var OAuthSchemeKey = $(o[k]).attr('oauthtype'); 118 | if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1) 119 | OAuthSchemeKeys.push(OAuthSchemeKey); 120 | } 121 | 122 | //TODO: merge not replace if scheme is different from any existing 123 | //(needs to be aware of schemes to do so correctly) 124 | window.enabledScopes=scopes; 125 | 126 | for (var key in authSchemes) { 127 | if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope. 128 | var flow = authSchemes[key].flow; 129 | 130 | if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) { 131 | var dets = authSchemes[key]; 132 | url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code'); 133 | window.swaggerUi.tokenName = dets.tokenName || 'access_token'; 134 | window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null); 135 | state = key; 136 | } 137 | else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) { 138 | var dets = authSchemes[key]; 139 | window.swaggerUi.tokenName = dets.tokenName || 'access_token'; 140 | clientCredentialsFlow(scopes, dets.tokenUrl, key); 141 | return; 142 | } 143 | else if(authSchemes[key].grantTypes) { 144 | // 1.2 support 145 | var o = authSchemes[key].grantTypes; 146 | for(var t in o) { 147 | if(o.hasOwnProperty(t) && t === 'implicit') { 148 | var dets = o[t]; 149 | var ep = dets.loginEndpoint.url; 150 | url = dets.loginEndpoint.url + '?response_type=token'; 151 | window.swaggerUi.tokenName = dets.tokenName; 152 | } 153 | else if (o.hasOwnProperty(t) && t === 'accessCode') { 154 | var dets = o[t]; 155 | var ep = dets.tokenRequestEndpoint.url; 156 | url = dets.tokenRequestEndpoint.url + '?response_type=code'; 157 | window.swaggerUi.tokenName = dets.tokenName; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | redirect_uri = redirectUrl; 165 | 166 | url += '&redirect_uri=' + encodeURIComponent(redirectUrl); 167 | url += '&realm=' + encodeURIComponent(realm); 168 | url += '&client_id=' + encodeURIComponent(clientId); 169 | url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator)); 170 | url += '&state=' + encodeURIComponent(state); 171 | for (var key in additionalQueryStringParams) { 172 | url += '&' + key + '=' + encodeURIComponent(additionalQueryStringParams[key]); 173 | } 174 | 175 | window.open(url); 176 | }); 177 | 178 | popupMask.show(); 179 | popupDialog.show(); 180 | return; 181 | } 182 | 183 | 184 | function handleLogout() { 185 | for(key in window.swaggerUi.api.clientAuthorizations.authz){ 186 | window.swaggerUi.api.clientAuthorizations.remove(key) 187 | } 188 | window.enabledScopes = null; 189 | $('.api-ic.ic-on').addClass('ic-off'); 190 | $('.api-ic.ic-on').removeClass('ic-on'); 191 | 192 | // set the info box 193 | $('.api-ic.ic-warning').addClass('ic-error'); 194 | $('.api-ic.ic-warning').removeClass('ic-warning'); 195 | } 196 | 197 | function initOAuth(opts) { 198 | var o = (opts||{}); 199 | var errors = []; 200 | 201 | appName = (o.appName||errors.push('missing appName')); 202 | popupMask = (o.popupMask||$('#api-common-mask')); 203 | popupDialog = (o.popupDialog||$('.api-popup-dialog')); 204 | clientId = (o.clientId||errors.push('missing client id')); 205 | clientSecret = (o.clientSecret||null); 206 | realm = (o.realm||errors.push('missing realm')); 207 | scopeSeparator = (o.scopeSeparator||' '); 208 | additionalQueryStringParams = (o.additionalQueryStringParams||{}); 209 | 210 | if(errors.length > 0){ 211 | log('auth unable initialize oauth: ' + errors); 212 | return; 213 | } 214 | 215 | $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); 216 | $('.api-ic').unbind(); 217 | $('.api-ic').click(function(s) { 218 | if($(s.target).hasClass('ic-off')) 219 | handleLogin(); 220 | else { 221 | handleLogout(); 222 | } 223 | false; 224 | }); 225 | } 226 | 227 | function clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) { 228 | var params = { 229 | 'client_id': clientId, 230 | 'client_secret': clientSecret, 231 | 'scope': scopes.join(' '), 232 | 'grant_type': 'client_credentials' 233 | } 234 | $.ajax( 235 | { 236 | url : tokenUrl, 237 | type: "POST", 238 | data: params, 239 | success:function(data, textStatus, jqXHR) 240 | { 241 | onOAuthComplete(data,OAuthSchemeKey); 242 | }, 243 | error: function(jqXHR, textStatus, errorThrown) 244 | { 245 | onOAuthComplete(""); 246 | } 247 | }); 248 | 249 | } 250 | 251 | window.processOAuthCode = function processOAuthCode(data) { 252 | var OAuthSchemeKey = data.state; 253 | var params = { 254 | 'client_id': clientId, 255 | 'code': data.code, 256 | 'grant_type': 'authorization_code', 257 | 'redirect_uri': redirect_uri 258 | }; 259 | 260 | if (clientSecret) { 261 | params.client_secret = clientSecret; 262 | } 263 | 264 | $.ajax( 265 | { 266 | url : window.swaggerUi.tokenUrl, 267 | type: "POST", 268 | data: params, 269 | success:function(data, textStatus, jqXHR) 270 | { 271 | onOAuthComplete(data, OAuthSchemeKey); 272 | }, 273 | error: function(jqXHR, textStatus, errorThrown) 274 | { 275 | onOAuthComplete(""); 276 | } 277 | }); 278 | }; 279 | 280 | window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) { 281 | if(token) { 282 | if(token.error) { 283 | var checkbox = $('input[type=checkbox],.secured') 284 | checkbox.each(function(pos){ 285 | checkbox[pos].checked = false; 286 | }); 287 | alert(token.error); 288 | } 289 | else { 290 | var b = token[window.swaggerUi.tokenName]; 291 | if (!OAuthSchemeKey){ 292 | OAuthSchemeKey = token.state; 293 | } 294 | if(b){ 295 | // if all roles are satisfied 296 | var o = null; 297 | $.each($('.auth .api-ic .api_information_panel'), function(k, v) { 298 | var children = v; 299 | if(children && children.childNodes) { 300 | var requiredScopes = []; 301 | $.each((children.childNodes), function (k1, v1){ 302 | var inner = v1.innerHTML; 303 | if(inner) 304 | requiredScopes.push(inner); 305 | }); 306 | var diff = []; 307 | for(var i=0; i < requiredScopes.length; i++) { 308 | var s = requiredScopes[i]; 309 | if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) { 310 | diff.push(s); 311 | } 312 | } 313 | if(diff.length > 0){ 314 | o = v.parentNode.parentNode; 315 | $(o.parentNode).find('.api-ic.ic-on').addClass('ic-off'); 316 | $(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on'); 317 | 318 | // sorry, not all scopes are satisfied 319 | $(o).find('.api-ic').addClass('ic-warning'); 320 | $(o).find('.api-ic').removeClass('ic-error'); 321 | } 322 | else { 323 | o = v.parentNode.parentNode; 324 | $(o.parentNode).find('.api-ic.ic-off').addClass('ic-on'); 325 | $(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off'); 326 | 327 | // all scopes are satisfied 328 | $(o).find('.api-ic').addClass('ic-info'); 329 | $(o).find('.api-ic').removeClass('ic-warning'); 330 | $(o).find('.api-ic').removeClass('ic-error'); 331 | } 332 | } 333 | }); 334 | window.swaggerUi.api.clientAuthorizations.add(window.OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header')); 335 | window.swaggerUi.load(); 336 | } 337 | } 338 | } 339 | }; 340 | -------------------------------------------------------------------------------- /resources/public/js/list.min.js: -------------------------------------------------------------------------------- 1 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gb;b++)a.push(r.items[b].values());return a},this.add=function(a,b){if(0!==a.length){if(b)return void t(a,b);var c=[],e=!1;a[0]===d&&(a=[a]);for(var f=0,g=a.length;g>f;f++){var h=null;e=r.items.length>r.page,h=new s(a[f],d,e),r.items.push(h),c.push(h)}return r.update(),c}},this.show=function(a,b){return this.i=a,this.page=b,r.update(),r},this.remove=function(a,b,c){for(var d=0,e=0,f=r.items.length;f>e;e++)r.items[e].values()[a]==b&&(r.templater.remove(r.items[e],c),r.items.splice(e,1),f--,e--,d++);return r.update(),d},this.get=function(a,b){for(var c=[],d=0,e=r.items.length;e>d;d++){var f=r.items[d];f.values()[a]==b&&c.push(f)}return c},this.size=function(){return r.items.length},this.clear=function(){return r.templater.clear(),r.items=[],r},this.on=function(a,b){return r.handlers[a].push(b),r},this.off=function(a,b){var c=r.handlers[a],d=h(c,b);return d>-1&&c.splice(d,1),r},this.trigger=function(a){for(var b=r.handlers[a].length;b--;)r.handlers[a][b](r);return r},this.reset={filter:function(){for(var a=r.items,b=a.length;b--;)a[b].filtered=!1;return r},search:function(){for(var a=r.items,b=a.length;b--;)a[b].found=!1;return r}},this.update=function(){var a=r.items,b=a.length;r.visibleItems=[],r.matchingItems=[],r.templater.clear();for(var c=0;b>c;c++)a[c].matching()&&r.matchingItems.length+1>=r.i&&r.visibleItems.length0?setTimeout(function(){b(c,d,e)},1):(a.update(),d(e))};return b}},{}],3:[function(a,b,c){b.exports=function(a){return a.handlers.filterStart=a.handlers.filterStart||[],a.handlers.filterComplete=a.handlers.filterComplete||[],function(b){if(a.trigger("filterStart"),a.i=1,a.reset.filter(),void 0===b)a.filtered=!1;else{a.filtered=!0;for(var c=a.items,d=0,e=c.length;e>d;d++){var f=c[d];b(f)?f.filtered=!0:f.filtered=!1}}return a.update(),a.trigger("filterComplete"),a.visibleItems}}},{}],4:[function(a,b,c){b.exports=function(a){return function(b,c,d){var e=this;this._values={},this.found=!1,this.filtered=!1;var f=function(b,c,d){if(void 0===c)d?e.values(b,d):e.values(b);else{e.elm=c;var f=a.templater.get(e,b);e.values(f)}};this.values=function(b,c){if(void 0===b)return e._values;for(var d in b)e._values[d]=b[d];c!==!0&&a.templater.set(e,e.values())},this.show=function(){a.templater.show(e)},this.hide=function(){a.templater.hide(e)},this.matching=function(){return a.filtered&&a.searched&&e.found&&e.filtered||a.filtered&&!a.searched&&e.filtered||!a.filtered&&a.searched&&e.found||!a.filtered&&!a.searched},this.visible=function(){return!(!e.elm||e.elm.parentNode!=a.list)},f(b,c,d)}}},{}],5:[function(a,b,c){b.exports=function(b){var c=a("./item")(b),d=function(a){for(var b=a.childNodes,c=[],d=0,e=b.length;e>d;d++)void 0===b[d].data&&c.push(b[d]);return c},e=function(a,d){for(var e=0,f=a.length;f>e;e++)b.items.push(new c(d,a[e]))},f=function(a,c){var d=a.splice(0,50);e(d,c),a.length>0?setTimeout(function(){f(a,c)},1):(b.update(),b.trigger("parseComplete"))};return b.handlers.parseComplete=b.handlers.parseComplete||[],function(){var a=d(b.list),c=b.valueNames;b.indexAsync?f(a,c):e(a,c)}}},{"./item":4}],6:[function(a,b,c){b.exports=function(a){var b,c,d,e,f={resetList:function(){a.i=1,a.templater.clear(),e=void 0},setOptions:function(a){2==a.length&&a[1]instanceof Array?c=a[1]:2==a.length&&"function"==typeof a[1]?e=a[1]:3==a.length&&(c=a[1],e=a[2])},setColumns:function(){0!==a.items.length&&void 0===c&&(c=void 0===a.searchColumns?f.toArray(a.items[0].values()):a.searchColumns)},setSearchString:function(b){b=a.utils.toString(b).toLowerCase(),b=b.replace(/[-[\]{}()*+?.,\\^$|#]/g,"\\$&"),d=b},toArray:function(a){var b=[];for(var c in a)b.push(c);return b}},g={list:function(){for(var b=0,c=a.items.length;c>b;b++)g.item(a.items[b])},item:function(a){a.found=!1;for(var b=0,d=c.length;d>b;b++)if(g.values(a.values(),c[b]))return void(a.found=!0)},values:function(c,e){return!!(c.hasOwnProperty(e)&&(b=a.utils.toString(c[e]).toLowerCase(),""!==d&&b.search(d)>-1))},reset:function(){a.reset.search(),a.searched=!1}},h=function(b){return a.trigger("searchStart"),f.resetList(),f.setSearchString(b),f.setOptions(arguments),f.setColumns(),""===d?g.reset():(a.searched=!0,e?e(d,c):g.list()),a.update(),a.trigger("searchComplete"),a.visibleItems};return a.handlers.searchStart=a.handlers.searchStart||[],a.handlers.searchComplete=a.handlers.searchComplete||[],a.utils.events.bind(a.utils.getByClass(a.listContainer,a.searchClass),"keyup",function(b){var c=b.target||b.srcElement,d=""===c.value&&!a.searched;d||h(c.value)}),a.utils.events.bind(a.utils.getByClass(a.listContainer,a.searchClass),"input",function(a){var b=a.target||a.srcElement;""===b.value&&h("")}),h}},{}],7:[function(a,b,c){b.exports=function(a){a.sortFunction=a.sortFunction||function(b,c,d){return d.desc="desc"==d.order,a.utils.naturalSort(b.values()[d.valueName],c.values()[d.valueName],d)};var b={els:void 0,clear:function(){for(var c=0,d=b.els.length;d>c;c++)a.utils.classes(b.els[c]).remove("asc"),a.utils.classes(b.els[c]).remove("desc")},getOrder:function(b){var c=a.utils.getAttribute(b,"data-order");return"asc"==c||"desc"==c?c:a.utils.classes(b).has("desc")?"asc":a.utils.classes(b).has("asc")?"desc":"asc"},getInSensitive:function(b,c){var d=a.utils.getAttribute(b,"data-insensitive");"false"===d?c.insensitive=!1:c.insensitive=!0},setOrder:function(c){for(var d=0,e=b.els.length;e>d;d++){var f=b.els[d];if(a.utils.getAttribute(f,"data-sort")===c.valueName){var g=a.utils.getAttribute(f,"data-order");"asc"==g||"desc"==g?g==c.order&&a.utils.classes(f).add(c.order):a.utils.classes(f).add(c.order)}}}},c=function(){a.trigger("sortStart");var c={},d=arguments[0].currentTarget||arguments[0].srcElement||void 0;d?(c.valueName=a.utils.getAttribute(d,"data-sort"),b.getInSensitive(d,c),c.order=b.getOrder(d)):(c=arguments[1]||c,c.valueName=arguments[0],c.order=c.order||"asc",c.insensitive="undefined"==typeof c.insensitive?!0:c.insensitive),b.clear(),b.setOrder(c),c.sortFunction=c.sortFunction||a.sortFunction,a.items.sort(function(a,b){var d="desc"===c.order?-1:1;return c.sortFunction(a,b,c)*d}),a.update(),a.trigger("sortComplete")};return a.handlers.sortStart=a.handlers.sortStart||[],a.handlers.sortComplete=a.handlers.sortComplete||[],b.els=a.utils.getByClass(a.listContainer,a.sortClass),a.utils.events.bind(b.els,"click",c),a.on("searchStart",b.clear),a.on("filterStart",b.clear),c}},{}],8:[function(a,b,c){var d=function(a){var b,c=this,d=function(){b=c.getItemSource(a.item),b=c.clearSourceItem(b,a.valueNames)};this.clearSourceItem=function(b,c){for(var d=0,e=c.length;e>d;d++){var f;if(c[d].data)for(var g=0,h=c[d].data.length;h>g;g++)b.setAttribute("data-"+c[d].data[g],"");else c[d].attr&&c[d].name?(f=a.utils.getByClass(b,c[d].name,!0),f&&f.setAttribute(c[d].attr,"")):(f=a.utils.getByClass(b,c[d],!0),f&&(f.innerHTML=""));f=void 0}return b},this.getItemSource=function(b){if(void 0===b){for(var c=a.list.childNodes,d=0,e=c.length;e>d;d++)if(void 0===c[d].data)return c[d].cloneNode(!0)}else{if(/^tr[\s>]/.exec(b)){var f=document.createElement("table");return f.innerHTML=b,f.firstChild}if(-1!==b.indexOf("<")){var g=document.createElement("div");return g.innerHTML=b,g.firstChild}var h=document.getElementById(a.item);if(h)return h}throw new Error("The list need to have at list one item on init otherwise you'll have to add a template.")},this.get=function(b,d){c.create(b);for(var e={},f=0,g=d.length;g>f;f++){var h;if(d[f].data)for(var i=0,j=d[f].data.length;j>i;i++)e[d[f].data[i]]=a.utils.getAttribute(b.elm,"data-"+d[f].data[i]);else d[f].attr&&d[f].name?(h=a.utils.getByClass(b.elm,d[f].name,!0),e[d[f].name]=h?a.utils.getAttribute(h,d[f].attr):""):(h=a.utils.getByClass(b.elm,d[f],!0),e[d[f]]=h?h.innerHTML:"");h=void 0}return e},this.set=function(b,d){var e=function(b){for(var c=0,d=a.valueNames.length;d>c;c++)if(a.valueNames[c].data){for(var e=a.valueNames[c].data,f=0,g=e.length;g>f;f++)if(e[f]===b)return{data:b}}else{if(a.valueNames[c].attr&&a.valueNames[c].name&&a.valueNames[c].name==b)return a.valueNames[c];if(a.valueNames[c]===b)return b}},f=function(c,d){var f,g=e(c);g&&(g.data?b.elm.setAttribute("data-"+g.data,d):g.attr&&g.name?(f=a.utils.getByClass(b.elm,g.name,!0),f&&f.setAttribute(g.attr,d)):(f=a.utils.getByClass(b.elm,g,!0),f&&(f.innerHTML=d)),f=void 0)};if(!c.create(b))for(var g in d)d.hasOwnProperty(g)&&f(g,d[g])},this.create=function(a){if(void 0!==a.elm)return!1;var d=b.cloneNode(!0);return d.removeAttribute("id"),a.elm=d,c.set(a,a.values()),!0},this.remove=function(b){b.elm.parentNode===a.list&&a.list.removeChild(b.elm)},this.show=function(b){c.create(b),a.list.appendChild(b.elm)},this.hide=function(b){void 0!==b.elm&&b.elm.parentNode===a.list&&a.list.removeChild(b.elm)},this.clear=function(){if(a.list.hasChildNodes())for(;a.list.childNodes.length>=1;)a.list.removeChild(a.list.firstChild)},d()};b.exports=function(a){return new d(a)}},{}],9:[function(a,b,c){function d(a){if(!a||!a.nodeType)throw new Error("A DOM element reference is required");this.el=a,this.list=a.classList}var e=a("./index-of"),f=/\s+/,g=Object.prototype.toString;b.exports=function(a){return new d(a)},d.prototype.add=function(a){if(this.list)return this.list.add(a),this;var b=this.array(),c=e(b,a);return~c||b.push(a),this.el.className=b.join(" "),this},d.prototype.remove=function(a){if("[object RegExp]"==g.call(a))return this.removeMatching(a);if(this.list)return this.list.remove(a),this;var b=this.array(),c=e(b,a);return~c&&b.splice(c,1),this.el.className=b.join(" "),this},d.prototype.removeMatching=function(a){for(var b=this.array(),c=0;cf;f++)void 0!==b[f]&&b[f].nodeName===b&&(c=b[f].nodeValue);return c}},{}],13:[function(a,b,c){b.exports=function(){return document.getElementsByClassName?function(a,b,c){return c?a.getElementsByClassName(b)[0]:a.getElementsByClassName(b)}:document.querySelector?function(a,b,c){return b="."+b,c?a.querySelector(b):a.querySelectorAll(b)}:function(a,b,c){var d=[],e="*";null===a&&(a=document);for(var f=a.getElementsByTagName(e),g=f.length,h=new RegExp("(^|\\s)"+b+"(\\s|$)"),i=0,j=0;g>i;i++)if(h.test(f[i].className)){if(c)return f[i];d[j]=f[i],j++}return d}}()},{}],14:[function(a,b,c){var d=[].indexOf;b.exports=function(a,b){if(d)return a.indexOf(b);for(var c=0;cr)return-1;if(r>s)return 1}for(var u=0,v=p.length,w=q.length,x=Math.max(v,w);x>u;u++){if(d=t(p[u]||"",v),e=t(q[u]||"",w),isNaN(d)!==isNaN(e))return isNaN(d)?1:-1;if(/[^\x00-\x80]/.test(d+e)&&d.localeCompare){var y=d.localeCompare(e);return y/Math.abs(y)}if(e>d)return-1;if(d>e)return 1}return 0}},{}],16:[function(a,b,c){function d(a){return"[object Array]"===Object.prototype.toString.call(a)}b.exports=function(a){if("undefined"==typeof a)return[];if(null===a)return[null];if(a===window)return[window];if("string"==typeof a)return[a];if(d(a))return a;if("number"!=typeof a.length)return[a];if("function"==typeof a&&a instanceof Function)return[a];for(var b=[],c=0;c