Releases: finagle/finch
Finch 0.5.0
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
RequestReader
s 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)
}
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 ValidationRule
s 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]
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)) => ???
}
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 toXBody
XArrayBody
renamed toXBinaryBody
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
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 onHttpRequest
: anyReq
type may be used insteadResponseBuilder
is no longer depend on JSON response type: any media type may be built with itRequestReader
is no longer depend on JSON: any typeA
may be read withRequiredBody[A]
andOptionalBody[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 toRequiredArrayBody
/OptionalArrayBody
RequiredJsonBody
/OptionalJsonBody
renamed toRequiredBody
/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
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 deprecatedServerBuilder
:
Await.ready(Httpx.serve(new InetSocketAddress(8080), endpoint.toService))
- An
Endpoint
companion object has anapply
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
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
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
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
This release are mostly about request reader:
- There are only async readers in Finch.io (no country for
Param
, onlyRequiredParam
andOptionalParam
, only hard-core) - New request reader that reads constant value:
ConstParam()
(useful for passing URI params) - Headers may be fetched with
RequiredHeader
andOptionalHeader
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
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
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
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