
Router , RequestReader and ResponseBuilder .Routerio.finch.route package implements the route combinators API, which allows you to build an infinite number of routers, combining them from primitive routers accessible from the box. Parser Combinators and scodec use the same approach.Router[A] is a function of Route => Option[(Route, A)] . Router takes the abstract Route route and returns Option from the remaining route and the extracted value of type A In other words, Router returns Some(...) in case of success (if the request was able to route it).int , long , string and boolean . In addition, there are routers that do not extract the value from the route, but simply associate it with a sample (for example, routers for HTTP methods: Get , Post ).router routes the GET /(users|user)/:id requests and extracts the integer id value from the route. Pay attention to the operator / (or andThen ), with the help of which we sequentially compose two routers, and also on the operator | (or orElse ), which allows you to compose two routers in terms of logical or . val router: Router[Int] => Get / ("users" | "user") / int("id") / . case class Ticket(userId: Int, ticketId: Int) val r0: Router[Int / Int] = Get / "users" / int / "tickets" / int val r1: Router[Ticket] = r0 map { case a / b => Ticket(a, b) } Service ) from the route. Such routers are called endpoints (in fact, Endpoint[Req, Rep] is just a type alias on Router[Service[Req, Rep]] ). Endpoint s can be implicitly converted to Finagle services ( Service ), which allows them to be used transparently with the Finagle HTTP API. val users: Endpoint[HttpRequest, HttpResponse] = (Get / "users" / long /> GetUser) | (Post / "users" /> PostUser) | (Get / "users" /> GetAllUsers) Httpx.serve(":8081", users) RequestReaderio.finch.request.RequestReader is key in Finch. Obviously, most of the REST API (without taking into account business logic) is reading and validating the request parameters. This is RequestReader . Like everything else in Finch, RequestReader[A] is a HttpRequest => Future[A] function. Thus, RequestReader[A] accepts an HTTP request and reads some value of type A from it. The result is placed in the Future , primarily in order to represent the stage of reading the parameters as an additional Future transformation (as a rule, the first) in the data-flow of the service. Therefore, if RequestReader returns Future.exception , no further transformations will be performed. This behavior is extremely convenient in 99% of cases when the service should not do any real work if one of its parameters is invalid.RequestReader title reads the required query-string “title” parameter or returns a NotPresent exception if the parameter is missing in the request. val title: RequestReader[String] = RequiredParam("title") def hello(name: String) = new Service[HttpRequest, HttpResponse] { def apply(req: HttpRequest) = for { t <- title(req) } yield Ok(s"Hello, $t $name!") } io.finch.request package provides a rich set of built-in RequestReader s for reading various information from an HTTP request: starting from query-string parameters and ending with cookies. All available RequestReader -y are divided into two groups - required (required) and optional (optional). Required readers read the value or exception NotPresent , optional - Option[A] . val firstName: RequestReader[String] = RequiredParam("fname") val secondName: RequestReader[Option[String]] = OptionalParam("sname") RequestReader provides an API with which you can compose two readers into one. There are two APIs: monadic (using flatMap ) and applicative (using ~ ). While monadic syntax looks familiar, it is highly recommended to use applicative syntax, which allows you to accumulate errors, while the fail-fast nature of monads returns only the first one. The example below shows both ways of composing a reader. case class User(firstName: String, secondName: String) // the monadic style val monadicUser: RequestReader[User] = for { firstName <- RequiredParam("fname") secondName <- OptionalParam("sname") } yield User(firstName, secondName.getOrElse("")) // the applicate style val applicativeUser: RequestReader[User] = RequiredParam("fname") ~ OptionalParam("sname") map { case fname ~ sname => User(fname, sname.getOrElse("")) } RequestReader allows RequestReader to read from the request values of types other than String. You can convert the readable value using the RequestReader.as[A] method. case class User(name: String, age: Int) val user: RequestReader[User] = RequiredParam("name") ~ OptionalParam("age").as[Int] map { case name ~ age => User(fname, age.getOrElse(100)) } as[A] method is an implicit DecodeRequest[A] type parameter. Type-class DecodeRequest[A] carries information about how type A can be obtained from a String . In case of conversion error, RequestReader will read NotParsed exception. Out of the box, conversions to Int , Long , Float , Double and Boolean types are supported.RequestReader implemented in the same way: we can use the as[Json] method if for Json there is an implementation of DecodeRequest[Json] in the current scope. In the example below, RequestReader user reads a user that is serialized in JSON format to the body of the HTTP request. val user: RequestReader[Json] = RequiredBody.as[Json] RequestReader greatly simplified. import io.finch.jackson._ case class User(name: String, age: Int) val user: RequestReader[User] = RequiredBody.as[User] RequestReader.should and RequestReader.shouldNot . There are two ways of validation: using inline rules and using ready-made ValidationRule . In the example below, the age reader reads the “age” parameter, provided it is greater than 0 and less than 120. Otherwise, the reader will read the NotValid exception. val age: RequestReader[Int] = RequiredParam("age").as[Int] should("be > than 0") { _ > 0 } should("be < than 120") { _ < 120 } io.finch.request package and the io.finch.request composers and from the ValidationRule . val age: RequestReader[Int] = RequiredParam("age").as[Int] should (beGreaterThan(0) and beLessThan(120)) ResponseBuilderio.finch.response package provides a simple API for building HTTP responses. It is considered common practice to use a specific ResponseBuilder corresponding to the response status code, for example, Ok or Created . val response: HttpResponse = Created("User 1 has been created") // plain/text response io.finch.response package is type-class EncodeResponse[A] . ResponseBuilder is able to build HTTP responses from any type A , if for it there is an implicit value EncodeResponse[A] in the current scope. This is how JSON support is implemented in ResponseBuilder : for each supported library there is an implementation of EncodeResponse[A] . The following code shows integration with the standard JSON implementation from the finch-json module. import io.finch.json._ val response = Ok(Json.obj("name" -> "John", "id" -> 0)) // application/json response ResponseBuilder adding implicit EncodeResponse[A] values for the required type to the current scope. For example, for type User . case class User(id: Int, name: String) implicit val encodeUser = EncodeResponse[User]("applciation/json") { u => s"{\"name\" : ${u.name}, \"id\" : ${u.id}}" } val response = Ok(User(10, "Bob")) // application/json response Source: https://habr.com/ru/post/250591/
All Articles