7zark7
7zark7

Reputation: 10145

Modeling with Scala case class

I'm attempting to model responses from REST APIs as case classes which I can use pattern matching on.

I thought it would be a good fit assuming inheritance, but I see that this is deprecated. I know there are already questions related to case classes and inheritance, but my question is more about how you would model the following the "right way" here without inheritance.

I started with the following two case classes, which work fine:

case class Body(contentType: String, content: String)
case class Response(statusCode: Int, body: Body)

i.e. a REST call would return with something like:

Response(200, Body("application/json", """{ "foo": "bar" }"""))

which I could pattern match like:

response match {
  case Response(200, Body("application/json", json)) => println(json)
  case Response(200, Body("text/xml", xml)) => println(xml)
  case Response(_,_) => println("Something unexpected")
}

etc. which works fine.

Where I ran into trouble is: I'd like helper extensions for these case classes, such as:

case class OK(body: Body) extends Response(200, body)
case class NotFound() extends Response(404, Body("text/plain", "Not Found"))

case class JSON(json: String) extends Body("application/json", json)
case class XML(xml: String) extends Body("text/xml", xml)

so that I can do simplified pattern matches like this:

response match {
  case OK(JSON(json)) => println(json)
  case OK(XML(xml)) => println(xml)
  case NotFound() => println("Something is not there")

  // And still drop down to this if necessary:
  case Response(302, _) => println("It moved")
}

and also which would also allow my REST code to directly use and return:

Response(code, Body(contentType, content))

which is easier to build a response dynamically.

so...

I can get it to compile (with deprecation warnings) via:

case class OK(override val body: Body) extends Response(200, body)

However, this doesn't seem to work with pattern matching.

Response(200, Body("application/json", "")) match {
  case OK(_) => ":-)"
  case _ => ":-("
}
res0: java.lang.String = :-(

Any ideas on how this could work? I'm open to different approaches, but this was my attempt to find a practical use for case classes

Upvotes: 6

Views: 1973

Answers (3)

0__
0__

Reputation: 67300

There are several reasons why case classes shouldn't be subclassed. In your case, the problem becomes that OK is another type than (a subtype of) Response, therefore the match fails (even if the arguments match, the type doesn't match).

You will want custom extractors, instead. For example:

case class Response(code: Int, body: String)
object OK {
  def apply(body: String) = Response(200, body)
  def unapply(m: Response): Option[String] = m match {
    case Response(200, body) => Some(body)
    case _                   => None
  }
}

def test(m: Response): String = m match {
   case OK(_) => ":-)"
   case _     => ":-("
}

test(Response(300, "Hallo"))  // :-(
test(Response(200, "Welt"))   // :-)
test(OK("Welt"))              // :-)

There are few more examples of custom extractors in this thread.

Upvotes: 10

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297265

While custom extractors mentioned by 0__ can certainly be used, you'll lose exhaustiveness guarantees of sealed type hierarchies. While in the example you gave in the question there's nothing sealed, the problem is well suited to them.

In that case, my suggestion is to simply make sure the case class is always at the bottom of the type hierarchy, and make the upper classes normal. For example:

sealed class Response(val statusCode: Int, val body: Body) sealed
case class Ok(override val body: Body) extends Response(200, body)
sealed class NotOk(statusCode: Int, body: Body) extends Response(statusCode, body)
case object NotFound extends NotOk(404, "Not found")
// and so on...

Upvotes: 1

AndreasScheinert
AndreasScheinert

Reputation: 1918

Have you looked at the scala library unfiltered? http://unfiltered.lessis.me/ It may help you with approaching you problem . HTH

Upvotes: 1

Related Questions