unstatic.ztapir.core.scala Maven / Gradle / Ivy
package unstatic.ztapir
import scala.collection.*
import sttp.tapir.ztapir.*
import sttp.tapir.{Endpoint, EndpointIO, EndpointInput, EndpointOutput}
import sttp.tapir.internal.RichEndpoint
import sttp.model.{Header, MediaType, Method, StatusCode}
import sttp.tapir.EndpointOutput.MappedPair
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.ziohttp.ZioHttpToResponseBody
import unstatic.*
import unstatic.UrlPath.*
import zio.*
import java.nio.file.Path as JPath
type ZTServerEndpoint = ZServerEndpoint[Any,Any] //ServerEndpoint[Any,[t] =>> ZIO[Any,String,t]]
val NoIdentifiers = immutable.Set.empty[String]
// valid operations returning ZServerEndpoint[Nothing,Any] seems to be a Scala 3/tapir
// type inference glitch.
//
// see https://github.com/softwaremill/tapir/issues/2694
//
// for now we workaround.
extension ( badInference : ZServerEndpoint[Nothing,Any] )
def glitchWiden : ZTServerEndpoint =
badInference.asInstanceOf[ZTServerEndpoint]
private def endpointForFixedPath( serverRootedPath : Rooted ) : Endpoint[Unit, Unit, Unit, Unit, Any] =
if (serverRootedPath == Rooted.root) then
endpoint.get.in("")
else
serverRootedPath.elements.foldLeft(endpoint.get)( (accum, next) => accum.in( next ) )
private def inputsForFixedPath( serverRootedPath : Rooted ) : EndpointInput[Unit] =
if (serverRootedPath == Rooted.root) then
"" : EndpointInput[Unit] // root always represents a directory
else
serverRootedPath.elements.tail.foldLeft(serverRootedPath.elements.head : EndpointInput[Unit])( (accum, next) => accum / next )
private def errMapped[T]( task : Task[T] ) : zio.ZIO[Any,String,T] =
// XXX: Should I do something to break harder on non-nonFatal errors?
task.mapError { t =>
import java.io.*
val sw = new StringWriter()
t.printStackTrace(new PrintWriter(sw))
sw.toString()
}
private def errMapped[S,T]( f : Function1[S,Task[T]] ) : Function1[S,zio.ZIO[Any,String,T]] =
// XXX: Should I do something to break harder on non-nonFatal errors?
f.andThen( errMapped )
private def redirectEndpoint( fromServerRooted : Rooted, toServerRooted : Rooted ) : Endpoint[Unit, Unit, Unit, Unit, Any] =
endpointForFixedPath(fromServerRooted)
.out( redirectOutputs(toServerRooted))
private def redirectOutputs( redirectToServerRooted : Rooted ) =
statusCode(StatusCode.MovedPermanently) and header(Header.location(redirectToServerRooted.toString()))
private val UnitTask = ZIO.attempt( () )
private val CharsetUTF8 = scala.io.Codec.UTF8.charSet
private val SomeUTF8 = Some(CharsetUTF8)
val UnitThrowableUnitLogic = (_ : Unit) => (ZIO.unit : Task[Unit])
val UnitUnitUnitLogic = UnitThrowableUnitLogic.andThen(_.mapError(_ => ()) )
val MediaTypeRss = MediaType("application","rss+xml",None,immutable.Map.empty[String,String])
private def redirectZTEndpointBinding( fromServerRooted : Rooted, toServerRooted : Rooted, site : Site ) : ZTEndpointBinding =
val endpoint = redirectEndpoint(fromServerRooted,toServerRooted)
val ztServerEndpoint = endpoint.zServerLogic( UnitUnitUnitLogic ).glitchWiden
ZTEndpointBinding.generic[Unit,Unit](site.siteRootedPath(fromServerRooted), ztServerEndpoint, UnitThrowableUnitLogic, NoIdentifiers)
private def publicReadOnlyUtf8HtmlEndpoint( siteRootedPath: Rooted, site : Site, task: zio.Task[String] ) : ZTServerEndpoint =
val endpoint =
endpointForFixedPath( site.serverRootedPath(siteRootedPath) )
.errorOut(stringBody(CharsetUTF8))
.out(header(Header.contentType(MediaType.TextHtml.charset(CharsetUTF8))))
.out(htmlBodyUtf8)
endpoint.zServerLogic( _ => errMapped(task) )
// XXX: should I modify this to output immutable.ArraySeq[Byte]?
private def publicReadOnlyUtf8RssEndpoint( siteRootedPath: Rooted, site : Site, task: zio.Task[immutable.ArraySeq[Byte]] ) : ZTServerEndpoint =
val endpoint =
endpointForFixedPath( site.serverRootedPath(siteRootedPath) )
.errorOut(stringBody(CharsetUTF8))
.out(header(Header.contentType(MediaTypeRss)))
.out(byteArrayBody)
endpoint.zServerLogic( _ => errMapped(task.map(_.toArray)) )
private def staticDirectoryServingEndpoint(siteRootedPath: Rooted, site: Site, dir: JPath): ZTServerEndpoint =
val serverRootedPath = site.serverRootedPath(siteRootedPath)
// see https://tapir.softwaremill.com/en/latest/endpoint/static.html
val inputs = if serverRootedPath.isRoot then emptyInput else inputsForFixedPath(serverRootedPath)
filesGetServerEndpoint[Task](inputs)(dir.toAbsolutePath.toString)
private def staticFileServingEndpoint(siteRootedPath: Rooted, site: Site, file: JPath): ZTServerEndpoint =
val serverRootedPath = site.serverRootedPath(siteRootedPath)
if siteRootedPath.isDir then
if siteRootedPath.isRoot || serverRootedPath.isRoot then // second case should be impossible without first
throw new MustRepresentDirectory(s"Illegal endpoint, the root directory must represent a directory, not a single file.")
else
scribe.warn(s"A UrlPath marked as a directory (would print with terminal slash) is given as endpoint for single file '${file}'.")
val inputs = inputsForFixedPath(serverRootedPath) // we know it's not root
fileGetServerEndpoint[Task](inputs)(file.toAbsolutePath.toString)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy