1

My method:

protected final def validatePayload[T](payload: Option[Payload]) = {
    payload match {
      case None => throw new IllegalArgumentException("Payload was None.")
      case Some(p) => p.resource match {
        case None => throw new IllegalArgumentException("Resource was None.")
        case Some(resource) => resource match {
          case temp: T =>
          case _ => throw new IllegalArgumentException("Resource is not the right type.")
        }
      }
    }
  }

Payload:

case class Payload(id: String, resource: Option[Any])

Usage:

  validatePayload[String](Some(Payload("id", Some(5))))

I'd expect this to throw the illegal arg since I'm telling it to accept String and I'm passing in a Int. Why is it not?

My objective is to validate the payload been sent to an actor, the actor should only react to a specific type of resource and nothing else. How can I fix this to accomplish that?

iCodeLikeImDrunk
  • 17,085
  • 35
  • 108
  • 169

2 Answers2

4

ClassTag

The simplest case is when you can use a ClassTag (the limitation for this case is given below). For that case you can simply add a context bound to the function type definition, and it just works:

import scala.reflect.ClassTag

protected final def validatePayload[T : ClassTag](payload: Option[Payload]) = {
  // everything else is the same...
}

// Throws an error
validatePayload[String](Some(Payload("id", Some(5)))) 

At runtime it's pretty much equivalent to Java's instanceof operator and a type cast.

TypeTag

But the ClassTag doesn't work for generic types. For example, sequences with different element types aren't distinguished:

// Doesn't throw
validatePayload[Seq[String]](Some(Payload("id", Some(Seq(1,2,3)))))

If you need to distinguish generic types, you'd have to use TypeTags. You must know the type of resource, when you are creating the payload, and the payload must store its Type or the TypeTag of its type.

Here is an example:

import reflect.runtime.universe._

case class Payload[T](id: String, resource: Option[T])(implicit val tag: TypeTag[T])

def validatePayload[T : TypeTag](payload: Option[Payload[_]]) = {
  payload match {
    case None => throw new IllegalArgumentException("Payload was None.")
    case Some(p) => p.resource match {
      case None => throw new IllegalArgumentException("Resource was None.")
      case Some(resource) => resource match {
        case temp if p.tag.tpe <:< typeOf[T] =>
        case _ => throw new IllegalArgumentException("Resource is not the right type.")
      }
    }
  }
}

Now it will distinguish generics:

// Throws an error
validatePayload[Seq[String]](Some(Payload("id", Some(Seq(1,2,3)))))

But TypeTags rely on types known at compile time. So if resource has type Any before you create Payload with it, the validatePayload[T] will not throw only if T is Any. And there are some other quirks:

// Doesn't throw
validatePayload[Seq[Int]](Some(Payload("id", Some(List(1,2,3)))))
// Throws, although resource *is* a List[Int] at runtime
validatePayload[List[Int]](Some(Payload("id", Some(Seq(1,2,3)))))

cast from shapeless

A more robust method is provided by a third-party library shapeless. Here is an example:

import shapeless.Typeable
import shapeless.syntax.typeable._

def validatePayload[T : Typeable](payload: Option[Payload]) = {
  payload match {
    case None => throw new IllegalArgumentException("Payload was None.")
    case Some(p) => p.resource match {
      case None => throw new IllegalArgumentException("Resource was None.")
      case Some(resource) => resource match {
        case temp if temp.cast[T].isDefined =>
        case _ => throw new IllegalArgumentException("Resource is not the right type.")
      }
    }
  }
}

Both don't throw now:

validatePayload[Seq[Int]](Some(Payload("id", Some(List(1,2,3)))))
validatePayload[List[Int]](Some(Payload("id", Some(Seq(1,2,3)))))
Kolmar
  • 14,086
  • 1
  • 22
  • 25
3

Due to type erasure you can't check type like this, but ClassTag is a workaround.

case class Payload(id: String, resource: Option[Any])

import scala.reflect.ClassTag
def validatePayload[T: ClassTag](payload: Option[Payload]) = {
  payload flatMap (_.resource) filter { res =>
    val c = implicitly[ClassTag[T]].runtimeClass
    c.isInstance(res)
  } getOrElse (throw new IllegalArgumentException("Invalid payload"))
}

I simplified the code, if you don't need custom errors, it's less verbose for me at least. Although if you want to stick to your code, only important parts from your problems view is declaring that the type T needs and implicit ClassTag[T] which is declared like this [T: ClassTag] and check if the type is valid here:

val c = implicitly[ClassTag[T]].runtimeClass
c.isInstance(res)

Here is a test

scala> validatePayload[String](Some(Payload("id", Some("a"))))
res3: Any = a

scala> validatePayload[String](Some(Payload("id", Some(5))))
java.lang.IllegalArgumentException: Invalid payload
  at $anonfun$validatePayload$3.apply(<console>:20)
  at $anonfun$validatePayload$3.apply(<console>:20)
  at scala.Option.getOrElse(Option.scala:121)
  at .validatePayload(<console>:20)
  ... 33 elided
Łukasz
  • 8,555
  • 2
  • 28
  • 51
  • wow. thanks! silly question, but would there be any downsides to this workaround? performance issues? – iCodeLikeImDrunk Oct 09 '15 at 18:31
  • 1
    I don't think it is a silly question. Most likely some performance hit, as it has to use reflection. I don't know much about technical side of this. – Łukasz Oct 09 '15 at 18:34