servant-swagger-ui-0.1.1.2.1.4: src/Servant/Swagger/UI.hs
{-# 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 <oleg.grenrus@iki.fi>
--
-- Provides 'SwaggerUI' and corresponding 'swaggerUIServer' to embed
-- <http://swagger.io/swagger-ui/ swagger ui> 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 @/<dir>@ 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")