|
| 1 | +package io.sphere.json.generic |
| 2 | + |
| 3 | +import cats.data.Validated.Valid |
| 4 | +import cats.implicits._ |
| 5 | +import io.sphere.json.{JSON, JValidation, parseJSON} |
| 6 | +import io.sphere.util.test._ |
| 7 | +import org.json4s.DefaultReaders.StringReader |
| 8 | +import org.json4s._ |
| 9 | +import org.scalatest.matchers.must.Matchers |
| 10 | +import org.scalatest.wordspec.AnyWordSpec |
| 11 | + |
| 12 | +/** Shared test case logic for jsonTypeSwitch, because the scala 3 tests has a new syntax for |
| 13 | + * jsonTypeSwitch (using tuples, instead of fixed parameter lists) This way we can tests both |
| 14 | + * syntaxes |
| 15 | + */ |
| 16 | +trait JsonTypeSwitchSpecCommon { self: AnyWordSpec with Matchers => |
| 17 | + import JsonTypeSwitchModels._ |
| 18 | + |
| 19 | + def testDeriveASubsetOfASealedTrait(format: JSON[A]): Unit = { |
| 20 | + val b = B(123) |
| 21 | + val jsonB = format.write(b) |
| 22 | + val b2 = format.read(jsonB).expectValid |
| 23 | + b2 must be(b) |
| 24 | + |
| 25 | + val c = C(2345345) |
| 26 | + val jsonC = format.write(c) |
| 27 | + val c2 = format.read(jsonC).expectValid |
| 28 | + c2 must be(c) |
| 29 | + } |
| 30 | + |
| 31 | + def testDeriveSubsetWithMongoKey(format: JSON[A]): Unit = { |
| 32 | + val d = D(123) |
| 33 | + val json = format.write(d) |
| 34 | + val d2 = format.read(json) |
| 35 | + |
| 36 | + (json \ "type").as[String] must be("D2") |
| 37 | + d2 must be(Valid(d)) |
| 38 | + } |
| 39 | + |
| 40 | + def testCombineSumTypes(format: JSON[Message]): Unit = { |
| 41 | + val m: Seq[Message] = List( |
| 42 | + TypeA.ClassA1(23), |
| 43 | + TypeA.ClassA2("world"), |
| 44 | + TypeB.ClassB1(valid = false), |
| 45 | + TypeB.ClassB2(Seq("a23", "c62"))) |
| 46 | + |
| 47 | + val jsons = m.map(format.write) |
| 48 | + jsons must be( |
| 49 | + List( |
| 50 | + JObject("number" -> JLong(23), "type" -> JString("ClassA1")), |
| 51 | + JObject("name" -> JString("world"), "type" -> JString("ClassA2")), |
| 52 | + JObject("valid" -> JBool(false), "type" -> JString("ClassB1")), |
| 53 | + JObject( |
| 54 | + "references" -> JArray(List(JString("a23"), JString("c62"))), |
| 55 | + "type" -> JString("ClassB2")) |
| 56 | + )) |
| 57 | + |
| 58 | + val messages = jsons.map(format.read).map(_.toOption.get) |
| 59 | + messages must be(m) |
| 60 | + } |
| 61 | + |
| 62 | + def testCustomSubtypeImpl(format: JSON[A]): Unit = { |
| 63 | + check[A](D(2345), """ {"type": "D2", "int": 2345 } """)(format) |
| 64 | + check[A](C(4), """ {"type": "C", "int": 4 } """)(format) |
| 65 | + check[A](B(34), """ {"type": "B", "field": "Custom-B-34" } """)(format) |
| 66 | + } |
| 67 | + |
| 68 | + def testPlatformFormattedNotificationCase(): Unit = { |
| 69 | + val formatSub2 = jsonTypeSwitch[SubTrait2, SubTrait2.O3.type, SubTrait2.O4.type](Nil) |
| 70 | + val formatSub3 = jsonTypeSwitch[SubTrait3, SubTrait3.O5.type, SubTrait3.O6.type](Nil) |
| 71 | + |
| 72 | + val typeSelectors = formatSub2.typeSelectors ++ formatSub3.typeSelectors |
| 73 | + val formatSuper: JSON[SuperTrait] = jsonTypeSwitch[SuperTrait, SubTrait1](typeSelectors) |
| 74 | + |
| 75 | + val objs = |
| 76 | + List[SuperTrait]( |
| 77 | + SubTrait1.O1, |
| 78 | + SubTrait1.O2, |
| 79 | + SubTrait2.O3, |
| 80 | + SubTrait2.O4, |
| 81 | + SubTrait3.O5, |
| 82 | + SubTrait3.O6) |
| 83 | + |
| 84 | + val res = objs.map(formatSuper.write).map(formatSuper.read).sequence.expectValid |
| 85 | + |
| 86 | + res must be(objs) |
| 87 | + } |
| 88 | + |
| 89 | + private def check[T](a: T, json: String)(format: JSON[T]): Unit = { |
| 90 | + val parsedJson = parseJSON(json).expectValid |
| 91 | + val json2 = format.write(a) |
| 92 | + json2 must be(parsedJson) |
| 93 | + format.read(json2).expectValid must be(a) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +object JsonTypeSwitchModels { |
| 98 | + sealed trait A |
| 99 | + case class B(int: Int) extends A |
| 100 | + object B { implicit val json: JSON[B] = deriveJSON } |
| 101 | + |
| 102 | + case class C(int: Int) extends A |
| 103 | + object C { implicit val json: JSON[C] = deriveJSON } |
| 104 | + |
| 105 | + @JSONTypeHint("D2") case class D(int: Int) extends A |
| 106 | + object D { implicit val json: JSON[D] = deriveJSON } |
| 107 | + |
| 108 | + trait Message |
| 109 | + |
| 110 | + sealed trait TypeA extends Message |
| 111 | + object TypeA { |
| 112 | + case class ClassA1(number: Int) extends TypeA |
| 113 | + case class ClassA2(name: String) extends TypeA |
| 114 | + implicit val json: JSON[TypeA] = deriveJSON[TypeA] |
| 115 | + } |
| 116 | + |
| 117 | + sealed trait TypeB extends Message |
| 118 | + object TypeB { |
| 119 | + case class ClassB1(valid: Boolean) extends TypeB |
| 120 | + case class ClassB2(references: Seq[String]) extends TypeB |
| 121 | + implicit val json: JSON[TypeB] = deriveJSON[TypeB] |
| 122 | + } |
| 123 | + |
| 124 | + trait SuperTrait |
| 125 | + |
| 126 | + sealed trait SubTrait1 extends SuperTrait |
| 127 | + object SubTrait1 { |
| 128 | + case object O1 extends SubTrait1 |
| 129 | + case object O2 extends SubTrait1 |
| 130 | + implicit val json: JSON[SubTrait1] = deriveJSON |
| 131 | + } |
| 132 | + |
| 133 | + sealed trait SubTrait2 extends SuperTrait |
| 134 | + object SubTrait2 { |
| 135 | + case object O3 extends SubTrait2 { implicit val json: JSON[O3.type] = deriveJSON } |
| 136 | + case object O4 extends SubTrait2 { implicit val json: JSON[O4.type] = deriveJSON } |
| 137 | + implicit val json: JSON[SubTrait2] = deriveJSON |
| 138 | + } |
| 139 | + |
| 140 | + sealed trait SubTrait3 extends SuperTrait |
| 141 | + object SubTrait3 { |
| 142 | + case object O5 extends SubTrait3 { implicit val json: JSON[O5.type] = deriveJSON } |
| 143 | + case object O6 extends SubTrait3 { implicit val json: JSON[O6.type] = deriveJSON } |
| 144 | + implicit val json: JSON[SubTrait3] = deriveJSON |
| 145 | + } |
| 146 | + |
| 147 | + sealed trait SubTrait4 extends SuperTrait |
| 148 | + object SubTrait4 { |
| 149 | + case object O7 extends SubTrait4 { implicit val json: JSON[O7.type] = deriveJSON } |
| 150 | + case object O8 extends SubTrait4 { implicit val json: JSON[O8.type] = deriveJSON } |
| 151 | + implicit val json: JSON[SubTrait4] = deriveJSON |
| 152 | + } |
| 153 | + |
| 154 | + /** A custom JSON[B] for testing custom subtype implementations */ |
| 155 | + val customJsonB: JSON[B] = new JSON[B] { |
| 156 | + override def read(jval: JValue): JValidation[B] = jval match { |
| 157 | + // JObject(List((field,JString(Custom-B-34)), (type,JString(B)))) |
| 158 | + case JObject(fields) => |
| 159 | + fields.collectFirst { case ("field", JString(s)) if s.startsWith("Custom-B-") => s } match { |
| 160 | + case Some(s) => Valid(B(s.stripPrefix("Custom-B-").toInt)) |
| 161 | + case None => ??? |
| 162 | + } |
| 163 | + case _ => ??? |
| 164 | + } |
| 165 | + |
| 166 | + override def write(value: B): JValue = |
| 167 | + JObject(List("field" -> JString(s"Custom-B-${value.int}"))) |
| 168 | + } |
| 169 | +} |
0 commit comments