Search code examples
scalashapelessscala-macros

Macros/Shapeless to control string value in method


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


Solution

  • 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: