sttp.tapir.redoc.Redoc.scala Maven / Gradle / Ivy
package sttp.tapir.redoc
import sttp.model.{Header, HeaderNames, MediaType, StatusCode}
import sttp.tapir._
import sttp.tapir.server.ServerEndpoint
object Redoc {
val defaultRedocVersion = "2.0.0-rc.56"
def redocHtml(
title: String,
specUrl: String,
redocVersion: String = defaultRedocVersion,
redocOptions: Option[String] = None,
redocThemeOptionsJson: Option[String] = None
): String = {
val options = redocOptions.filterNot(_.isEmpty).getOrElse("")
val themeOptions = redocThemeOptionsJson.filterNot(_.isEmpty).fold("")(json => s"theme='$json'")
s"""
|
|
|
| $title
|
|
|
|
|
|
|
|
|
|
|
|
|
|""".stripMargin
}
def apply[F[_]](
title: String,
spec: String,
options: RedocUIOptions
): List[ServerEndpoint[Any, F]] = {
val specName = options.specName
val htmlName = options.htmlName
val prefixInput: EndpointInput[Unit] = options.pathPrefix.map(stringToPath).foldLeft(emptyInput)(_.and(_))
val baseEndpoint = infallibleEndpoint.get.in(prefixInput)
val redirectOutput = statusCode(StatusCode.PermanentRedirect).and(header[String](HeaderNames.Location))
def contentEndpoint(fileName: String, mediaType: MediaType) =
baseEndpoint.in(fileName).out(stringBody).out(header(Header.contentType(mediaType)))
val specNameLowerCase = specName.toLowerCase
val specMediaType =
if (specNameLowerCase.endsWith(".json")) MediaType("application", "json")
else if (specNameLowerCase.endsWith(".yaml") || specNameLowerCase.endsWith(".yml")) MediaType("text", "yaml")
else MediaType("text", "plain")
val specEndpoint = contentEndpoint(specName, specMediaType).serverLogicSuccessPure[F](_ => spec)
val specPrefix = if (options.useRelativePaths) "." else "/" + (options.contextPath ++ options.pathPrefix).mkString("/")
val html: String = redocHtml(
title,
s"$specPrefix/$specName",
options.redocVersion,
options.redocOptions,
options.redocThemeOptionsJson
)
val htmlEndpoint = contentEndpoint(htmlName, MediaType.TextHtml).serverLogicSuccessPure[F](_ => html)
val lastSegmentInput: EndpointInput[Option[String]] = extractFromRequest(_.uri.path.lastOption)
val redirectToHtmlEndpoint =
baseEndpoint
.in(lastSegmentInput)
.out(redirectOutput)
.serverLogicSuccessPure[F] { lastSegment =>
if (options.useRelativePaths) {
val pathFromLastSegment: String = lastSegment match {
case Some(s) if s.nonEmpty => s + "/"
case _ => ""
}
s"./$pathFromLastSegment$htmlName"
} else
options.contextPath ++ options.pathPrefix match {
case Nil => s"/$htmlName"
case segments => s"/${segments.mkString("/")}/$htmlName"
}
}
List(specEndpoint, htmlEndpoint, redirectToHtmlEndpoint)
}
}