Skip to content

Instantly share code, notes, and snippets.

@hamnis
Created February 1, 2021 07:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hamnis/d7f21e5c6aa81c6d731b11548eb977e8 to your computer and use it in GitHub Desktop.
Save hamnis/d7f21e5c6aa81c6d731b11548eb977e8 to your computer and use it in GitHub Desktop.
Content-Negotiation for http4s
import org.http4s.{MediaType, MediaRange, Request, Response}
import org.http4s.headers.{Accept, MediaRangeAndQValue}
import cats.data.NonEmptyList
import org.http4s.QValue
/*
* https://tools.ietf.org/html/rfc7231#section-5.3
*/
object ContentNegotiation {
def mediaTypes[F[_]](
r: Request[F],
head: (MediaType, F[Response[F]]),
tail: (MediaType, F[Response[F]])*
): F[Response[F]] = {
val accepts = r.headers.get(Accept).map(_.values.toList).getOrElse(Nil)
score(accepts, NonEmptyList(head, tail.toList)).maxByOption(_._1).map(_._2).getOrElse(head._2)
}
def score[A](accepts: List[MediaRangeAndQValue], provided: NonEmptyList[(MediaType, A)]): List[(QValue, A)] =
provided.toList.flatMap {
case (mediaType, response) =>
def exact =
accepts.collectFirst {
case MediaRangeAndQValue(`mediaType`, qValue) =>
(qValue, response)
}
def tpe =
accepts.collectFirst {
case MediaRangeAndQValue(mediaRange, qValue) if mediaType.satisfiedBy(mediaRange) =>
(qValue, response)
}
def range =
accepts.collectFirst {
case MediaRangeAndQValue(mediaRange, qValue)
if mediaRange != MediaRange.`*/*` && mediaRange.satisfiedBy(mediaType) =>
(qValue, response)
}
def wildcard =
accepts.collectFirst {
case MediaRangeAndQValue(mediaRange, qValue) if mediaRange == MediaRange.`*/*` =>
(qValue, response)
}
exact orElse tpe orElse range orElse wildcard
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment