scala-cask.apiPackage.mustache Maven / Gradle / Ivy
The newest version!
{{>licenseInfo}}
package {{apiPackage}}
import cask.FormEntry
import io.undertow.server.handlers.form.{FormData, FormParserFactory}
import java.io.File
import scala.jdk.CollectionConverters.*
import java.time.LocalDate
import java.util.UUID
import scala.reflect.ClassTag
import scala.util.*
import upickle.default.*
extension (f: java.io.File) {
def bytes: Array[Byte] = java.nio.file.Files.readAllBytes(f.toPath)
def toBase64: String = java.util.Base64.getEncoder.encodeToString(bytes)
}
given Writer[java.io.File] = new Writer[java.io.File] {
def write0[V](out: upickle.core.Visitor[?, V], v: java.io.File) = out.visitString(v.toBase64, -1)
}
// needed for BigDecimal params
given cask.endpoints.QueryParamReader.SimpleParam[BigDecimal](BigDecimal.apply)
// a parsed value from an HTTP request
opaque type Parsed[A] = Either[String, A]
object Parsed {
def apply[A](value: A): Parsed[A] = Right(value)
def eval[A](value: => A): Parsed[A] = Try(value) match {
case Failure(exp) => Left(s"Error: ${exp.getMessage}")
case Success(ok) => Right(ok)
}
def fromTry[A](value : Try[A]) = value match {
case Failure(err) => Left(err.getMessage)
case Success(ok) => Right(ok)
}
def fail[A](msg: String): Parsed[A] = Left(msg)
def optionalValue(map: Map[String, collection.Seq[String]], key: String): Parsed[Option[String]] = {
map.get(key) match {
case Some(Seq(only: String)) => Parsed(Option(only))
case Some(Seq()) => Parsed(None)
case Some(many) => Parsed.fail(s"${many.size} values set for '$key'")
case None => Parsed(None)
}
}
def singleValue(map: Map[String, collection.Seq[String]], key : String): Parsed[String] = {
map.get(key) match {
case Some(Seq(only : String)) => Parsed(only)
case Some(Seq()) => Parsed("")
case Some(many) => Parsed.fail(s"${many.size} values set for '$key'")
case None => Parsed.fail(s"required '$key' was not set")
}
}
def manyValues(map: Map[String, collection.Seq[String]], key : String, required: Boolean): Parsed[List[String]] = {
map.get(key) match {
case Some(many) => Parsed(many.toList)
case None if required => Parsed.fail(s"required '$key' was not set")
case None => Parsed(Nil)
}
}
}
extension[A] (parsed: Parsed[A]) {
def toEither: Either[String, A] = parsed
def asLong(using ev : A =:= String): Parsed[Long] = as[Long](_.toLongOption)
def asBoolean(using ev : A =:= String): Parsed[Boolean] = as[Boolean](_.toBooleanOption)
def asInt(using ev : A =:= String): Parsed[Int] = as[Int](_.toIntOption)
def asByte(using ev : A =:= String): Parsed[Byte] = as[Byte](_.toByteOption)
def asUuid(using ev : A =:= String): Parsed[UUID] = as[UUID](x => Try(UUID.fromString(x)).toOption)
def asFloat(using ev : A =:= String): Parsed[Float] = as[Float](_.toFloatOption)
def asDouble(using ev : A =:= String): Parsed[Double] = as[Double](_.toDoubleOption)
def asDate(using ev: A =:= String): Parsed[LocalDate] = as[LocalDate](x => Try(LocalDate.parse(x)).toOption)
private def as[B : ClassTag](f : String => Option[B])(using ev : A =:= String): Parsed[B] = parsed.flatMap { str =>
f(ev(str)) match {
case None => Parsed.fail(s"'$str' cannot be parsed as a ${implicitly[ClassTag[B]].runtimeClass}")
case Some(x) => Parsed(x)
}
}
def mapError(f : String => String) : Parsed[A] = parsed match {
case Left(msg) => Left(f(msg))
case right => right
}
def map[B](f: A => B): Parsed[B] = parsed match {
case Right(value) => Right(f(value))
case Left(err) => Left(err)
}
def flatMap[B](f : A => Parsed[B]): Parsed[B] = parsed match {
case Right(value) => f(value)
case Left(err) => Left(err)
}
}
extension (request: cask.Request) {
def formSingleValueRequired(paramName: String): Parsed[String] = {
val data = formDataForKey(paramName).map(_.getValue).toSeq
Parsed.singleValue(Map(paramName -> data), paramName)
}
def formSingleValueOptional(paramName: String): Parsed[Option[String]] = {
val data = formDataForKey(paramName).map(_.getValue).toSeq
Parsed.optionalValue(Map(paramName -> data), paramName)
}
def formValueAsFileOptional(paramName: String): Parsed[Option[File]] = {
val data = formDataForKey(paramName)
data.map(_.getFileItem.getFile.toFile).toSeq match {
case Seq() => Parsed(None)
case Seq(file) => Parsed(Option(file))
case many => Parsed.fail(s"${many.size} file values set for '$paramName'")
}
}
def formValueAsFileRequired(paramName: String): Parsed[File] = {
val data = formDataForKey(paramName)
data.map(_.getFileItem.getFile.toFile).toSeq match {
case Seq() => Parsed.fail(s"No file form data was submitted for '$paramName'. The submitted form keys were: ${formDataKeys.mkString(",")}")
case Seq(file) => Parsed(file)
case many => Parsed.fail(s"${many.size} file values set for '$paramName'")
}
}
def formManyValues(paramName: String, required: Boolean): Parsed[List[String]] = {
val data = formDataForKey(paramName).map(_.getValue).toSeq
Parsed.manyValues(Map(paramName -> data), paramName, required)
}
def formData: FormData = FormParserFactory.builder().build().createParser(request.exchange).parseBlocking()
def formDataKeys: Iterator[String] = formData.iterator().asScala
def formDataForKey(paramName: String): Iterable[FormData.FormValue] = formData.get(paramName).asScala
def headerSingleValueOptional(paramName: String): Parsed[Option[String]] = Parsed.optionalValue(request.headers, paramName)
def headerSingleValueRequired(paramName: String): Parsed[String] = Parsed.singleValue(request.headers, paramName)
def headerManyValues(paramName: String, required: Boolean): Parsed[List[String]] = Parsed.manyValues(request.headers, paramName, required)
def bodyAsString = new String(request.readAllBytes(), "UTF-8")
def bodyAsJson : Try[ujson.Value] = {
val jason = bodyAsString
try {
Success(read[ujson.Value](jason))
} catch {
case scala.util.control.NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
}
}
def pathValue(index: Int, paramName: String, required : Boolean): Parsed[String] = {
request
.remainingPathSegments
.lift(index) match {
case Some(value) => Right(value)
case None if required => Left(s"'$paramName'' is a required path parameter at path position $index")
case None => Right("")
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy