{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Servant.Swagger.UI -- Copyright : (C) 2016 Oleg Grenrus -- License : BSD3 -- Maintainer : Oleg Grenrus -- -- Provides 'SwaggerUI' and corresponding 'swaggerUIServer' to embed -- into the application. -- -- All of UI files are embedded into the binary. -- -- /An example:/ -- -- @ -- -- | Actual API. -- type BasicAPI = Get '[PlainText, JSON] Text -- :\<|> "cat" :> Capture ":name" CatName :> Get '[JSON] Cat -- -- -- | Swagger schema endpoint. -- type SwaggerSchemaEndpoint = "swagger.js" :> Get '[JSON] Swagger -- -- -- | Unhabitated new data type, to be able to refer to API type from the API type. -- data API -- -- -- | Underlying API type. -- -- -- -- NOTE: place BasicAPI last for it not to override SwaggerSchemaEndpoing and SwaggerUI. -- -- If you place BasicAPI first and it has Raw endpoint at root Swagger schema and UI won't work. -- type API' = SwaggerSchemaEndpoint -- :\<|> 'SwaggerUI' "ui" SwaggerSchemaEndpoint API -- :\<|> BasicAPI -- -- -- Optionally we can do: -- -- -- -- type API' = SwaggerSchemaEndpoint -- -- :\<|> BasicAPI -- -- -- -- type API = SwaggerUI "ui" SwaggerSchemaEndpoint API' -- -- :\<|> API' -- -- -- Unfortunately we have to write these trivial instances. -- -- instance HasServer API context where -- type ServerT API m = ServerT API' m -- route _ = route (Proxy :: Proxy API') -- -- type instance IsElem' e API = IsElem e API' -- -- server :: Server API -- server = pure swaggerDoc -- :\<|> 'swaggerUIServer' -- :\<|> (pure "Hello World" :\<|> catEndpoint) -- where -- catEndpoint name = pure $ Cat name False -- @ module Servant.Swagger.UI ( -- * Swagger UI API SwaggerUI, swaggerUIServer, -- * Internals SwaggerUiHtml(..), ) where import Data.ByteString (ByteString) import Data.FileEmbed (embedStringFile) import Data.Monoid ((<>)) import GHC.TypeLits (Symbol) import Network.Wai.Application.Static (embeddedSettings, staticApp) import Servant import Servant.HTML.Blaze (HTML) import Text.Blaze (ToMarkup (..)) import Servant.Swagger.UI.Internal import qualified Data.Text as T -- | Swagger API ui. type SwaggerUI (dir :: Symbol) endpoint api = dir :> ( Get '[HTML] (SwaggerUiHtml endpoint api) :<|> "index.html" :> Get '[HTML] (SwaggerUiHtml endpoint api) :<|> Raw) -- | Index file for swagger ui. -- -- It's configured by the location of swagger schema. data SwaggerUiHtml endpoint api = SwaggerUiHtml instance (IsElem endpoint api, HasLink endpoint, MkLink endpoint ~ URI) => ToMarkup (SwaggerUiHtml endpoint api) where toMarkup _ = preEscapedToMarkup $ T.replace "SWAGGER_URL_PLACEHOLDER" url swaggerUiIndexTemplate where uri = safeLink (Proxy :: Proxy api) (Proxy :: Proxy endpoint) :: URI url = T.pack $ "/" <> uriPath uri -- TODO: do we need more? -- | Serve Swagger UI on @/@ using @endpoint@ as Swagger spec source for @api@. swaggerUIServer :: Server (SwaggerUI dir endpoint api) swaggerUIServer = return SwaggerUiHtml :<|> return SwaggerUiHtml :<|> rest where rest = staticApp $ embeddedSettings swaggerUiFiles swaggerUiIndexTemplate :: T.Text swaggerUiIndexTemplate = $(embedStringFile "index.html.tmpl") swaggerUiFiles :: [(FilePath, ByteString)] swaggerUiFiles = $(mkRecursiveEmbedded "swagger-dist-2.1.4")