I'm creating a DSL where users have a given when then, where they pass a string with the action they want.
All those actions are controlled using some regular expressions.
Now using shapeless or macros I would like to control what users compones using the DSL, and in case the string does not fit the string as we expect make the code don't compile.
Si for instance
Given("a good action") -> fine
When("wrong string action") -> compilation error
Can anybody please point me to a good blog or documentation of how to achieve this?
Regards
After the solution of Gabriele now I´m facing a design issue. This is my vaidator class
object DSLValidator {
import scala.language.experimental.macros
def Given(message: String): Any = macro checkActionImpl
def When(message: String): Any = macro checkActionImpl
def Then(message: String): Any = macro checkActionImpl
def And(message: String): Any = macro checkActionImpl
def checkActionImpl(c: blackbox.Context)(message: c.Tree): c.Tree = {
import c.universe._
def isValidAction(s: String): Boolean = checkMessage(s)
message match {
case _tree#Literal(Constant(s: String)) if isValidAction(s) => _tree
case _ => c.abort(c.enclosingPosition, "Invalid action for DSL. Check the allowed actions in RegexActions")
}
}
val PARAMS = "(.*)=(.*)"
val PAYLOAD_VALUE_REGEX = s"^Payload $PARAMS".r
def checkMessage(action: String): Boolean = {
action match {
case PAYLOAD_VALUE_REGEX(c, c1) => true
case "Make a request to server" => true
case _ => false
}
}
}
And if I invoke the Given/When/Then from another class with wrong arguments the compiler complain as we expect.
#Test
def TestDSL(): Unit = {
DSLValidator.Given("Make a request to wrong")--> not compiling
DSLValidator.When("Payload code=works") --> compiling
println("End")
}
But now thinking the whole idea of was not only to check the values on compilation time but also execute the logic of the Given/When/then
And I realize than when I use macros in a function there´s nothing else that we can do so.
def Given(message: String): Any = {
macro checkActionImpl
//My bussiness logic
}
is not compiling, so my question is, where can I put now the business logic and where the invocation of the macro function to continue working.
Also using a static class as bridge does not work and wont compile since the string message does not fit.
object Macros {
def Given(message: String) = {
DSLValidator.Given(message)
//Logic here
println("Given")
}
}
Macros.Given("Make a request to wrong")
Any idea?
Regards
I think shapeless cannot help you in this case, so a macro would be the way to go.
Here's a skeleton for a macro you can start from:
import scala.language.experimental.macros
def Given(message: String): /* the return type of Given */ = macro givenImpl
def givenImpl(c: Context)(message: c.Tree): c.Tree = {
import c.universe._
def isValid(s: String): Boolean = ??? // your validation logic here
message match {
case case t # Literal(Constant(s: String)) if isValid(s) => t
case _ => c.abort(c.enclosingPosition, "Invalid message")
}
}
In order to fully understand this I can recommend you to read:
http://docs.scala-lang.org/overviews/macros/overview.html
https://blog.scalac.io/2016/02/25/def-hello-macro-world.html
Related
My first steps with ZIO, I am trying to convert this readfile function with a compatible ZIO version.
The below snippet compiles, but I am not closing the source in the ZIO version. How do I do that?
def run(args: List[String]) =
myAppLogic.exitCode
val myAppLogic =
for {
_ <- readFileZio("C:\\path\\to\file.csv")
_ <- putStrLn("Hello! What is your name?")
name <- getStrLn
_ <- putStrLn(s"Hello, ${name}, welcome to ZIO!")
} yield ()
def readfile(file: String): String = {
val source = scala.io.Source.fromFile(file)
try source.getLines.mkString finally source.close()
}
def readFileZio(file: String): zio.Task[String] = {
val source = scala.io.Source.fromFile(file)
ZIO.fromTry[String]{
Try{source.getLines.mkString}
}
}
The simplest solution for your problem would be using bracket function, which in essence has similar purpose as try-finally block. It gets as first argument effect that closes resource (in your case Source) and as second effect that uses it.
So you could rewrite readFileZio like:
def readFileZio(file: String): Task[Iterator[String]] =
ZIO(Source.fromFile(file))
.bracket(
s => URIO(s.close),
s => ZIO(s.getLines())
)
Another option is to use ZManaged which is a data type that encapsulates the operation of opening and closing resource:
def managedSource(file: String): ZManaged[Any, Throwable, BufferedSource] =
Managed.make(ZIO(Source.fromFile(file)))(s => URIO(s.close))
And then you could use it like this:
def readFileZioManaged(file: String): Task[Iterator[String]] =
managedSource(file).use(s => ZIO(s.getLines()))
I wrote simple callback(handler) function which i pass to async api and i want to wait for result:
object Handlers {
val logger: Logger = Logger("Handlers")
implicit val cs: ContextShift[IO] =
IO.contextShift(ExecutionContext.Implicits.global)
class DefaultHandler[A] {
val response: IO[MVar[IO, A]] = MVar.empty[IO, A]
def onResult(obj: Any): Unit = {
obj match {
case obj: A =>
println(response.flatMap(_.tryPut(obj)).unsafeRunSync())
println(response.flatMap(_.isEmpty).unsafeRunSync())
case _ => logger.error("Wrong expected type")
}
}
def getResponse: A = {
response.flatMap(_.take).unsafeRunSync()
}
}
But for some reason both tryPut and isEmpty(when i'd manually call onResult method) returns true, therefore when i calling getResponse it sleeps forever.
This is the my test:
class HandlersTest extends FunSuite {
test("DefaultHandler.test") {
val handler = new DefaultHandler[Int]
handler.onResult(3)
val response = handler.getResponse
assert(response != 0)
}
}
Can somebody explain why tryPut returns true, but nothing puts. And what is the right way to use Mvar/channels in scala?
IO[X] means that you have the recipe to create some X. So on your example, yuo are putting in one MVar and then asking in another.
Here is how I would do it.
object Handlers {
trait DefaultHandler[A] {
def onResult(obj: Any): IO[Unit]
def getResponse: IO[A]
}
object DefaultHandler {
def apply[A : ClassTag]: IO[DefaultHandler[A]] =
MVar.empty[IO, A].map { response =>
new DefaultHandler[A] {
override def onResult(obj: Any): IO[Unit] = obj match {
case obj: A =>
for {
r1 <- response.tryPut(obj)
_ <- IO(println(r1))
r2 <- response.isEmpty
_ <- IO(println(r2))
} yield ()
case _ =>
IO(logger.error("Wrong expected type"))
}
override def getResponse: IO[A] =
response.take
}
}
}
}
The "unsafe" is sort of a hint, but every time you call unsafeRunSync, you should basically think of it as an entire new universe. Before you make the call, you can only describe instructions for what will happen, you can't actually change anything. During the call is when all the changes occur. Once the call completes, that universe is destroyed, and you can read the result but no longer change anything. What happens in one unsafeRunSync universe doesn't affect another.
You need to call it exactly once in your test code. That means your test code needs to look something like:
val test = for {
handler <- TestHandler.DefaultHandler[Int]
_ <- handler.onResult(3)
response <- handler.getResponse
} yield response
assert test.unsafeRunSync() == 3
Note this doesn't really buy you much over just using the MVar directly. I think you're trying to mix side effects inside IO and outside it, but that doesn't work. All the side effects need to be inside.
I'm trying to build a generic router for a personal project by Binding.scala.
I've defined a PageState trait
sealed trait WhistState {
def text: String
def hash: String
def render: Binding[Node]
}
with a number of subclasses for each route type. I've then trying to create a router, which based on the hash chooses the correct class.
object Router {
val defaultState: WhistState = DefaultState("Games")
val allStates: Vector[WhistState] = Vector(defaultState)
val route: Route.Hash[WhistState] = Route.Hash[WhistState](defaultState)(new Route.Format[WhistState] {
override def unapply(hashText: String): Option[WhistState] = allStates.find(_.hash == window.location.hash)
override def apply(state: WhistState): String = state.hash
})
route.watch()
}
And then I'm my application classes im trying to use this
object Application {
import example.route.Router._
#dom
def render: Binding[Node] = {
for (hash <- route.state.bind.hash) yield println("hash: " + hash)
route.state.bind match {
case default: WhistState => println("default"); default.render.bind
case _ => println("none"); <div>NotFound</div>
}
}
def main(args: Array[String]): Unit = {
dom.render(document.querySelector("#content"), render)
}
}
I've would expect that changing the hash would force a reevaluation of route.state.bind match ... expression.
Any idea to why this doesn't work?
Best regards
route.state is only changed when unapply returns a Some(newState) and newState does not equal to the previous state.
In your case, unapply always returns Some(defaultState) or None. That's why route.state never changes.
To give you a minimal example:
object Main extends JSApp
{
val someThing: String = determineSomething("test")
def main(): Unit =
{
println(someThing)
}
}
Now, two possibilities here:
private def determineSomething(s: String): String = "succeeded"
If the project is executed like this, well, I get a log entry saying
succeeded
But when I use the more functional syntax:
private val determineSomething: (s: String) => "succeeded"
I get
TypeError: this.determineSomething$1 is null
I am curious as to the why this happens as in the (JVM) repl, both ways work perfectly fine.
I think what you want is something like this:
object Main extends JSApp {
private val determineSomething: String => String = (s: String) => "succeeded"
val someThing: String = determineSomething("test")
def main(): Unit = {
println(someThing)
}
}
The declaration of determineSomething needs to come before the declaration of something, otherwise the former will be uninitialized when the compiler attempts to initialize the latter.
So I have a routes file that looks something like this:
GET /myRes controllers.MyController.get(ids: Option[String], elems: Option[String])
All well and good. Users can get stuff by doing:
/myRes
/myRes?ids=X
/myRes?elems=Y
/myRes?ids=X&elems=Y
However, they can also query the interface by doing:
/myRes?id=X
Which is problematic, because in this case the user is gets the same result as if they had queried /myRes, which is almost certainly not the result they expected. This has been causing a lot of confusion/bugs for developers of the API. Is there an elegant way to catch incorrect/unspecified query parameters being passed to the controller and return a hard error for such queries?
Edit: Changed title to something more descriptive. My problem is basically validating the query parameters to catch any query parameters passed to the API which are not valid/correct.
It can be done with the help of a macro annotation like the following one:
import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import play.api.mvc._
#compileTimeOnly("respond 400 bad request in case of unexpected params")
class StrictParams extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro StrictParamsMacro.impl
}
object StrictParamsMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
annottees.map(_.tree).toList match {
case q"def $name(..$params) = $action { ..$body }" :: Nil =>
val supportedParamNames = params.map(ab => ab.name.toString).toSet
c.Expr[Any](
q"""def $name(..$params) = { req: Request[_] =>
val unsupportedParams = req.queryString.keySet -- $supportedParamNames
if (unsupportedParams.nonEmpty) {
BadRequest(unsupportedParams.mkString("Unsupported Params: ", ", ", ""))
} else {
$body
}
}"""
)
}
}
}
Then you can annotate your action method like this:
#StrictParams
def get(ids: Option[String], elems: Option[String]) = Action {
...
}
i usually pass it like this on get method
GET /getSomething Controllers.Application.getData()
GET /getSomething/:id Controllers.Application.getData(id:Integer)
GET /getSomething/:id/:name Controllers.Application.getData(id:Integer, name :String)
You can define a QueryStringBindable[A] to bind a Map of query string parameters to an instance of type A.
See the corresponding documentation.
Didn't fully explore it yet, but it doesn't seem too difficult to implement a QueryStringBindable[Option[A]].
If you want to forbid confusing parameters (e.g. allowing ids but not id), you can check for unexpected keys in the parameters Map and return an error message if needed (although I would recommand to accept the id key to match the behaviour expected by users).