Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toService API #61

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ enablePlugins(TutPlugin)

lazy val buildSettings = Seq(
organization := "io.github.finagle",
version := "0.3.3",
scalaVersion := "2.12.2",
crossScalaVersions := Seq("2.11.11", "2.12.2")
version := "0.4.0-SNAPSHOT",
scalaVersion := "2.12.4",
crossScalaVersions := Seq("2.11.11", "2.12.4")
)

val finagleVersion = "17.12.0"
val shapelessVersion = "2.3.3"
val catsVersion = "1.0.1"
val circeVersion = "0.9.0"

lazy val docSettings = Seq(
autoAPIMappings := true
Expand All @@ -29,8 +30,9 @@ lazy val baseSettings = docSettings ++ Seq(
"org.scalatest" %% "scalatest" % "3.0.3" % "test"
),
resolvers += Resolver.sonatypeRepo("snapshots"),
dependencyUpdatesFailBuild := false,
dependencyUpdatesExclusions := moduleFilter("org.scala-lang")
scalacOptions ++= Seq(
"-Ypartial-unification"
)
)

lazy val publishSettings = Seq(
Expand Down Expand Up @@ -75,7 +77,13 @@ lazy val `featherbed-core` = project
.settings(allSettings)

lazy val `featherbed-circe` = project
.settings(allSettings)
.settings(
libraryDependencies ++= Seq(
"io.circe" %% "circe-parser" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion % "test"
),
allSettings
)
.dependsOn(`featherbed-core`)

val scaladocVersionPath = settingKey[String]("Path to this version's ScalaDoc")
Expand All @@ -101,6 +109,9 @@ lazy val docs: Project = project
case _ => Nil
}
)
),
libraryDependencies ++= Seq(
"io.circe" %% "circe-generic" % circeVersion
)
).dependsOn(`featherbed-core`, `featherbed-circe`)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/01-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Add the following to build.sbt
resolvers += Resolver.sonatypeRepo("snapshots")

libraryDependencies ++= Seq(
"io.github.finagle" %"featherbed_2.11" %"0.3.0"
"io.github.finagle" %"featherbed_2.11" %"0.3.1"
)
```
Next, read about [Basic Usage](02-basic-usage.html)
64 changes: 33 additions & 31 deletions docs/src/main/tut/02-basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Create a `Client`, passing the base URL to the REST endpoints:
import java.net.URL
val client = new featherbed.Client(new URL("http://localhost:8765/api/"))
```

*Note:* It is important to put a trailing slash on your URL. This is because the resource path you'll pass in below
is evaluated as a relative URL to the base URL given. Without a trailing slash, the `api` directory above would be
lost when the relative URL is resolved.
Expand All @@ -41,9 +42,10 @@ Now you can make some requests:
```tut:book
import com.twitter.util.Await

Await.result {
val request = client.get("test/resource").send[Response]()
request map {
val request = client.get("test/resource").toService[Response]

Await.result {
request() map {
response => response.contentString
}
}
Expand All @@ -61,30 +63,30 @@ Here's an example of using a `POST` request to submit a web form-style request:
```tut:book
import java.nio.charset.StandardCharsets._

val request = client.
post("another/resource").
withCharset(UTF_8).
withHeaders("X-Foo" -> "scooby-doo").
form.toService[Map[String, String], Response]

Await.result {
client
.post("another/resource")
.withParams(
"foo" -> "foz",
"bar" -> "baz")
.withCharset(UTF_8)
.withHeaders("X-Foo" -> "scooby-doo")
.send[Response]()
.map {
response => response.contentString
}
request(Map("foo" -> "foz", "bar" -> "baz")) map {
response => response.contentString
}
}
```

Here's how you might send a `HEAD` request (note the lack of a type argument to `send()` for a HEAD request):

```tut:book
val request = client.head("head/request").toService

Await.result {
client.head("head/request").send().map(_.headerMap)
request().map(_.headerMap)
}
```

A `DELETE` request:
A `DELETE` request (using the `send` syntax rather than `toService`):

```tut:book
Await.result {
Expand All @@ -94,34 +96,34 @@ Await.result {
}
```

And a `PUT` request - notice how content can be provided to a `PUT` request by giving it a `Buf` buffer and a MIME type
to serve as the `Content-Type`:
And a `PUT` request - notice how a `Content-Type` is provided to the `toService` method, and we use `Buf` in this
example to provide the content of the request.

```tut:book
import com.twitter.io.Buf

val request = client.
put("put/request").
toService[Buf, Response]("text/plain")

Await.result {
client.put("put/request")
.withContent(Buf.Utf8("Hello world!"), "text/plain")
.send[Response]()
.map {
response => response.statusCode
}
request(Buf.Utf8("Hello world!")).map {
response => response.statusCode
}
}
```

You can also provide content to a `POST` request in the same fashion:

```tut:book
import com.twitter.io.Buf
val request = client.
post("another/post/request").
toService[Buf, Response]("text/plain")

Await.result {
client.post("another/post/request")
.withContent(Buf.Utf8("Hello world!"), "text/plain")
.send[Response]()
.map {
response => response.contentString
}
request(Buf.Utf8("Hello world!")).map {
response => response.contentString
}
}
```

Expand Down
26 changes: 13 additions & 13 deletions docs/src/main/tut/03-content-types-and-encoders.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ import featherbed.circe._
// An ADT for the request
case class Foo(someText : String, someInt : Int)

// It can be passed directly to the POST
val req = client.post("foo/bar").withContent(Foo("Hello world!", 42), "application/json")
// Create a service for the request
val req = client.post("foo/bar").
toService[Foo, Response]("application/json")

// We can pass Foo directly to the service
val result = Await.result {
req.send[Response]() map {
req(Foo("Hello world!", 42)) map {
response => response.contentString
}
}
Expand All @@ -69,18 +71,16 @@ type's companion object. See the Circe documentation for more details about JSO

### A Note About Evaluation

You may have noticed that above we created a value called `req`, which held the result of specifying the request
type and its parameters. We later called `send[Response]` on that value and `map`ped over the result to specify a
You may have noticed that above we created a value called `req`, which held the result of specifying the request type
and creating a service. We later called `req()` on that value and `map`ped over the result to specify a
transformation of the response.

It's important to note that the request itself **is not performed** until the call to `send`. Until that call is made,
you will have an instance of some kind of request, but you will not have a `Future` representing the response. That is,
the request itself is *lazy*. The reason this is important to note is that `req` itself can actually be used to make
the same request again. If another call is made to `send`, a new request of the same parameters will be initiated and a
new `Future` will be returned. This can be a useful and powerful thing, but it can also bite you if you're unaware.

For more information about lazy tasks, take a look at scalaz's `Task` or cats's `Eval`. Again, this is important to
note, and is different than what people are used to with Finagle's `Future` (which is not lazy).
It's important to note that the request itself **is not performed** until the call to `Service#apply`. Until that call
is made, you will have an instance of some kind of request, but you will not have a `Future` representing the response.
That is, the request itself is *lazy*. The reason this is important to note is that `req` itself can actually be used
to make the same request again. If another call is made to `req()`, a new request of the same configuration will be
initiated with the new input and a new `Future` will be returned. This can be a useful and powerful thing, but it
can also bite you if you're unaware.

### A Note About Types

Expand Down
7 changes: 5 additions & 2 deletions docs/src/main/tut/05-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,13 @@ When using the `send[T]` method, the resulting `Future` will *fail* if the serve
in order to handle an error, you must handle it at the `Future` level using the `Future` API:

```tut:book:nofail
val req = client.get("not/found").accept("application/json")
val req = client.
get("not/found").
accept("application/json").
toService[Foo]

Await.result {
req.send[Foo]().handle {
req().handle {
case ErrorResponse(request, response) =>
throw new Exception(s"Error response $response to request $request")
}
Expand Down
9 changes: 0 additions & 9 deletions featherbed-circe/build.sbt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import java.nio.charset.Charset
import cats.data.ValidatedNel
import cats.implicits._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import shapeless.Witness
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package featherbed.circe

import cats.implicits._
import com.twitter.util.Future
import io.circe._
import io.circe.generic.auto._
import io.circe.parser.parse
import io.circe.syntax._
import org.scalatest.FlatSpec
import shapeless.{Coproduct, Witness}
import shapeless.union.Union
import shapeless.Coproduct

case class Foo(someText: String, someInt: Int)

Expand Down Expand Up @@ -52,9 +50,9 @@ class CirceSpec extends FlatSpec {
}

"API example" should "compile" in {
import shapeless.Coproduct
import java.net.URL
import com.twitter.util.Await
import shapeless.Coproduct
case class Post(userId: Int, id: Int, title: String, body: String)

case class Comment(postId: Int, id: Int, name: String, email: String, body: String)
Expand Down
Loading