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.