diff --git a/be2-scala/src/main/scala/ch/epfl/pop/json/HighLevelProtocol.scala b/be2-scala/src/main/scala/ch/epfl/pop/json/HighLevelProtocol.scala index cf53a02dd2..790a0f6664 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/json/HighLevelProtocol.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/json/HighLevelProtocol.scala @@ -53,6 +53,8 @@ object HighLevelProtocol extends DefaultJsonProtocol { PARAM_MESSAGE_ID -> obj.message_id.toJson, PARAM_WITNESS_SIG -> obj.witness_signatures.toJson ) + + def fields: Set[String] = Set(PARAM_SENDER, PARAM_DATA, PARAM_SIGNATURE, PARAM_WITNESS_SIG, PARAM_MESSAGE_ID) } implicit object ParamsWithChannelFormat extends RootJsonFormat[ParamsWithChannel] { @@ -225,7 +227,7 @@ object HighLevelProtocol extends DefaultJsonProtocol { ) JsObject(jsObjContent) } - + def fields: Set[String] = Set(PARAM_SENDER_PK, PARAM_RUMOR_ID, PARAM_MESSAGES) } implicit object RumorStateFormat extends RootJsonFormat[RumorState] { @@ -250,9 +252,19 @@ object HighLevelProtocol extends DefaultJsonProtocol { implicit object ResultObjectFormat extends RootJsonFormat[ResultObject] { override def read(json: JsValue): ResultObject = json match { case JsNumber(resultInt) => new ResultObject(resultInt.toInt) - case JsArray(resultArray) => new ResultObject(resultArray.map(_.convertTo[Message]).toList) - case JsObject(resultMap) => new ResultObject(resultMap.map { case (k, v) => (Channel(k), v.convertTo[Set[Message]]) }) - case _ => throw new IllegalArgumentException(s"Unrecognizable channel value in $json") + case JsArray(resultArray) => + // in case of empty array, we cannot differentiate List[Rumor] and List[Message] + // We don't differentiate and use an EmptyList to make result available to different response handler + if (resultArray.isEmpty) + new ResultObject(ResultEmptyList()) + resultArray.head.asJsObject.fields.keySet match + case keys if keys == RumorFormat.fields => + new ResultObject(ResultRumor(resultArray.map(_.convertTo[Rumor]).toList)) + case keys if keys == messageFormat.fields => + new ResultObject(ResultMessage(resultArray.map(_.convertTo[Message]).toList)) + case _ => throw new IllegalArgumentException(s"Can't parse jsArray $json to a ResultObject object") + case JsObject(resultMap) => new ResultObject(resultMap.map { case (k, v) => (Channel(k), v.convertTo[Set[Message]]) }) + case _ => throw new IllegalArgumentException(s"Unrecognizable channel value in $json") } override def write(obj: ResultObject): JsValue = { @@ -260,8 +272,10 @@ object HighLevelProtocol extends DefaultJsonProtocol { JsNumber(obj.resultInt.getOrElse(0)) } else if (obj.resultMap.isDefined) { JsObject(obj.resultMap.get.map { case (chan, set) => (chan.channel, set.toJson) }) + } else if (obj.resultMessages.isDefined) { + JsArray(obj.resultMessages.get.map(m => m.toJson).toVector) } else { - JsArray(obj.resultMessages.getOrElse(Nil).map(m => m.toJson).toVector) + JsArray(obj.resultRumor.getOrElse(Nil).map(r => r.toJson).toVector) } } } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/model/network/ResultObject.scala b/be2-scala/src/main/scala/ch/epfl/pop/model/network/ResultObject.scala index 9451381d09..e262f9a3d5 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/model/network/ResultObject.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/model/network/ResultObject.scala @@ -1,23 +1,61 @@ package ch.epfl.pop.model.network +import ch.epfl.pop.model.network.method.Rumor import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.objects.Channel -class ResultObject(val resultInt: Option[Int], val resultMessages: Option[List[Message]], val resultMap: Option[Map[Channel, Set[Message]]]) { +sealed trait ResultType +final case class ResultInt(result: Int) extends ResultType +final case class ResultMessage(result: List[Message]) extends ResultType +final case class ResultMap(result: Map[Channel, Set[Message]]) extends ResultType +final case class ResultRumor(result: List[Rumor]) extends ResultType +final case class ResultEmptyList() extends ResultType - def this(result: Int) = this(Some(result), None, None) +class ResultObject(val result: Option[ResultType]) { - def this(result: List[Message]) = this(None, Some(result), None) + // sugar syntax and legacy purposes + def this(result: Int) = this(Some(ResultInt(result))) + def this(result: Map[Channel, Set[Message]]) = this(Some(ResultMap(result))) - def this(mapResult: Map[Channel, Set[Message]]) = this(None, None, Some(mapResult)) + def this(result: ResultType) = this(Some(result)) - def isIntResult: Boolean = resultInt.isDefined + def resultInt: Option[Int] = { + result match + case Some(resultInt: ResultInt) => Some(resultInt.result) + case _ => None + } + + def resultMessages: Option[List[Message]] = { + result match + case Some(resultMessage: ResultMessage) => Some(resultMessage.result) + case Some(resultEmptyList: ResultEmptyList) => Some(List.empty) + case _ => None + } + + def resultMap: Option[Map[Channel, Set[Message]]] = { + result match + case Some(resultMap: ResultMap) => Some(resultMap.result) + case _ => None + } + + def resultRumor: Option[List[Rumor]] = { + result match + case Some(resultRumor: ResultRumor) => Some(resultRumor.result) + case Some(resultEmptyList: ResultEmptyList) => Some(List.empty) + case _ => None + } + + def isIntResult: Boolean = + result match + case Some(_: ResultInt) => true + case _ => false - override def equals(o: Any): Boolean = { - o match { + override def equals(obj: Any): Boolean = { + obj match case that: ResultObject => - this.resultInt == that.resultInt && that.resultMessages == this.resultMessages && that.resultMap == this.resultMap - case _ => false - } + (this.result, that.result) match + case (Some(a), Some(b)) => a == b + case (None, None) => true + case _ => false } } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/AnswerGenerator.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/AnswerGenerator.scala index b2d4e7b406..33028a7930 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/AnswerGenerator.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/AnswerGenerator.scala @@ -35,7 +35,7 @@ class AnswerGenerator(dbActor: => AskableActorRef) extends AskPatternConstants { val askCatchup = dbActor ? DbActor.Catchup(channel) Await.ready(askCatchup, duration).value match { case Some(Success(DbActor.DbActorCatchupAck(messages))) => - val resultObject: ResultObject = new ResultObject(messages) + val resultObject: ResultObject = new ResultObject(ResultMessage(messages)) Right(JsonRpcResponse(RpcValidator.JSON_RPC_VERSION, Some(resultObject), None, rpcRequest.id)) case Some(Failure(ex: DbActorNAckException)) => Left(PipelineError(ex.code, s"AnswerGenerator failed : ${ex.message}", rpcRequest.getId)) case reply => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, s"AnswerGenerator failed : unexpected DbActor reply '$reply'", rpcRequest.getId)) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/json/HighLevelProtocolSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/json/HighLevelProtocolSuite.scala index dd19275f25..476fe8eca7 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/json/HighLevelProtocolSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/json/HighLevelProtocolSuite.scala @@ -3,13 +3,15 @@ package ch.epfl.pop.json import ch.epfl.pop.IOHelper import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.network.method.{GreetServer, ParamsWithChannel, ParamsWithMap, ParamsWithMessage, Rumor, RumorState} -import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType, ResultObject} +import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType, ResultMessage, ResultObject, ResultRumor} import ch.epfl.pop.model.objects.* import ch.epfl.pop.pubsub.graph.validators.RpcValidator import org.scalatest.Inspectors.forEvery import org.scalatest.funsuite.AnyFunSuite as FunSuite import org.scalatest.matchers.should.Matchers import spray.json.* +import util.examples.MessageExample +import util.examples.Rumor.RumorExample import scala.collection.immutable.{HashMap, Set} @@ -323,6 +325,24 @@ class HighLevelProtocolSuite extends FunSuite with Matchers { answerFromJson.error should equal(None) } + test("result object parses list of rumor correctly") { + val resultRumor: ResultObject = new ResultObject(ResultRumor(List(RumorExample.rumorExample))) + val resultRumorJsValue = HighLevelProtocol.ResultObjectFormat.write(resultRumor) + val resultRumorFromJson = HighLevelProtocol.ResultObjectFormat.read(resultRumorJsValue) + + resultRumor shouldBe resultRumorFromJson + + } + + test("result object parses list of message correctly") { + val resultMessage: ResultObject = new ResultObject(ResultMessage(List(MessageExample.MESSAGE))) + val resultMessageJsValue = HighLevelProtocol.ResultObjectFormat.write(resultMessage) + val resultMessageFromJson = HighLevelProtocol.ResultObjectFormat.read(resultMessageJsValue) + + resultMessage shouldBe resultMessageFromJson + + } + test("Parser correctly encodes and decodes MethodType and rejects incorrect type") { MethodType.values.foreach(obj => { if obj != MethodType.INVALID then { diff --git a/be2-scala/src/test/scala/ch/epfl/pop/model/network/ResultObjectSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/model/network/ResultObjectSuite.scala index f75e865f24..6c25fd3a5e 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/model/network/ResultObjectSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/model/network/ResultObjectSuite.scala @@ -2,8 +2,9 @@ package ch.epfl.pop.model.network import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.objects.Channel -import org.scalatest.funsuite.{AnyFunSuite => FunSuite} +import org.scalatest.funsuite.AnyFunSuite as FunSuite import org.scalatest.matchers.should.Matchers +import util.examples.Rumor.RumorExample class ResultObjectSuite extends FunSuite with Matchers { test("Int constructor works") { @@ -15,10 +16,11 @@ class ResultObjectSuite extends FunSuite with Matchers { } test("List constructor works") { - val obj: ResultObject = new ResultObject(List.empty) + val obj: ResultObject = new ResultObject(ResultMessage(List.empty)) obj.resultInt should equal(None) obj.resultMap should equal(None) + obj.resultRumor shouldBe None obj.resultMessages should equal(Some(List.empty)) } @@ -31,9 +33,19 @@ class ResultObjectSuite extends FunSuite with Matchers { } + test("rumor list constructor works") { + val obj: ResultObject = new ResultObject(ResultRumor(List(RumorExample.rumorExample))) + + obj.resultRumor shouldBe Some(List(RumorExample.rumorExample)) + obj.resultInt shouldBe None + obj.resultMessages shouldBe None + obj.resultMap shouldBe None + + } + test("isIntResult returns right result") { val obj: ResultObject = new ResultObject(1) - val obj2: ResultObject = new ResultObject(List.empty) + val obj2: ResultObject = new ResultObject(ResultMessage(List.empty)) val obj3: ResultObject = new ResultObject(Map[Channel, Set[Message]]()) obj.isIntResult should equal(true) @@ -44,11 +56,13 @@ class ResultObjectSuite extends FunSuite with Matchers { test("equals works") { val obj: ResultObject = new ResultObject(1) - val obj2: ResultObject = new ResultObject(List.empty) + val obj2: ResultObject = new ResultObject(ResultMessage(List.empty)) val obj5: ResultObject = new ResultObject(Map[Channel, Set[Message]]()) val obj3: ResultObject = new ResultObject(1) - val obj4: ResultObject = new ResultObject(List.empty) + val obj4: ResultObject = new ResultObject(ResultMessage(List.empty)) val obj6: ResultObject = new ResultObject(Map[Channel, Set[Message]]()) + val rumorResult1: ResultObject = new ResultObject(ResultRumor(List(RumorExample.rumorExample))) + val rumorResult2: ResultObject = new ResultObject(ResultRumor(List(RumorExample.rumorExample))) obj.equals(obj3) should equal(true) obj2.equals(obj4) should equal(true) @@ -56,5 +70,7 @@ class ResultObjectSuite extends FunSuite with Matchers { obj5.equals(obj6) should equal(true) obj5.equals(obj) should equal(false) obj6.equals(obj2) should equal(false) + rumorResult1.equals(rumorResult2) shouldBe true + rumorResult1.equals(obj4) shouldBe false } } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/AnswerGeneratorSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/AnswerGeneratorSuite.scala index 8d97604d95..b818965c65 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/AnswerGeneratorSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/AnswerGeneratorSuite.scala @@ -5,13 +5,13 @@ import akka.pattern.{AskableActorRef, ask} import akka.testkit.{ImplicitSender, TestKit} import akka.util.Timeout import ch.epfl.pop.model.network.method.message.Message -import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, ResultObject} +import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, ResultMessage, ResultObject} import ch.epfl.pop.model.objects.DbActorNAckException import ch.epfl.pop.pubsub.graph.validators.RpcValidator -import ch.epfl.pop.pubsub.graph.validators.SchemaVerifierSuite._ +import ch.epfl.pop.pubsub.graph.validators.SchemaVerifierSuite.* import ch.epfl.pop.storage.DbActor import org.scalatest.BeforeAndAfterAll -import org.scalatest.funsuite.{AnyFunSuiteLike => FunSuiteLike} +import org.scalatest.funsuite.AnyFunSuiteLike as FunSuiteLike import org.scalatest.matchers.should.Matchers import util.examples.MessageExample @@ -94,7 +94,7 @@ class AnswerGeneratorSuite extends TestKit(ActorSystem("Test")) with FunSuiteLik lazy val dbActorRef = mockDbWithMessages(Nil) val message: GraphMessage = new AnswerGenerator(dbActorRef).generateAnswer(Right(rpcCatchupReq)) - def resultObject: ResultObject = new ResultObject(Nil) + def resultObject: ResultObject = new ResultObject(ResultMessage(Nil)) val expected = Right(JsonRpcResponse( RpcValidator.JSON_RPC_VERSION, @@ -113,7 +113,7 @@ class AnswerGeneratorSuite extends TestKit(ActorSystem("Test")) with FunSuiteLik lazy val dbActorRef = mockDbWithMessages(messages) val gmsg: GraphMessage = new AnswerGenerator(dbActorRef).generateAnswer(Right(rpcCatchupReq)) - def resultObject: ResultObject = new ResultObject(messages) + def resultObject: ResultObject = new ResultObject(ResultMessage(messages)) val expected = Right(JsonRpcResponse( RpcValidator.JSON_RPC_VERSION,