For validating data you need to use the Validator
typeclass.
A small example:
import ba.sake.validson.Validator
case class ValidatedData(num: Int, str: String, seq: Seq[String])
object ValidatedData:
given Validator[ValidatedData] = Validator
.derived[ValidatedData]
.positive(_.num)
.notBlank(_.str)
.minItems(_.seq, 1)
The ValidatedData
can be any case class
: json data, form data, query params..
Create a file validation.sc
and paste this code into it:
//> using scala "3.4.2"
//> using dep ba.sake::sharaf:0.8.0
import io.undertow.Undertow
import ba.sake.querson.QueryStringRW
import ba.sake.tupson.JsonRW
import ba.sake.validson.Validator
import ba.sake.sharaf.*, routing.*
case class Car(brand: String, model: String, quantity: Int) derives JsonRW
object Car:
given Validator[Car] = Validator
.derived[Car]
.notBlank(_.brand)
.notBlank(_.model)
.nonNegative(_.quantity)
case class CarQuery(brand: String) derives QueryStringRW
object CarQuery:
given Validator[CarQuery] = Validator
.derived[CarQuery]
.notBlank(_.brand)
case class CarApiResult(message: String) derives JsonRW
val routes = Routes:
case GET() -> Path("cars") =>
val qp = Request.current.queryParamsValidated[CarQuery]
Response.withBody(CarApiResult(s"Query OK: ${qp}"))
case POST() -> Path("cars") =>
val json = Request.current.bodyJsonValidated[Car]
Response.withBody(CarApiResult(s"JSON body OK: ${json}"))
Undertow.builder
.addHttpListener(8181, "localhost")
.setHandler(
SharafHandler(routes).withExceptionMapper(ExceptionMapper.json)
)
.build
.start()
println(s"Server started at http://localhost:8181")
Then run it like this:
scala-cli validation.sc
Notice above that we used queryParamsValidated
and not plain queryParams
(does not validate query params).
Also, for JSON body parsing+validation we use bodyJsonValidated
and not plain bodyJson
(does not validate JSON body).
When you do a GET http://localhost:8181/cars?brand=
you will get a nice JSON error message with HTTP Status of 400 Bad Request
:
{
"instance": null,
"invalidArguments": [
{
"reason": "must not be blank",
"path": "$.brand",
"value": ""
}
],
"detail": "",
"type": null,
"title": "Validation errors",
"status": 400
}
The error message format follows the RFC 7807 problem detail.
When you do a POST http://localhost:8181/cars with a malformed body:
{
"brand": " ",
"model": "ML350",
"quantity": -5
}
you will get these errors:
{
"instance": null,
"invalidArguments": [
{
"reason": "must not be blank",
"path": "$.brand",
"value": " "
},
{
"reason": "must not be negative",
"path": "$.quantity",
"value": "-5"
}
],
"detail": "",
"type": null,
"title": "Validation errors",
"status": 400
}