I'm building an Akka HTTP application using scala 3.3 and Akka actor.
When a star, planet or moon is created in the StarRegistry object, I would like it's value to be temporarily stored in the registry function's map variable map: Map[String, Set[HeavenlyBody]].
The map should store the values like below, each parameter with its own type as described:
"stars" -> Set[CreateStar],
"planets" -> Set[CreatePlanet],
"moons" -> Set[CreateMoon],
In the starRoutes script are the APIs.
The stars API route is used to create a new star and also to GET all stars stored in the storage map above.
When the stars GET API route is fetched, I want the api to fetch all the stars stored in the stars field from the registry function's map above.
The description of the star functionalities are similar to that of the moon and planet functionalities.
The current code is giving the error:
Type Mismatch Error: D:\Studio\WORK\akka\AKKA-HTTP\scala-akka-http-interstellar-IoT\src\main\scala\com\example\StarRegistry.scala:
replyTo ! Stars(map.get("stars").toSeq)
Found: Seq[Set[com.example.HeavenlyBody]]
Required: Seq[com.example.CreateStar]
Type Mismatch Error: D:\Studio\WORK\akka\AKKA-HTTP\scala-akka-http-interstellar-IoT\src\main\scala\com\example\StarRegistry.scala:199:45
replyTo ! Planets(map.get("planets").toSeq)
Found: Seq[Set[com.example.HeavenlyBody]]
Required: Seq[com.example.CreatePlanet]
Type Mismatch Error: D:\Studio\WORK\akka\AKKA-HTTP\scala-akka-http-interstellar-IoT\src\main\scala\com\example\StarRegistry.scala:213:41
replyTo ! Moons(map.get("moons").toSeq)
Found: Seq[com.example.HeavenlyBody]
Required: Seq[com.example.CreateMoon]
My current code looks like below.
Thanks in advance for your help!
StarRegistry.scala:
sealed trait HeavenlyBody
final case class CreateStar(id: String, name: String, location: String, size: Option[Size]) extends HeavenlyBody
final case class CreatePlanet(id: String, name: String, location: String, size: Option[Size]) extends HeavenlyBody
final case class CreateMoon(id: String, name: String, location: String, size: Option[Size]) extends HeavenlyBody
final case class Stars(stars: immutable.Seq[CreateStar])
final case class Planets(planets: immutable.Seq[CreatePlanet])
final case class Moons(moons: immutable.Seq[CreateMoon])
final case class Size(radius: Long)
object StarRegistry {
// actor protocol
sealed trait Command
final case class GetStars(replyTo: ActorRef[Stars]) extends Command
final case class NewStar(star: CreateStar, replyTo: ActorRef[ActionPerformed]) extends Command
final case class GetPlanets(replyTo: ActorRef[Planets]) extends Command
final case class NewPlanet(planet: CreatePlanet, replyTo: ActorRef[ActionPerformed]) extends Command
final case class GetMoons(replyTo: ActorRef[Moons]) extends Command
final case class NewMoon(moon: CreateMoon, replyTo: ActorRef[ActionPerformed]) extends Command
final case class ActionPerformed(description: String)
def apply(): Behavior[Command] = registry(Map("stars" -> Set.empty, "planets" -> Set.empty, "moons" -> Set.empty))
private def registry(map: Map[String, Set[HeavenlyBody]]): Behavior[Command] =
Behaviors.receiveMessage {
case GetStars(replyTo) =>
replyTo ! Stars(map.get("stars").toSeq)
Behaviors.same
case NewStar(star, replyTo) =>
replyTo ! ActionPerformed(s"Star '${star.name}' created!")
registry(map("stars") += star)
case GetPlanets(replyTo) =>
replyTo ! Planets(map.get("planets").toSeq)
Behaviors.same
case NewPlanet(planet, replyTo) =>
replyTo ! ActionPerformed(s"Planet '${planet.name}' created!")
registry(map("planets") += planet)
case GetMoons(replyTo) =>
replyTo ! Moons(map.get("moons").toSeq)
Behaviors.same
case NewMoon(moon, replyTo) =>
replyTo ! ActionPerformed(s"Moon '${moon.name}' created!")
registry(map("moons") += moon)
}
}
jsonFormats.scala:
...
...
object JsonFormats {
import DefaultJsonProtocol._
implicit val sizeJsonFormat: RootJsonFormat[Size] = jsonFormat1(Size.apply)
implicit val createStarJsonFormat: RootJsonFormat[CreateStar] = jsonFormat4(CreateStar.apply)
implicit val createPlanetJsonFormat: RootJsonFormat[CreatePlanet] = jsonFormat4(CreatePlanet.apply)
implicit val createMoonJsonFormat: RootJsonFormat[CreateMoon] = jsonFormat4(CreateMoon.apply)
implicit val starsJsonFormat: RootJsonFormat[Stars] = jsonFormat1(Stars.apply)
implicit val planetsJsonFormat: RootJsonFormat[Planets] = jsonFormat1(Planets.apply)
implicit val moonsJsonFormat: RootJsonFormat[Moons] = jsonFormat1(Moons.apply)
implicit val actionPerformedJsonFormat: RootJsonFormat[ActionPerformed] = jsonFormat1(ActionPerformed.apply)
}
starRoutes.scala:
class StarRoutes(starRegistry: ActorRef[StarRegistry.Command])(implicit val system: ActorSystem[_]) {
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import JsonFormats._ //#import-json-formats
def getStars(): Future[Stars] =
starRegistry.ask(GetStars.apply)
def createStar(star: CreateStar): Future[ActionPerformed] =
starRegistry.ask(NewStar(star, _))
def getPlanets(): Future[Planets] =
starRegistry.ask(GetPlanets.apply)
def createPlanet(planet: CreatePlanet): Future[ActionPerformed] =
starRegistry.ask(NewPlanet(planet, _))
def getMoons(): Future[Moons] =
starRegistry.ask(GetMoons.apply)
def getMoon(name: String): Future[GetMoonResponse] =
starRegistry.ask(GetMoon(name, _))
val starRoutes: Route =
...
pathPrefix("stars") {
concat(
pathEnd {
concat(
get {
complete(getStars())
},
post {
entity(as[CreateStar]) { star =>
onSuccess(createStar(star)) { performed =>
complete((StatusCodes.Created, performed))
}
}
})
},
}
...
pathPrefix("planets") {
concat(
pathEnd {
concat(
get {
complete(getPlanets())
},
post {
entity(as[CreatePlanet]) { planet =>
onSuccess(createPlanet(planet)) { performed =>
complete((StatusCodes.Created, performed))
}
}
})
},
...
pathPrefix("moons") {
concat(
pathEnd {
concat(
get {
complete(getMoons())
},
post {
entity(as[CreateMoon]) { moon =>
onSuccess(createMoon(moon)) { performed =>
complete((StatusCodes.Created, performed))
}
}
})
}
private def registry(map: Map[String, Set[HeavenlyBody]]): Behavior[Command] =
Behaviors.receiveMessage {
case GetStars(replyTo) =>
replyTo ! Stars(map.get("stars").toSeq)
You get the error because map.get(...).toSeq returns Seq[HeavenlyBody] and you want Seq[CreateStar]. You may think that all the elements of the Seq are CreateStar but the compiler can't tell.
The solution is to use collect to constrain the type:
replyTo ! Stars(map.get("stars").toSeq.collect{case x: CreateStar => x})
This will discard any elements that are not CreateStar and return Seq[CreateStar] as required.
You also might want to consider using enum HeavenlyBody rather than a sealed trait.