Modeling with the case Scala class

I am trying to model the responses from the REST API as case classes for which I can use pattern matching.

I thought it would be well suited for inheritance, but I see that it is deprecated. I know that there are already questions related to case classes and inheritance, but my question is about how you could model the next "correct path" here without inheritance.

I started with the following two classes that work great:

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

i.e. the REST call will return with something like:

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

which I could match as:

 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 great.

Where I encountered difficulties, I would like to get helper extensions for these case classes, for example:

 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 as follows:

 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 which will also allow my REST code to directly use and return:

 Response(code, Body(contentType, content)) 

which is easier to build an answer dynamically.

So...

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

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

However, this does not seem to work with pattern matching.

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

Any ideas on how this might work? I am open to different approaches, but it was my attempt to find practical application for class classes

+6
source share
3 answers

There are several reasons why case classes should not be subclassed . In your case, the problem becomes such that OK is a different type than the (subtype) Response , so the match does not work (even if the arguments match, the type does not match).

Instead, you will need custom extractors . For instance:

 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 several examples of custom extractors in this thread .

+10
source

Have you not looked at the scala library? http://unfiltered.lessis.me/ This can help you get closer to you. NTN

+1
source

While the custom extractors mentioned by 0__ can certainly be used, you will lose comprehensive guarantees for sealed type hierarchies. While there is nothing sealed in the example that you asked in the question, the problem suits them well.

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

 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... 
+1
source

Source: https://habr.com/ru/post/918902/


All Articles