Skip to content

Instantly share code, notes, and snippets.

@easel
Created January 6, 2016 17:08
Show Gist options
  • Save easel/c84c1181021d89bc5ae9 to your computer and use it in GitHub Desktop.
Save easel/c84c1181021d89bc5ae9 to your computer and use it in GitHub Desktop.
Circe Play Body Parser
import java.nio.ByteBuffer
import cats.data.Xor
import com.theseventhsense.utils.logging.Logging
import io.circe
import io.circe.{Decoder, Json}
import play.api.http.LazyHttpErrorHandler
import play.api.http.Status._
import play.api.libs.iteratee._
import play.api.mvc.BodyParsers.parse._
import play.api.mvc.{BodyParser, RequestHeader, Result, _}
import scala.concurrent.{ExecutionContext, Future}
trait CirceBodyParser extends Logging {
object Circe {
val jsonCirceBodyParser = {
BodyParser[Json]("json (circe)") { requestHeader =>
import play.api.libs.iteratee.Execution.Implicits.trampoline
Iteratee.consume[Array[Byte]]().mapM[Either[Result, Json]] { bytes =>
val maybeJson = io.circe.jawn.parseByteBuffer(ByteBuffer.wrap(bytes))
maybeJson match {
case Xor.Left(error) =>
createBadResult("json parsing error: " + error.getMessage())(requestHeader).map(Left.apply)
case Xor.Right(json) => Future.successful(Right(json))
}
}
}
}
def tolerantJson[A](maxLength: Int, parser: BodyParser[A]): BodyParser[A] = {
BodyParser[A]("maxLength=" + maxLength + ", wrapping=" + parser.toString) { requestHeader =>
import play.api.libs.iteratee.Execution.Implicits.trampoline
Traversable.takeUpTo[Array[Byte]](maxLength.toLong).transform(parser(requestHeader))
.flatMap(Iteratee.eofOrElse(MaxSizeExceeded(maxLength.toLong)))
.mapM {
case Right(Right(result)) => Future.successful(Right(result))
case Right(Left(badRequest)) => Future.successful(Left(badRequest))
case Left(maxSizeExceeded) => createBadResult(
"Request Entity Too Large",
REQUEST_ENTITY_TOO_LARGE
)(requestHeader).map(Left.apply)
}
}
}
def json: BodyParser[Json] = when(
_.contentType.exists(m => m.equalsIgnoreCase("text/json") || m.equalsIgnoreCase("application/json")),
tolerantJson(DefaultMaxTextLength, jsonCirceBodyParser),
createBadResult("Expecting text/json or application/json body", UNSUPPORTED_MEDIA_TYPE)
)
def createBadResult(msg: String, statusCode: Int = BAD_REQUEST): RequestHeader => Future[Result] = { request =>
LazyHttpErrorHandler.onClientError(request, statusCode, msg)
}
def decode[T](maxLength: Long)(implicit
decoder: Decoder[T],
ec: ExecutionContext): BodyParser[Xor[circe.Error, T]] = {
json.map(_.as[T])
}
def decode[T](implicit decoder: Decoder[T], ec: ExecutionContext): BodyParser[Xor[circe.Error, T]] = {
decode[T](DefaultMaxTextLength.toLong)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment