Skip to content

Releases: finagle/finch

Finch 0.5.0

13 Feb 17:30
Compare
Choose a tag to compare

This release is probably the most important and big one since 0.1.0. A bunch of cool and fancy features as well as some breaking API changes are introduced here. We decided to get them all out of the way at once rather than incrementally breaking API. Although, we don't plan to make big API changes in further releases. So, this release might be treated as early pre-alpha 1.0.0.

New Features

We will start with new features. There are two of them:

  • New route combinators API
  • New request reader API

Route Combinators

The route combinators API is introduced in favour of io.finch.Endpoint. While we haven't deprecated the old-style endpoint yet, it's highly recommended to consider migration to the new API. The example bellow, shows how the old-style endpoint might be rewritten using the route combinators API.

// The old-style endpoint
object Users extends Endpoint {
  def route {
    case Method.Get -> Root / "users" => GetAllUsers
    case Method.Get -> Root / "users" / id => GetUser(id)
  }
}

// The new-style endpoint
val users = 
  (Get / "users" /> GetAllUsers) |
  (Get / "users" / int /> GetUser)

See docs on routers for more details.

Applicative Request Reader

RequestReaders applicative behaviour introduces a new way to compose readers together. In addition to monadic API, request readers are support applicative API via operand ~. The main advantage of using the applicative API is that it allows to collect errors rather then fail-fast. We recommend to migrate the monadic request readers to applicative request readers and respond clients the rich set of errors rather then only first one. The following example shows how monadic request reader might be rewritten in the applicative style.

// the monadic style
val monadicUser = for {
  name <- RequiredParam("name")
  age <- RequiredIntParam("age")
} yield User(name, age)

// the applicate style
val applicativeUser = 
  RequiredParam("name") ~ RequiredParam("age") map {
    case name ~ age => User(name, age)
  }

See docs on applicative API.

Reusable Validation Rules

This item goes to features but it also brakes the current API around ValidationRule. Since version 0.5.0 validation rule is no longer an instance of RequestReader but standalone abstraction, which might be composed with request readers. Brand new request reader API introduces a new approach of validating request items. Instead of old-style ValidationRule the should or shouldNot methods should be used. The following example shows the difference between the old-style and new-style validation.

// the old-style validation
val oldNumber = for {
  n <- RequiredIntParam("n")
  _ <- ValidationRule("n", "should be greater than 0") { n > 0 }
  _ <- ValidationRule("n", "should be less than 100") { n < 100 }
} yield n

// the new-style inline validation
val newNumber = 
  RequiredIntParam("n") should("be greater than 0") { _ > 0 } should("be less than 100") { _ < 100 }

The most beauty thing about new validation is that reusable ValidationRules might dramatically simplify the example above. Keeping in mind that validation rules are composable and there are built-in rules beGreaterThan and beLessThan, the example might be rewritten as shown bellow.

val newNumber = RequiredIntParam("n") should (beGreaterThan(0) and beLessThan(100))

See docs on request validation.

as[A] method as a replacement for typed readers

This release also introduces new style of defining typed readers, i.e., RequiredIntParam, OptionalFloatParam, etc. It's recommended to use new as[A] API instead. The following example shows the difference.

// old-style typed reader
val a = RequredIntParam("a")

// new-style typed reader
val b = RequiredParam("a").as[Int]

See docs on type conversions.

Breaking API Changes

Request Reader Errors

The whole exceptions hierarchy was revised. We ended up having just three base exceptions thrown by RequestReader: NotPresent, NotParsed and NotValid. The following example shows a pattern according to which exception handling should be changed.

// the old-style handling
x handle {
  case ParamNotFound(param) => ???
}

// the new style-handling
y handle {
  case NotPresent(ParamItem(param)) => ???
}

See docs on error handling.

Renamed Things

The only thing we renamed in this release is body readers. In the following list X means either Required or Optional.

  • XStringBody renamed to XBody
  • XArrayBody renamed to XBinaryBody

Downloads

Grad the new release at Maven Central:

libraryDependencies ++= Seq(
  "com.github.finagle" %% "[finch-module]" % "0.5.0"
)

Thanks to awesome contributors @jenshalm, @rpless, @travisbrown who made this release possible!

Finch 0.4.0

19 Jan 07:43
Compare
Choose a tag to compare

Highlights

  • New package jackson - a Jackson support
  • New method ResponseBuilder.withCookies()
  • Updated demo with custom request type and implicit conversion: Seq[ToJson] => ToJson
  • Scaladoc

Finch becomes more general! Three major components of Finch were generalised in this release:

  • Endpoint is no longer depend on HttpRequest: any Req type may be used instead
  • ResponseBuilder is no longer depend on JSON response type: any media type may be built with it
  • RequestReader is no longer depend on JSON: any type A may be read with RequiredBody[A] and OptionalBody[A]
Implicit view Req => HttpRequest instead sub-typing

Finch moved forward to composition from inheritance. Since, 0.4.0 release, an Endpoint changed its type-parameters bound from Endpoint[Req <: HttpRequest] to Endpoint[Req, Rep]. Thus, in fact, any request type may be used in the endpoint, even the custom case class like

// we compose MyRequest with HttpRequest but not extend it
case class MyRequest(http: HttpRequest)
val e: Endpoint[MyRequest, HttpResponse]

There is also an implicit conversion in io.finch._ that converts Endpoint[Req, Rep] to Finagle Service[Req, Rep]. Thus, the method Endpoint.toService is no longer exist. In order to enable the implicit conversion there is should be an implicit view Req => HttpRequest available in the scope. For example for MyRequest it looks like:

implicit val myReqEv = (req: MyRequest) => req.http

Having an implicit view imported in the scope, an Endpoint may be treated as a usual Finagle Service:

case class MyRequest(http: HttpRequest)
implicit val myReqEv = (req: MyRequest) => req.http
val e: Endpoint[MyRequest, HttpResponse]
// an endpoint `e` will be converted to service implicitly
Httpx.serve(new InetSocketAddress(8081), e)

Note, that in case of using pure HttpRequest and HttpEndpoint there is no need to define an implicit view from HttpRequest => HttpRequest since it's already defined in Scala's Predef.

This new functionality is also supported by RequestReader, which previously had a signature:

trait RequestReader[A] {
  def apply(req: HttpRequest): Future[A]
}

Since release 0.4.0 RequestReader takes any request type, which has an implicit view to HttpRequest. In fact, signature has been changed to:

trait RequestReader[A] {
  def apply[Req](req: Req)(implicit ev: Req => HttpRequest): Future[A]
}

This allows to use requests readers smoothly even with custom request types like MyRequest:

case class MyRequest(http: HttpRequest)
implicit val myReqEv = (req: MyRequest) => req.http
val req: MyRequest = ???
// we don't need to call it as `req.http` since there is an implicit view available
val s: Future[String] = RequiredParam("name")(req)

Finch provides the developers all the suitable abstractions to switch from inheritance to composition. While it's still possible to extends HttpRequest and pass it around, the composition in form MyRequest(http: HttpRequest) is preferred.

Generalized EncodeResponse/DecodeRequest instead of EncodeJson/DecodeJson

Finch moved away from JSON dependency to general concept of some type A that might be decoded from request using the DecodeRequest and encoded to response using the EncodeResoponse. This gives the opportunity to support not just JSON but any format (i.e., XML, EDN or even custom case class). Unless it looks like a big change, the JSON support via pluggable libraries remanned the same with type changes in API:

  • RequiredBody/OptionalBody renamed to RequiredArrayBody/OptionalArrayBody
  • RequiredJsonBody/OptionalJsonBody renamed to RequiredBody/OptionalBody

Thus the usage of new API with finch-json looks like:

val readJson: RequestReader[Json] = RequiredBody[Json]

The following example demonstrates the power of new concept by defining a custom decoder for Double values. Thus the double values encoded in request body may be read with predefined reader RequiredBody.

implicit val decodeDouble = new DecodeRequest[Double] {
  def apply(s: String): Option[Double] =
    try { Some(s.toDouble) } catch { case _: NumberFormatException => None }
}
val req: HttpRequest = ???
val readDouble: RequestReader[Double] = RequiredBody[Double]
val double = readDouble(req)

Grab on Maven Central:

libraryDependencies ++= Seq(
  "com.github.finagle" %% "[finch-module]" % "0.4.0"
)

As always kudos to awesome contributors: @pasviegas, @rodrigopr, @rpless, @travisbrown!

Finch 0.3.0

16 Dec 18:26
Compare
Choose a tag to compare

This is another big step for Finch. An enormous work has been done by core contributors: @benjumanji, @rodrigopr and @rpless. Thank you guys!

The following changes are made:

  • Finch is on Scala 2.11.4!
  • Finch is now on finagle-httpx (Netty-free implementation of HTTP), which means that new client/server construction API may be used instead of deprecated ServerBuilder:
Await.ready(Httpx.serve(new InetSocketAddress(8080), endpoint.toService))
  • An Endpoint companion object has an apply method that builds new endpoints on the fly:
val e = Endpoint[String, String] {
  case Method.Get -> Root / "a" => ???
  case Method.Get -> Root / "b" => ???
}
  • Futures may be used instead of services in the Endpoint route's destination:
val e = Endpoint[String, String] {
  case Method.Get -> Root / "a" -> "a".toFuture
  case Method.Get -> Root / "b" -> stringService
}
  • Finch supports the Argonaut library that provides purely functional JSON API.
  • The code coverage has been dramatically improved.

Grab it on Maven Central:

libraryDependencies ++= Seq(
  "com.github.finagle" %% "finch-module" % "0.3.0"
)

Finch.io 0.2.0

05 Dec 06:10
Compare
Choose a tag to compare

This release is a result of a huge project redesign: instead of mono-project, it's multi-project now. We've managed to resolve the plugabble JSON libraries ticket. So, there are two new JSON modules in Finch.io: finch-json (own immutable JSON) and finch-jawn (bindings to Jawn library). Finally, Finch.io is on Maven Central now:

"com.github.finagle" %% "finch-module" % "0.2.0"

Many thanks to @BenWhitehead, @rpless, @travisbrown, @rodrigopr!

Finch.io 0.1.6

19 Aug 03:17
Compare
Choose a tag to compare

This release is a result of @rpless's hard work on tests. All the major components are tested now. Also a couple of minor bugs has been fixed during testing.

The documentation has been improved.

Finch.io 0.1.5

09 Aug 02:58
Compare
Choose a tag to compare

This release contains the following changes:

  • Better names for ConstParam/NoParams: ConstReader/EmptyReader
  • Support for double/float params via RequiredDoubleParam, OptionalDoubleParam, etc.
  • Redirect factory object that generates redirect microservices. Thanks @rpless!

Finch.io 0.1.4

31 Jul 16:59
Compare
Choose a tag to compare

This release are mostly about request reader:

  • There are only async readers in Finch.io (no country for Param, only RequiredParam and OptionalParam, only hard-core)
  • New request reader that reads constant value: ConstParam() (useful for passing URI params)
  • Headers may be fetched with RequiredHeader and OptionalHeader readers

So, all the things are build around common interface of RequestReader, which means everything may be composed in a single for-comprehension chain:

val magic = for {
  a <- RequiredParam("a")
  b <- OptionalParam("b")
  c <- RequiredHeader("c")
  d <- ConstParam(10)
  _ <- ValidationRule("d", "should be 10") { d == 10 }
} yield Seq(a, b, c, d)

Enjoy!

Finch.io 0.1.3

29 Jul 11:49
Compare
Choose a tag to compare

This release contains several functional improvements:

ValidationRule accepts param name in order to pass it into the exception. So, a fancy errors may be generated out the expections ParamNotFound and ValidationFailed

val params = for {
  name <- RequiredParam("name")
  _ <- ValidationRule("name", "should be longer then 5 chars") { name.length > 5 }
} yield name

val a = new Service[HttpRequest, HttpResponse] {
  def apply(req: HttpRequest) = for {
    name <- params(req)
  } yield Ok()
}

val req: HttpRequest = ???

a(req) handle {
  case e: ParamNotFound => BadRequest(s"${e.param} not found")
  case e: ValidationFailed => BadRequest(s"${e.param} is not validated: ${e.rule}")
}

Also, a Respond builder may consume HTTP headers with withHeaders method:

val ok: Respond = Ok.withHeaders("Header-A" - > "a", "Header-B" -> B)
val rep: HttpRequest = ok()

Also, a new right-hand-side operand for pipe ! operator is pure service. Services, filters and endpoints may be piped to other service like following.

Composing services:

val ab: Service[A, B] = ???
val bc: Service[B, C] = ???
val ac: Service[A, C] = ab ! bc

Composing endpoints:

val ab: Endpoint[A, B] = ???
val bc: Endpoint[B, C] = ???
val ac: Endpoint[A, C] = ab ! bc

Finally, TurnJsonIntoHttp is no longer a filter but pure service. So, it may be constructred without request type.

Finch.io 0.1.2

26 Jul 12:10
Compare
Choose a tag to compare

The package structure is improved in this release. Also a setup function is no longer required for converting the endpoint into service. Facets are just a special kind of filters: filters that don't change request type but change the response type. Finally, everything (filters, services and endpoints) may be piped with pipe ! operator.

See more details in README.md file.

Enjoy!

Finch.io 0.1.1

23 Jul 14:49
Compare
Choose a tag to compare

This release contains cosmetic code improvements and also HTTP Basic Auth support via BasicallyAuthorize finagled filter.

Usage:

val respond: Service[HttpRequest, HttpResponse] = ???
val protectedRespond = BasicallyAuthorize("user", "password") andThen respond