Skip to content

Commit

Permalink
Merge pull request #1889 from dedis/work-be2-daniel-response-pull-sta…
Browse files Browse the repository at this point in the history
…te-json

[BE2] Added RumorStateAns to possible values of response
  • Loading branch information
K1li4nL committed May 29, 2024
2 parents f7254f2 + 520214c commit 08b8aab
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 27 deletions.
24 changes: 19 additions & 5 deletions be2-scala/src/main/scala/ch/epfl/pop/json/HighLevelProtocol.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down Expand Up @@ -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] {
Expand All @@ -250,18 +252,30 @@ 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 = {
if (obj.isIntResult) {
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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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))
}

Expand All @@ -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)
Expand All @@ -44,17 +56,21 @@ 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)
obj2.equals(obj) should equal(false)
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit 08b8aab

Please sign in to comment.