Last active
July 17, 2025 14:06
-
-
Save jelinski/2f3e8c5098fd4b5a4b3897a2f872d7fd to your computer and use it in GitHub Desktop.
An example showing how to dynamically set header value for Content-Disposition HTTP header using Tapir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.effect.IO | |
import cats.syntax.all.* | |
import sttp.capabilities.fs2.Fs2Streams | |
import sttp.model.HeaderNames.ContentDisposition | |
import sttp.tapir.EndpointIO.Header | |
import sttp.tapir.* | |
import java.nio.charset.StandardCharsets | |
// This is an example showing how to dynamically set the Content-Disposition HTTP header in Tapir | |
object ContentDispositionTapirExample { | |
import ContentDispositionHeader.* | |
val route = | |
endpoint.get | |
.in("download") | |
.in(path[String].name("fileName")) | |
.out(contentDispositionHeader) | |
.out(streamBinaryBody(Fs2Streams[IO])(CodecFormat.OctetStream())) | |
val attachmentWithFileNameEndpoint = | |
route | |
.in("attachment-with-file-name") | |
.serverLogicSuccessPure { fileName => | |
attachmentWithFileName(fileName) -> downloadFile(fileName) | |
} | |
val attachmentWithoutFileNameEndpoint = | |
route | |
.in("attachment") | |
.serverLogicSuccessPure { fileName => | |
attachment -> downloadFile(fileName) | |
} | |
val inlineContentEndpoint = | |
route | |
.in("inline") | |
.serverLogicSuccessPure { fileName => | |
inline -> downloadFile(fileName) | |
} | |
def downloadFile(fileName: String) = | |
fs2.Stream(s"Contents from file: $fileName".getBytes(StandardCharsets.UTF_8).toSeq*).covary[IO] | |
sealed trait ContentDispositionHeader | |
object ContentDispositionHeader { | |
def attachmentWithFileName(fileName: String): ContentDispositionHeader = ContentDispositionAttachment(fileName.some) | |
def attachment: ContentDispositionHeader = ContentDispositionAttachment(none) | |
def inline: ContentDispositionHeader = ContentDispositionInline() | |
private case class ContentDispositionAttachment(fileName: Option[String]) extends ContentDispositionHeader | |
private case class ContentDispositionInline() extends ContentDispositionHeader | |
val contentDispositionHeader: Header[ContentDispositionHeader] = | |
header[String](ContentDisposition) | |
.mapDecode( | |
DecodeResult | |
.Error(_, new IllegalArgumentException("Content-Disposition header is not expected in HTTP Request")) | |
) { | |
case ContentDispositionAttachment(Some(fileName)) => s"attachment; filename=\"$fileName\"" | |
case ContentDispositionAttachment(None) => "attachment" | |
case ContentDispositionInline() => "inline" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment