How to write Junit test cases for ZIO Http? - scala

Here's my code:
import zhttp.http._
import zio._
import zhttp.http.{Http, Method, Request, Response, Status}
import zhttp.service.Server
case class Experiments(experimentId: String, variantId: String, accountId: String, deviceId: String, date: Int)
object Experiments {
implicit val encoder: JsonEncoder[Experiments] = DeriveJsonEncoder.gen[Experiments]
implicit val decoder: JsonDecoder[Experiments] = DeriveJsonDecoder.gen[Experiments]
implicit val codec: JsonCodec[Experiments] = DeriveJsonCodec.gen[Experiments]
implicit val schema: Schema[Experiments] = DeriveSchema.gen
}
object HttpService {
def apply(): Http[ExpEnvironment, Throwable, Request, Response] =
Http.collectZIO[Request] {
case req#(Method.POST -> !! / "zioCollector") =>
val c = req.body.asString.map(_.fromJson[Experiments])
for {
u <- req.body.asString.map(_.fromJson[Experiments])
r <- u match {
case Left(e) =>
ZIO.debug(s"Failed to parse the input: $e").as(
Response.text(e).setStatus(Status.BadRequest)
)
case Right(u) =>
println(s"$u + =====")
ExpEnvironment.register(u)
.map(id => Response.text(id))
}
}
yield r
}
}
object ZioCollectorMain extends ZIOAppDefault {
def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any] = {
Server.start(
port = 9001,
http = HttpService()).provide(ZLayerExp.layer)
}
}
How to write Junit test cases for ZIO Http?
I'm looking into Zio-testing, hopefully this would be helpful.
Any docs, links, code is helpful
#Zio # Zio-http #Zio-json #Scala #AkkatoZioMigration

Related

Extract Scala function name and params implicitly

I have the following code:
def disableRules(someId: String) = Action.async { implicit req =>
Metrics.measureTime("disableRules") {
someFutureOpr(someId).map(_ => Ok)
.recover {
case e: Exception => handlerError(s"Failure occurred on disableRules request ${e.getMessage}", "disableRules")
}
}
}
def activeRules(someId: String) = Action.async { implicit req =>
Metrics.measureTime("activeRules") {
someFutureOpr2(someId).map(_ => Ok)
.recover {
case e: Exception => handlerError(s"Failure occurred on activeRules request ${e.getMessage}", "activeRules")
}
}
}
...
As you can see, I have mesureTime and handleError functions that I pass to them the name of the function as String, is there way to make it implicitly, I mean its will take the function Name, if not - there way to extract the function Name and print it, also regarding params.
You can solve this with a macro (and no runtime reflection):
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
class CaptureImpl(val c: blackbox.Context) {
import c.universe._
def describe[T: c.WeakTypeTag](
expr: c.Expr[T]
): c.Expr[String] = c.Expr[String](q"(${expr.tree.toString()})")
}
object CaptureMethod {
def apply[T](expr: T): String = macro CaptureImpl.describe[T]
}
Example:
object Test {
def foo(): String = "hello"
def bar(a: Int): Int = a
def baz(s: String): String = s
def main(args: Array[String]): Unit = {
println(CaptureMethod(foo()))
println(CaptureMethod(bar(1)))
println(CaptureMethod(baz("yes")))
}
}
Yields:
Test.this.foo()
Test.this.bar(1)
Test.this.baz("yes")
Calculate it inside Metrics:
object Metrics {
def currentMethodName() : String = Thread.currentThread.getStackTrace()(3).getMethodName
def measureTime(): Unit = {
println(currentMethodName)
}
}
Then for example:
def a1() = {
Metrics.measureTime()
}
def a2() = {
Metrics.measureTime()
}
will output:
a1
a2
Is this a safe operation?
If we had:
def currentMethodName() : String = Thread.currentThread.getStackTrace.toList.mkString("\n")
we get:
java.lang.Thread.getStackTrace(Thread.java:1559)
HelloWorld1$Metrics$.currentMethodName(HelloWorld1.scala:69)
HelloWorld1$Metrics$.measureTime(HelloWorld1.scala:72)
HelloWorld1$.a1(HelloWorld1.scala:77)
HelloWorld1$.main(HelloWorld1.scala:103)
HelloWorld1.main(HelloWorld1.scala)
So we see that:
In index 0 we get getStackTrace.
In index 1 we have currentMethodName.
In index 2 we have measureTime.
Since measureTime is not the first method of the stack trace, fir sure we have another element in the stack trace. Therefore in your case yes, it is safe.

Tapir, Circe, Schema

I have a case class like this
case class OffboardingError1(
userId: Option[String],
error: OffboardingErrorType1
)
object OffboardingError1 {
implicit val encode: Encoder[OffboardingError1] = deriveEncoder[OffboardingError1]
implicit val decode: Decoder[OffboardingError1] = deriveDecoder[OffboardingError1]
implicit val codecJson: CodecJson[OffboardingError1] = CodecJson.derive[OffboardingError1]
implicit val schema: Schema[OffboardingError1] = implicitly[Derived[Schema[OffboardingError1]]].value
}
OffboardingErrorType1 is defined as below:
trait OffboardingErrorType1 {
val name: String
val message: String
}
object OffboardingErrorType1 {
case class UnknownError1(name: String = "UnknownError", message: String) extends OffboardingErrorType1
case class S3ImageFindError1(name: String = "S3ImageFindError", message: String) extends OffboardingErrorType1
def getErrorTypeFromString(name: String, errorMessage: String): Option[OffboardingErrorType1] = name match {
case "UnknownError" => Some(UnknownError1(message = errorMessage))
case "S3ImageFindError" => Some(S3ImageFindError1(message = errorMessage))
case _ => None
}
implicit val encoder: Encoder[OffboardingErrorType1] = {
(offBoardingTypeError: OffboardingErrorType1) =>
Json.obj(
("name", offBoardingTypeError.name.asJson),
("message", offBoardingTypeError.message.asJson)
)
}
implicit val decoder: Decoder[OffboardingErrorType1] = {
c =>
for {
name <- c.downField("name").as[String]
errorMessage <- c.downField("message").as[String]
} yield getErrorTypeFromString(name, errorMessage) match {
case Some(cType) => cType
case _ => throw new IllegalArgumentException(s"bad name for offboarding error type: [${name}]")
}
}
implicit val codec: CodecJson[OffboardingErrorType1] = {
import argonaut._
import Argonaut._
CodecJson(
(errorType: OffboardingErrorType1) =>
("name" := errorType.name) ->:
("message" := errorType.message) ->:
jEmptyObject,
c => for {
name <- c.downField("name").as[String]
message <- c.downField("message").as[String]
result <- getErrorTypeFromString(name, message).fold(
DecodeResult.fail[OffboardingErrorType1](s"bad name for offboarding error type: [${name}]", CursorHistory.empty)
)(DecodeResult.ok)
} yield result
)
}
}
I am using tapir for end point , the code is like this
val coursePlanCreationJob: Endpoint[(String, CoursePlanCreationRequest), ServiceThrowable, JobInfo1, Nothing] =
V1Endpoint.v1Endpoint
.post
.description("create a coursePlan")
.in("courses" / "type" / "create-courseplan")
.in(jsonBody[CoursePlanCreationRequest])
.out(jsonBody[OffboardingError1])
the set compile gives me error could not find implicit value for parameter e: sttp.tapir.generic.Derived[sttp.tapir.Schema[io.ctek.services.palpatine.model.response.OffboardingError1]]
[error] implicit val schema: Schema[OffboardingError1] = implicitly[Derived[Schema[OffboardingError1]]].valueI am guessing it's because of "OffboardingErrorType1" trait. Anyone knows how to write Schema for this type of trait?

Reusablecode for db operations with dependency injection

I have multiple actors managing data models that are written to a mongo db.
object LevelManager {
val collectionName = "levels"
}
#Singleton
class LevelManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi) extends Actor with ActorLogging with InjectedActorSupport {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](LevelManager.collectionName))
override def receive: Receive = {
case msg:GetById =>
var level = collection.flatMap(c => c.find(Json.obj("_id" -> msg.id), Option.empty[JsObject]).one[LevelModel].map {
result =>
logger.info( result )
}
}
}
This works fine, but this db code is used in every actor and i did not manage to have it only once. I'm not sure if this is even a clever way, too. It derived from older scala times without dependency injection, where everything was put in an object trait.
So i'm looking for a trait or something, with basic db io handling
Edit: Before dependency injection i was able to use a trait like this:
trait BaseModel[T] {
val collectionName: String
val db = ReactiveMongoPlugin.db
def load(id: Long)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj("_id" -> id)).one[T]
}
def loadAll()(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj()).cursor[T].collect[Vector]()
}
def save(id: Long, model: T)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
val doc = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
coll.save(doc).map { lastError =>
if (!lastError.ok) Logger.error(lastError.message)
lastError.ok
}
}
I ended in creating a trait with def collection: Future[JSONCollection] and i'm now able to access the db my favorite db functions. This was my goal and makes life so much better. But i'm unsettled from the recent feedback here, if this has any disadvantages.
trait DBStuff[T] {
def collection: Future[JSONCollection]
def log: LoggingAdapter
def save(id: String, model: T)(implicit fmt: Format[T]) = {
val doc:JsObject = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
collection.flatMap(_.update.one(Json.obj("_id" -> id), doc, true)).map(lastError => if (!lastError.ok) log.warning(s"Mongo LastError: %s".format(lastError)))
}
def loadOne(id: String)(implicit fmt: Format[T]): Future[Option[T]] = loadOne( Json.obj("_id" -> id) )
def loadOne(obj: JsObject, projection:Option[JsObject] = None )(implicit fmt: Format[T]): Future[Option[T]] = {
collection.flatMap(_.find( obj, projection).one[T].map {
result =>
result
}.recover {
case err => log.error(s"DB Loading Error: $err")
None
})
}
def loadAll()(implicit fmt: Format[T]):Future[Vector[T]] = {
loadAll(Json.obj(), None )
}
def loadAll( obj: JsObject, projection:Option[JsObject] = None)(implicit fmt: Format[T]):Future[Vector[T]] = {
collection.flatMap(_.find(obj, projection ).cursor[T]().collect[Vector](Int.MaxValue, Cursor.FailOnError()).map {
result => result
}.recover {
case err =>
log.error(s"DB Loading Error: $err")
Vector.empty
})
}
...
}
#Singleton
class TaskManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi
) extends Actor with ActorLogging with InjectedActorSupport with DBStuff[TaskModel] {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](TaskManager.collectionName))
override def preStart() = {
loadAll() map {
result =>
//What ever
}
}

Scala, Sangria and Scalatra

We have a Scala application using Scalatra (http://scalatra.org/) as our web framework. I'm wondering if there are any good (or just any) resources out there on how to implement a GraphQL endpoint using Sangria (http://sangria-graphql.org/) and Scalatra?
I'm new to Scala and would appreciate any help to get started on this.
There aren't any that I know of but since Scalatra uses json4s you would use sangria's json4s marshaller .
Otherwise, if sangria could be clearer to you, here's a scala worksheet with a very simplistic example based off play + sangria - in this case you would just need to swap the json library.
The db is mocked (perhaps you use Slick?) and the http server as well but it's a simple case of swapping in the function definitions.
import sangria.ast.Document
import sangria.execution.{ErrorWithResolver, Executor, QueryAnalysisError}
import sangria.macros.derive.{ObjectTypeDescription, ObjectTypeName, deriveObjectType}
import sangria.parser.{QueryParser, SyntaxError}
import sangria.renderer.SchemaRenderer
import sangria.schema.{Argument, Field, IntType, ListType, ObjectType, OptionInputType, Schema, fields}
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success}
// replace with another json lib
// eg https://github.com/sangria-graphql/sangria-json4s-jackson
import play.api.libs.json._
import sangria.marshalling.playJson._
case class User(name: String, age: Int, phone: Option[String])
class FakeDb {
class UsersTable {
def getUsers(limit: Int): List[User] = {
// this would come from the db
List(
User("john smith", 23, None),
User("Anne Schwazenbach", 45, Some("2134556"))
)
}
}
val usersRepo = new UsersTable
}
object MySchema {
val limitArg: Argument[Int] = Argument("first", OptionInputType(IntType),
description = s"Returns the first n elements from the list.",
defaultValue = 10)
implicit val UsersType: ObjectType[FakeDb, User] = {
deriveObjectType[FakeDb, User](
ObjectTypeName("Users"),
ObjectTypeDescription("Users in the system")
)
}
private val Query: ObjectType[FakeDb, Unit] = ObjectType[FakeDb, Unit](
"Query", fields[FakeDb, Unit](
Field("users", ListType(UsersType),
arguments = limitArg :: Nil,
resolve = c => c.ctx.usersRepo.getUsers(c.arg(limitArg))
)
))
val theSchema: Schema[FakeDb, Unit] = Schema(Query)
}
object HttpServer {
def get(): String = {
// Http GET
SchemaRenderer.renderSchema(MySchema.theSchema)
}
def post(query: String): Future[JsValue] = {
// Http POST
val variables = None
val operation = None
QueryParser.parse(query) match {
case Success(q) => executeQuery(q, variables, operation)
case Failure(error: SyntaxError) => Future.successful(Json.obj("error" -> error.getMessage))
case Failure(error: Throwable) => Future.successful(Json.obj("error" -> error.getMessage))
}
}
private def executeQuery(queryAst: Document, vars: Option[JsValue], operation: Option[String]): Future[JsValue] = {
val schema: Schema[FakeDb, Unit] = MySchema.theSchema
Executor.execute[FakeDb, Unit, JsValue](schema, queryAst, new FakeDb,
operationName = operation,
variables=vars.getOrElse(Json.obj()))
.map((d: JsValue) => d)
.recover {
case error: QueryAnalysisError ⇒ Json.obj("error" -> error.getMessage)
case error: ErrorWithResolver ⇒ Json.obj("error" -> error.getMessage)
}
}
}
HttpServer.get()
val myquery = """
{
users {
name
}
}
"""
val res: JsValue = Await.result(HttpServer.post(myquery), 10.seconds)

akka http (un)marshall traits

Let's assume the following Code:
sealed trait Action {
def run(): Boolean
}
case class SimpleAction(parameter: String) extends Actions {
// some impl
}
case class ExtendedAction(parameter1: String, parameter2: String) extends Actions {
// some impl
}
Now I want to define a webservice where one can retrieve the Actions. How can I Marshall the Action as it's just the trait and no specific Type?
I have found this https://github.com/spray/spray-json#providing-jsonformats-for-other-types in the Docs. Is there any simpler way to achieve this than using this approach mixed with pattern matching?
import spray.json._
import DefaultJsonProtocol._
implicit val simpleActionFormat = jsonFormat1(SimpleAction)
implicit val extendedActionFormat = jsonFormat2(ExtendedAction)
implicit val actionFormat1 = new JsonFormat[Action] {
override def write(obj: Action): JsValue = obj match {
case a: SimpleAction => JsObject("type" -> "simple".toJson, "value" -> a.toJson)
case b: ExtendedAction => JsObject("type" -> "extended".toJson, "value" -> b.toJson)
}
override def read(json: JsValue): Action = json.asJsObject.getFields("type", "value") match {
case Seq(JsString("simple"), js) => js.convertTo[SimpleAction]
case Seq(JsString("extended"), js) => js.convertTo[ExtendedAction]
case _ => throw new RuntimeException(s"Invalid json format: $json")
}
}
Or if you only care about converting Actions to json, then simply:
implicit val simpleActionFormat = jsonFormat1(SimpleAction)
implicit val extendedActionFormat = jsonFormat2(ExtendedAction)
implicit val actionFormat = lift(new JsonWriter[Action] {
override def write(obj: Action): JsValue = obj match {
case a: SimpleAction => a.toJson
case b: ExtendedAction => b.toJson
}
})