Skip to content

Finch 0.5.0

Compare
Choose a tag to compare
@vkostyukov vkostyukov released this 13 Feb 17:30
· 1912 commits to master since this release

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!