Skip to content

Commit

Permalink
non empty map codec support
Browse files Browse the repository at this point in the history
  • Loading branch information
chr12c committed Sep 8, 2023
1 parent 0018130 commit 003ff06
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 2 deletions.
20 changes: 18 additions & 2 deletions modules/core/src/main/scala/vulcan/Codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
package vulcan

import cats.{Invariant, Show, ~>}
import cats.data.{Chain, NonEmptyChain, NonEmptyList, NonEmptySet, NonEmptyVector}
import cats.data.{Chain, NonEmptyChain, NonEmptyList, NonEmptyMap, NonEmptySet, NonEmptyVector}
import cats.free.FreeApplicative
import cats.implicits._

Expand All @@ -23,7 +23,7 @@ import vulcan.Avro.Bytes
import vulcan.internal.{Deserializer, Serializer}

import scala.annotation.implicitNotFound
import scala.collection.immutable.SortedSet
import scala.collection.immutable.{SortedMap, SortedSet}
import vulcan.internal.converters.collection._
import vulcan.internal.syntax._
import vulcan.internal.schema.adaptForSchema
Expand Down Expand Up @@ -972,6 +972,22 @@ object Codec extends CodecCompanionCompat {
)(_.toVector)
.withTypeName("NonEmptyVector")

/**
* @group Cats
*/
implicit final def nonEmptyMap[A](
implicit codec: Codec[A]
): Codec.Aux[Avro.Map[codec.AvroType], NonEmptyMap[String, A]] =
Codec
.map[A]
.imapError(
map =>
NonEmptyMap
.fromMap(SortedMap.from(map))
.toRight(AvroError.decodeEmptyCollection)
)(_.toSortedMap)
.withTypeName("NonEmptyMap")

/**
* @group General
*/
Expand Down
71 changes: 71 additions & 0 deletions modules/core/src/test/scala/vulcan/CodecSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,77 @@ final class CodecSpec extends BaseSpec with CodecSpecHelpers {
}
}

describe("nonEmptyMap") {
describe("schema") {
it("should be encoded as map") {
assertSchemaIs[NonEmptyMap[String, Int]] {
"""{"type":"map","values":"int"}"""
}
}
}

describe("encode") {
it("should encode as java map using encoder for value") {
assertEncodeIs[NonEmptyMap[String, Int]](
NonEmptyMap.of("key1" -> 1, "key2" -> 2, "key3" -> 3),
Right(
Map(Avro.String("key1") -> 1, Avro.String("key2") -> 2, Avro.String("key3") -> 3).asJava
)
)
}
}

describe("decode") {
it("should error if schema is not map") {
assertDecodeError[NonEmptyMap[String, Int]](
unsafeEncode[NonEmptyMap[String, Int]](NonEmptyMap.one("key", 1)),
SchemaBuilder.builder().intType(),
"Error decoding NonEmptyMap: Error decoding Map: Got unexpected schema type INT, expected schema type MAP"
)
}

it("should error if value is not java.util.Map") {
assertDecodeError[NonEmptyMap[String, Int]](
123,
unsafeSchema[NonEmptyMap[String, Int]],
"Error decoding NonEmptyMap: Error decoding Map: Got unexpected type java.lang.Integer, expected type java.util.Map"
)
}

it("should error if keys are not strings") {
assertDecodeError[NonEmptyMap[String, Int]](
NonEmptyMap.one(1, 2).toSortedMap.asJava,
unsafeSchema[NonEmptyMap[String, Int]],
"Error decoding NonEmptyMap: Error decoding Map: Got unexpected map key with type java.lang.Integer, expected Utf8"
)
}

it("should error if any keys are null") {
assertDecodeError[NonEmptyMap[String, Int]](
Map((null, 2)).asJava,
unsafeSchema[NonEmptyMap[String, Int]],
"Error decoding NonEmptyMap: Error decoding Map: Got unexpected map key with type null, expected Utf8"
)
}

it("should error on empty collection") {
assertDecodeError[NonEmptyMap[String, Int]](
unsafeEncode(Map.empty[String, Int]),
unsafeSchema[NonEmptyMap[String, Int]],
"Error decoding NonEmptyMap: Got unexpected empty collection"
)
}

it("should decode to map using decoder for value") {
val value = NonEmptyMap.of("key1" -> 1, "key2" -> 2, "key3" -> 3)
assertDecodeIs[NonEmptyMap[String, Int]](
unsafeEncode[NonEmptyMap[String, Int]](value),
Right(value)
)
}
}
}

describe("option") {
describe("schema") {
it("should be encoded as union") {
Expand Down

0 comments on commit 003ff06

Please sign in to comment.