Trace4Cats example for Tapir endpoints - scala

I am learning about different Scala libraries and I got to tracing. Trace4Cats claims integration with Tapir endpoints and I want to include it inside my example Play SIRD router that uses Tapir with OpenAPI documentation.
So far I've included these dependencies for tracing:
// Tracing
libraryDependencies += "io.janstenpickle" %% "trace4cats-core" % trace4CatsVersion
libraryDependencies += "io.janstenpickle" %% "trace4cats-inject" % trace4CatsVersion
libraryDependencies += "io.janstenpickle" %% "trace4cats-avro-exporter" % trace4CatsVersion
libraryDependencies += "io.janstenpickle" %% "trace4cats-sttp-tapir" % trace4CatsVersion
libraryDependencies += "io.janstenpickle" %% "trace4cats-datadog-http-exporter" % trace4CatsVersion
I have a working Tapir example with Play Framework's SIRD router, as suggested by Tapir docs. Here is the ApiRouter:
#Singleton
class ApiRouter #Inject() (implicit mat: Materializer) extends SimpleRouter {
// Interpreter
private val interpreter = PlayServerInterpreter()
// Controller logic
def countCharacters(s: String): Future[Either[Unit, Int]] =
Future(Right[Unit, Int](s.length))
// Endpoint
val countCharactersEndpoint: PublicEndpoint[String, Unit, Int, Any] =
endpoint
.tag("Example API")
.in("count")
.in(query[String]("string"))
.out(plainBody[Int])
.errorOut(
statusCode(StatusCode.NotFound)
)
// Route
val countCharactersRoutes: Routes =
interpreter.toRoutes(countCharactersEndpoint.serverLogic(countCharacters))
// OpenAPI
private val openApiDocs: OpenAPI = OpenAPIDocsInterpreter().toOpenAPI(
List(countCharactersEndpoint),
"Tapir Play Sample",
"1.0.0"
)
// Doc will be on /docs
private val openApiRoute: Routes = interpreter.toRoutes(SwaggerUI[Future](openApiDocs.toYaml))
// Router
override def routes: Routes =
openApiRoute
.orElse(countCharactersRoutes)
}
I have tried to search Trace4Cats docs on how to integrate it with Tapir, but all I have found is other examples, including STTP, but I'm not sure of the syntax for Tapir. I need help from someone that has experience with Trace4Cats (or Natchez or any other solution that can work here). Help is greatly appreciated.

Your question is too broad to give a precise answer but I would recommend you to look at the tests and examples on the GitHub repository of trace4cats: https://github.com/trace4cats/trace4cats-sttp/tree/master/modules/sttp-tapir/src/test/scala/io/janstenpickle/trace4cats/sttp/tapir

Related

How to exclude logging (like logback-classic) from jar published by sbt

My Scala project has a libraryDependency on slf4j because I use the API for logging. I also want to see the logging output while running from sbt or IntelliJ, both for the Apps that runMain and the unit tests that testOnly from sbt. Therefore there is also a libraryDependency on logback-classic. However, I do not want that second dependency published because of the convention stated below. When someone uses my published library, the transitive dependency should not be automatically brought in. How should that be done? I don't want to explain to the user how to manually exclude the transitive dependency, because they might be using any number of different tools. The logback-classic should continue to be included in an assembled jar, however, if at all possible. It doesn't seem like exclude() is the answer.
"Embedded components such as libraries or frameworks should not declare a dependency on any SLF4J binding/provider [like logback-classic] but only depend on slf4j-api. When a library declares a transitive dependency on a specific binding, that binding is imposed on the end-user negating the purpose of SLF4J. Note that declaring a non-transitive dependency on a binding, for example for testing, does not affect the end-user."
Publish the jar with slf4j-api but use the sbt Test configuration for logback. Unit tests will then have a concrete implementation but it won't be packaged in your artifact.
libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % "1.7.36",
"ch.qos.logback" % "logback-classic" % "1.2.11" % Test
)
This would be a project with sub-projects. Your sample app uses a concrete implementation, but not the library. Anyone using the library would provide their own.
lazy val root = (project in file("."))
.settings(
publish / skip := true,
)
.aggregate(sampleApp, theLibrary)
lazy val sampleApp = project
.settings(
publish / skip := true,
libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.2.11"
)
)
.dependsOn(theLibrary % "test->test;compile->compile")
lazy val theLibrary = project
.settings(
libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % "1.7.36",
"ch.qos.logback" % "logback-classic" % "1.2.11" % Test
)
)
My tentative solution is to add this code to an sbt file
ThisBuild / pomPostProcess := {
val logback = DependencyId("ch.qos.logback", "logback-classic")
val rule = DependencyFilter { dependencyId =>
dependencyId != logback
}
(node: Node) => new RuleTransformer(rule).transform(node).head
}
and back it up with this Scala code in the project directory
package org.clulab.sbt
import scala.xml.Node
import scala.xml.NodeSeq
import scala.xml.transform.RewriteRule
case class DependencyId(groupId: String, artifactId: String)
abstract class DependencyTransformer extends RewriteRule {
override def transform(node: Node): NodeSeq = {
val name = node.nameToString(new StringBuilder()).toString()
name match {
case "dependency" =>
val groupId = (node \ "groupId").text.trim
val artifactId = (node \ "artifactId").text.trim
transform(node, DependencyId(groupId, artifactId))
case _ => node
}
}
def transform(node: Node, dependencyId: DependencyId): NodeSeq
}
class DependencyFilter(filter: DependencyId => Boolean) extends DependencyTransformer {
def transform(node: Node, dependencyId: DependencyId): NodeSeq =
if (filter(dependencyId)) node
else Nil
}
object DependencyFilter {
def apply(filter: DependencyId => Boolean): DependencyFilter = new DependencyFilter(filter)
}
I'm still hoping to find a similar solution for editing ivy.xml.

How to use a standalone WSClient in a Scala application

I would like to use ws in a standalone application. Trying this code, copied from https://gist.github.com/cdimascio/46b2b7d2986636c1189c :
import com.ning.http.client.AsyncHttpClientConfig
import play.api.libs.ws.ning._
import play.api.libs.ws._
// provide an execution context
import scala.concurrent.ExecutionContext.Implicits.global
object WSStandaloneTest {
def main(args: Array[String]) {
// set up the client
val config = new NingAsyncHttpClientConfigBuilder(DefaultWSClientConfig()).build
val builder = new AsyncHttpClientConfig.Builder(config)
val client = new NingWSClient(builder.build)
// execute a GET request
val response = client.url("http://www.example.com").get
// print the response body
response.foreach(r => {
println(r.body)
// not the best place to close the client,
// but it ensures we dont close the threads before the response arrives
// Good enough for the gist :-D
client.close()
})
}
}
Results in the following error:
[error] object ning is not a member of package play.api.libs.ws
[error] import play.api.libs.ws.ning._
In my build.sbt I have this:
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.1"
libraryDependencies += "com.typesafe.play" %% "play-ws" % "2.6.1"
What am I doing wrong?
NingWSClient is deprecated in Play! 2.5.x.
In 2.6.x
The ning package has been replaced by the ahc package, and the Ning* classes replaced by AHC*.
There is a migration guide available in the official doc.
So you can choose to downgrade to 2.5.x and use ning or update the code.

Load Balancing akka-http

I am using akka-http, my build.sbt configuration is:
scalaVersion := "2.11.7"
libraryDependencies += "com.typesafe.akka" % "akka-actor_2.11" % "2.4.2"
libraryDependencies += "com.typesafe.akka" % "akka-http-experimental_2.11" % "2.4.2"
libraryDependencies += "com.typesafe.akka" % "akka-http-spray-json-experimental_2.11" % "2.4.2"
libraryDependencies += "com.typesafe.akka" % "akka-slf4j_2.11" % "2.4.2"
I am exposing a simple REST api with only one GET url
foo is a function that returns a Future
implicit val actorSystem = ActorSystem("system", config)
implicit val actorMaterializer = ActorMaterializer()
val route: Route = {
get {
path("foo") {
complete { foo }
}
}
}
The web-service is expected to have a lot of calls, and I want to make the service redundant in case of failure, so I want to have two instances running simultaneously to handle all the requests.
1) what is the best way to have two intances of the web-service handling the requests simultaneously?, with an external load balancer or with some magic (wich I don't know) inside akka/akka-http ?
2) What are the principal parameters I have to tune up to improve perfomance?
The answer to this question demonstrates how to make an Actor call from within a Route.
If you combine that technique with the clustering capabilities within Akka you should be able to get the job done.
Have your Route send a message to a Router that will dispatch the message to 1 of N remotely deployed Actors (from your question it sounds like a round robin router is what you want).
class HttpResponseActor extends Actor {
def foo : HttpResponse = ??? // Not specified in question
override def receive = {
case _ : HttpRequest => foo
}
}
import akka.actor.{ Address, AddressFromURIString }
import akka.remote.routing.RemoteRouterConfig
val addresses = Seq(Address("akka.tcp", "remotesys", "otherhost", 1234),
AddressFromURIString("akka.tcp://othersys#anotherhost:1234"))
val routerRemote =
system.actorOf(RemoteRouterConfig(RoundRobinPool(5), addresses).props(Props[HttpResponseActor]))
The remote Actor responds with the HttpResponse. This Response can go through the Router or directly back to the Route.
The Route sticks the answer in a complete Directive to return back to the client.
val route =
get {
path("foo") {
onComplete((routerRemote ? request).mapTo[HttpResponse]) {
case Success(response) => complete(response)
case Failure(ex) => complete((InternalServerError, s"Actor not playing nice: ${ex.getMessage}"))
}
}
}

Scala enumeration serialization in jersey/jackson is not working for me

I've read the jackson-module-scala page on enumeration handling (https://github.com/FasterXML/jackson-module-scala/wiki/Enumerations). Still I'm not getting it to work. The essential code goes like this:
#Path("/v1/admin")
#Produces(Array(MediaType.APPLICATION_JSON + ";charset=utf-8"))
#Consumes(Array(MediaType.APPLICATION_JSON + ";charset=utf-8"))
class RestService {
#POST
#Path("{type}/abort")
def abortUpload(#PathParam("type") typeName: ResourceTypeHolder) {
...
}
}
object ResourceType extends Enumeration {
type ResourceType = Value
val ssr, roadsegments, tmc, gab, tne = Value
}
class ResourceTypeType extends TypeReference[ResourceType.type]
case class ResourceTypeHolder(
#JsonScalaEnumeration(classOf[ResourceTypeType])
resourceType:ResourceType.ResourceType
)
This is how it's supposed to work, right? Still I get these errors:
Following issues have been detected:
WARNING: No injection source found for a parameter of type public void no.tull.RestService.abortUpload(no.tull.ResourceTypeHolder) at index 0.
unavailable
org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] No injection source found for a parameter of type public void no.tull.RestService.abortUpload(no.tull.ResourceTypeHolder) at index 0.; source='ResourceMethod{httpMethod=POST, consumedTypes=[application/json; charset=utf-8], producedTypes=[application/json; charset=utf-8], suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class no.tull.RestService, handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor#7ffe609f]}, definitionMethod=public void no.tull.RestService.abortUpload(no.tull.ResourceTypeHolder), parameters=[Parameter [type=class no.tull.ResourceTypeHolder, source=type, defaultValue=null]], responseType=void}, nameBindings=[]}']
at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:467)
at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:163)
at org.glassfish.jersey.server.ApplicationHandler$3.run(ApplicationHandler.java:323)
at org.glassfish.jersey.internal.Errors$2.call(Errors.java:289)
at org.glassfish.jersey.internal.Errors$2.call(Errors.java:286)
I have also assembled a tiny runnable project (while trying to eliminate any other complications) that demonstrates the problem: project.tgz
Update: Created an sbt-file to see if gradle was building a strange build. Got the same result, but this is the build.sbt:
name := "project"
version := "1.0"
scalaVersion := "2.10.4"
val jacksonVersion = "2.4.1"
val jerseyVersion = "2.13"
libraryDependencies ++= Seq(
"com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion,
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion,
"com.fasterxml.jackson.jaxrs" % "jackson-jaxrs-json-provider" % jacksonVersion,
"com.fasterxml.jackson.jaxrs" % "jackson-jaxrs-base" % jacksonVersion,
"com.fasterxml.jackson.module" % "jackson-module-scala_2.10" % jacksonVersion,
"org.glassfish.jersey.containers" % "jersey-container-servlet-core" % jerseyVersion
)
seq(webSettings :_*)
libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container",
"org.eclipse.jetty" % "jetty-plus" % "9.1.0.v20131115" % "container"
)
... and this is the project/plugins.sbt:
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.9.0")
You seem to possibly have a few problems with your tarball.
You need to add some Scala modules to Jackson to be able to use any Scala functionality. That can be done by doing this:
val jsonObjectMapper = new ObjectMapper()
jsonObjectMapper.registerModule(DefaultScalaModule)
val jsonProvider: JacksonJsonProvider = new JacksonJsonProvider(jsonObjectMapper)
According to this working jersey-jackson example. You also need to inject org.glassfish.jersey.jackson.JacksonFeature into Jersey which is found in jersey-media-json-jackson. My RestApplication.scala came out like this
import javax.ws.rs.core.Application
import javax.ws.rs.ext.{ContextResolver, Provider}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.google.common.collect.ImmutableSet
import org.glassfish.jersey.jackson.JacksonFeature
#Provider
class ObjectMapperProvider extends ContextResolver[ObjectMapper] {
val defaultObjectMapper = {
val jsonObjectMapper = new ObjectMapper()
jsonObjectMapper.registerModule(DefaultScalaModule)
jsonObjectMapper
}
override def getContext(typ: Class[_]): ObjectMapper = {
defaultObjectMapper
}
}
class RestApplication extends Application {
override def getSingletons: java.util.Set[AnyRef] = {
ImmutableSet.of(
new RestService,
new ObjectMapperProvider,
new JacksonFeature
)
}
}
The real issue, though, is the #PathParam annotation. This code path doesn't invoke Jackson at all. However, what's interesting is that Jersey appears to generically support parsing to any type that has a constructor of a single string. So if you modify your ResourceTypeHolder you can get the functionality you want after all.
case class ResourceTypeHolder(#JsonScalaEnumeration(classOf[ResourceTypeType]) resourceType:ResourceType.ResourceType) {
def this(name: String) = this(ResourceType.withName(name))
}
You might be able to add generic support for enum holders to Jersey as an injectable provider. However, that hasn't come up in dropwizard-scala, a project that would suffer the same fate as it uses Jersey too. Thus I imagine it's either impossible, or simply just not common enough for anyone to have done the work. When it comes to enum's, I tend to keep mine in Java.

How to make Squeryl work with the Play! Framework?

I'm trying to learn how to make a simple database app with Play and Squeryl. I've made the Tasks app from the Play tutorial, but I want to change the model / schema so that it uses Squeryl instead of Anorm. I've been looking at different tutorials, examples and answers, but I haven't really figured out how to do this.
So, given the source code from the Play Tutorial (ScalaTodoList); how do I proceed to make it work with Squeryl?
More specifically:
How do I implement the all(), create(), and delete() methods in my model? (I'd like to use auto-incrementing ID's for the Tasks)
Which database adapter to use is currently hard coded in Build.scala and Global.scala (see below). How can I make it such that it automatically uses H2 for dev/testing and Postgres on Heroku, like it does for Anorm in the Play tutorial?
How do I make sure that it automatically creates my tables?
This is what I've done thus far
I've completed the Play ScalaTodoList Tutorial.
In project/Build.scala, object ApplicationBuild, I've added the dependencies:
// From the "Squeryl Getting Started tutorial"
val posgresDriver = "postgresql" % "postgresql" % "8.4-702.jdbc4"
val h2 = "com.h2database" % "h2" % "1.2.127"
// From the "Squeryl Getting Started tutorial"
libraryDependencies ++= Seq(
"org.squeryl" %% "squeryl" % "0.9.5",
h2
)
// From the Play tutorial
val appDependencies = Seq(
// Add your project dependencies here,
"org.squeryl" %% "squeryl" % "0.9.5", // Copied from above so that it compiles (?)
"postgresql" % "postgresql" % "8.4-702.jdbc4"
)
added app/Global.scala (taken from the SO answer mentioned above, just changed the adapter to H2):
import play.db.DB
import play.api.Application
import play.api.GlobalSettings
import org.squeryl._
import org.squeryl.adapters._
object Global extends GlobalSettings {
override def onStart(app: Application): Unit =
{
SessionFactory.concreteFactory = Some(
() => Session.create(DB.getDataSource().getConnection(),
dbAdapter));
}
override def onStop(app: Application): Unit =
{
}
val dbAdapter = new H2Adapter(); // Hard coded. Not good.
}
in app/models/Task.scala I've added imports and removed the Anorm implemetations in all(), create(), and delete().
The controller from the Play tutorial expects the all() method to return List[Task].
import org.squeryl.PrimitiveTypeMode._
import org.squeryl.Schema
import org.squeryl.annotations.Column
case class Task(id: Long, label: String)
object Task extends Schema {
val tasks = table[Task] // Inspired by Squeryl tutorial
def all(): List[Task] = {
List[Task]() // ??
}
def create(label: String) {
// ??
}
def delete(id: Long) {
// ??
}
}
The rest of the files are left as they were at the end of the Play tutorial.
Here is an example Play 2 project with Squeryl:
https://github.com/jamesward/play2bars/tree/scala-squeryl
The "Play for Scala" (MEAP) book has a chapter on Squeryl integration
http://www.manning.com/hilton/