Skip to content

Instantly share code, notes, and snippets.

@xasima
Created July 17, 2016 21:25
Show Gist options
  • Select an option

  • Save xasima/4d40da673e6029e2ed3f8aff7c61cbce to your computer and use it in GitHub Desktop.

Select an option

Save xasima/4d40da673e6029e2ed3f8aff7c61cbce to your computer and use it in GitHub Desktop.
circe in finch question
import com.twitter.app.Flag
import com.twitter.finagle.http.{Request, Response}
import com.twitter.finagle.{Http, ListeningServer, Service}
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.{Encoder, Json}
import io.finch._
import io.finch.circe._
import io.finch.circe.dropNullKeys._
import io.circe.generic.auto._
import io.circe.syntax._
import com.twitter.finagle.http.Message
import com.twitter.finagle.http.Message.ContentTypeJson
import com.twitter.io.Buf._
object RestServer extends TwitterServer {
// -- Model ----------------------------
sealed trait BaseModel
final case class Foo(id: String) extends BaseModel
final case class Bar(param: String) extends BaseModel
final case class FooBar(foo: Foo, bars:List[Bar]) extends BaseModel
// -- Backend Mock ----------------------------
def getFooList(a:String = "")= List(Foo("1"), Foo("2"))
def getBarList(a:String = "")= List(Bar("a"), Bar("b"))
def getFooBar() =FooBar(Foo("3"), List(Bar("c"),Bar("d")))
// -- Encoders ----------------------------
// QUESTION 1 - Expected to be able to use base type (BaseModel) encoder by default and
// overwrite encoder just for some specific types
// but this doesn't work
//implicit val baseEncoder = Encoder.instance[BaseModel] { f => f.asJson }
/* Error:(36, 7) not enough arguments for method asJson:
(implicit encoder: io.circe.Encoder[RestServer.BaseModel])io.circe.Json.
Unspecified value parameter encoder.
f.asJson
^
*/
// QUESTION 2 - Moreover, expected to see the build-in cicle encoder for case classes
implicit val fooEncoder = Encoder.instance[Foo] { f =>
f.asJson
}
implicit val barEncoder = Encoder.instance[Bar] { f =>
f.asJson
}
implicit val foobarEncoder = Encoder.instance[FooBar] { f =>
f.asJson
}
implicit def fooResponseEncoder: EncodeResponse[Foo] =
EncodeResponse(ContentTypeJson)(f => Utf8(Map("data" -> f).asJson.noSpaces))
implicit def barResponseEncoder: EncodeResponse[Bar] =
EncodeResponse(ContentTypeJson)(b => Utf8(Map("data" -> b).asJson.noSpaces))
// QUESTION 3: The same, why need to explicitly declare natural encoding for case classes
implicit def fooListResponseEncoder: EncodeResponse[List[Foo]] =
EncodeResponse(ContentTypeJson)(fooList => Utf8(Json.arr(fooList map {_.asJson}:_*).asJson.noSpaces))
implicit def barListResponseEncoder: EncodeResponse[List[Bar]] =
EncodeResponse(ContentTypeJson)(barList => Utf8(Json.arr(barList map {_.asJson}:_*).asJson.noSpaces))
// QUESTION 4: Is something may be omitted, relying on previous encoders ?
// {foo: {"id":"1"}, bars: [{param:"a"}, {"param":"b"}]
//
implicit def fooBarResponseEncoder: EncodeResponse[FooBar] =
EncodeResponse(ContentTypeJson)(f =>
Utf8(Map("foo" -> f.foo.asJson,
"bars" -> Json.arr(f.bars map {_. asJson}: _*)).asJson.noSpaces))
// -- Endpoints ----------------------------
val fooListApi: Endpoint[List[Foo]] = get("v1" :: "foos" ) {
Ok(getFooList())
}
val fooApi: Endpoint[Foo] = get("v1" :: "foos" :: string ) {
(a: String) =>
Ok(getFooList(a)(0))
}
val barListApi: Endpoint[List[Bar]] = get("v1" :: "bars" ) {
Ok(getBarList() )
}
val barApi: Endpoint[Bar] = get("v1" :: "bars" :: string ) {
(a: String) =>
Ok(getBarList(a)(0) )
}
val foobarApi: Endpoint[FooBar] = get("v1" :: "foobar" ) {
Ok(getFooBar)
}
// Finch-finagle service
val apiService: Service[Request, Response] =
(fooListApi :+: fooApi :+: barListApi :+: barApi :+: foobarApi).handle({
case e: Exception => NotFound(e)
}).toService
val port: Flag[Int] = flag("port", 8081, "TCP port for HTTP server")
def main(): Unit = {
val server: ListeningServer = Http.server.serve(s":${port()}", apiService)
println("Listening: " + port() )
onExit { server.close() }
Await.ready(adminHttpServer)
}
}
@xasima
Copy link
Copy Markdown
Author

xasima commented Jul 18, 2016

See below the new version with all custom trivial encoders removed. I want to use embedded Circe’ generic derivation for case classes and ADTs. But this leads to the inability to find out proper encoders

Error:(70, 8) You can only convert a router into a Finagle service if the result type of the router is one of
the following:

  • A Response
  • A value of a type with an EncodeResponse instance
  • A coproduct made up of some combination of the above
    shapeless.:+:[List[server.RestServer2.Foo],shapeless.:+:[server.RestServer2.Foo,shapeless.:+:[List[server.RestServer2.Bar],shapeless.:+:[server.RestServer2.Bar,shapeless.:+:[server.RestServer2.FooBar,shapeless.CNil]]]]] does not satisfy the requirement. You may need to provide an EncodeResponse instance for shapeless.:+:[List[server.RestServer2.Foo],shapeless.:+:[server.RestServer2.Foo,shapeless.:+:[List[server.RestServer2.Bar],shapeless.:+:[server.RestServer2.Bar,shapeless.:+:[server.RestServer2.FooBar,shapeless.CNil]]]]](or for some part of shapeless.:+:[List[server.RestServer2.Foo],shapeless.:+:[server.RestServer2.Foo,shapeless.:+:[List[server.RestServer2.Bar],shapeless.:+:[server.RestServer2.Bar,shapeless.:+:[server.RestServer2.FooBar,shapeless.CNil]]]]]).
    }).toService
    ^
package server

import com.twitter.app.Flag
import com.twitter.finagle.http.{Request, Response}
import com.twitter.finagle.{Http, ListeningServer, Service}
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.{Encoder, Json}

// All import of Circe' and Circe-Finch auto derivations seems to be presented
import io.finch._
import io.finch.circe._
import io.finch.circe.dropNullKeys._
import io.circe.generic.auto._
import io.circe.syntax._
import com.twitter.finagle.http.Message
import com.twitter.finagle.http.Message.ContentTypeJson
import com.twitter.io.Buf._


object RestServer2 extends TwitterServer  {

  // -- Model ----------------------------

  sealed trait BaseModel

  final case class Foo(id: String) extends BaseModel
  final case class Bar(param: String) extends BaseModel
  final case class FooBar(foo: Foo, bars:List[Bar]) extends BaseModel


  // -- Backend Mock ----------------------------

  def getFooList(a:String = "")= List(Foo("1"), Foo("2"))
  def getBarList(a:String = "")= List(Bar("a"), Bar("b"))
  def getFooBar() =FooBar(Foo("3"), List(Bar("c"),Bar("d")))


  // -- Encoders ----------------------------
  // No explicit encoders

  // -- Endpoints ----------------------------

  val fooListApi: Endpoint[List[Foo]] = get("v1" :: "foos" ) {
    Ok(getFooList())
  }

  val fooApi: Endpoint[Foo] =    get("v1" :: "foos" :: string ) {
    (a: String) =>
      Ok(getFooList(a)(0))
  }

  val barListApi: Endpoint[List[Bar]] =    get("v1" :: "bars" ) {
    Ok(getBarList() )
  }

  val barApi: Endpoint[Bar] =    get("v1" :: "bars" :: string ) {
    (a: String) =>
      Ok(getBarList(a)(0) )
  }

  val foobarApi: Endpoint[FooBar] =    get("v1" :: "foobar" ) {

    Ok(getFooBar)
  }


  // Finch-finagle service
  val apiService: Service[Request, Response] =
    (fooListApi :+: fooApi :+: barListApi :+: barApi :+: foobarApi).handle({
      case e: Exception => NotFound(e)
    }).toService

  val port: Flag[Int] = flag("port", 8081, "TCP port for HTTP server")

  def main(): Unit = {
    val server: ListeningServer = Http.server.serve(s":${port()}", apiService)
    println("Listening: " + port() )
    onExit { server.close() }
    Await.ready(adminHttpServer)
  }
}

The part of build.sbt class

libraryDependencies ++= Seq(
  "com.github.finagle" %% "finch-core" % "0.11.0-SNAPSHOT" changing(),
  "com.github.finagle" %% "finch-circe" % "0.11.0-SNAPSHOT" changing(),

   //JSON
  "io.circe" %% "circe-generic" % "0.5.0-M1",

  // Utilities
  "com.twitter"    %% "util-core"             % "6.35.0",
  "com.twitter"    %% "util-app"              % "6.35.0",
  "com.twitter"    %% "util-eval"             % "6.35.0",
  "com.twitter"    %% "twitter-server"        % "1.21.0",
  "com.netaporter" %% "scala-uri" % "0.4.14",
  "joda-time" % "joda-time" % "2.9.3",
  "org.joda" % "joda-convert" % "1.8",
  "com.github.benhutchison" %% "mouse" % "0.2",


  // Logging
  "ch.qos.logback" % "logback-core" % "1.1.7",
  "ch.qos.logback" % "logback-classic" % "1.1.7",
  "org.slf4j" % "slf4j-api" % "1.7.21",

  // Testing
  "org.specs2" %% "specs2-core" % "3.8.3" % "test",
  "org.specs2" %% "specs2-mock" % "3.8.3" % "test",
  "org.specs2" %% "specs2-scalacheck" % "3.8.3" % "test",
  "org.specs2" %% "specs2-cats" % "3.8.3" % "test",
  "org.hamcrest" % "hamcrest-core" % "1.3" % "test",
  "org.mockito" % "mockito-all" % "1.10.19" % "test",
//  "org.scalacheck" %% "scalacheck" % "1.13.1" % "test"

  "commons-io"     % "commons-io"             % "2.4",
   "org.scala-lang" % "scala-compiler"         % "2.11.8"
)

resolvers += "Twitter" at "http://maven.twttr.com"

resolvers += Resolver.sonatypeRepo("snapshots")

scalaVersion := "2.11.8"

And the content of project/build.properties

sbt.version=0.13.7

@xasima
Copy link
Copy Markdown
Author

xasima commented Jul 18, 2016

Thanks @vkostyukov! Fixed just by removing import io.finch.circe.dropNullKeys._ everywhere.
Discussion was held here https://gitter.im/finagle/finch?at=578c7988196179690eff3eca

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment