Skip to content

Commit

Permalink
add more cats CollectionAdapter (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
xuwei-k authored Jul 9, 2023
1 parent ec4fc3e commit 86d9378
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package scalapb.validate.cats

import cats.Foldable
import cats.data.Chain
import cats.data.NonEmptyChain
import scalapb.validate.ValidationException
import scalapb.CollectionAdapter

class NonEmptyChainAdapter[T] extends CollectionAdapter[T, NonEmptyChain[T]] {
override def foreach(coll: NonEmptyChain[T])(f: T => Unit): Unit =
coll.iterator.foreach(f)

override def empty: NonEmptyChain[T] =
throw new ValidationException(
"No empty instance available for cats.Data.NonEmptyChain"
)

override def newBuilder: Builder =
List
.newBuilder[T]
.mapResult(list =>
NonEmptyChain
.fromSeq(list)
.toRight(
new ValidationException("Could not build an empty NonEmptyChain")
)
)

override def concat(
first: NonEmptyChain[T],
second: Iterable[T]
): NonEmptyChain[T] =
first :++ Chain(second.toList: _*)

override def toIterator(value: NonEmptyChain[T]): Iterator[T] = value.iterator

override def size(value: NonEmptyChain[T]): Int =
Foldable[NonEmptyChain].size(value).toInt
}

object NonEmptyChainAdapter {
def apply[T](): NonEmptyChainAdapter[T] = new NonEmptyChainAdapter[T]
}
42 changes: 42 additions & 0 deletions cats/src/main/scala/scalapb/validate/cats/NonEmptySeqAdapter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package scalapb.validate.cats

import cats.Foldable
import cats.data.NonEmptySeq
import scalapb.validate.ValidationException
import scalapb.CollectionAdapter

class NonEmptySeqAdapter[T] extends CollectionAdapter[T, NonEmptySeq[T]] {
override def foreach(coll: NonEmptySeq[T])(f: T => Unit): Unit =
coll.iterator.foreach(f)

override def empty: NonEmptySeq[T] =
throw new ValidationException(
"No empty instance available for cats.Data.NonEmptySeq"
)

override def newBuilder: Builder =
List
.newBuilder[T]
.mapResult(list =>
NonEmptySeq
.fromSeq(list)
.toRight(
new ValidationException("Could not build an empty NonEmptySeq")
)
)

override def concat(
first: NonEmptySeq[T],
second: Iterable[T]
): NonEmptySeq[T] =
first.appendSeq(second.toList)

override def toIterator(value: NonEmptySeq[T]): Iterator[T] = value.iterator

override def size(value: NonEmptySeq[T]): Int =
Foldable[NonEmptySeq].size(value).toInt
}

object NonEmptySeqAdapter {
def apply[T](): NonEmptySeqAdapter[T] = new NonEmptySeqAdapter[T]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package scalapb.validate.cats

import cats.Foldable
import cats.data.NonEmptyVector
import scalapb.validate.ValidationException
import scalapb.CollectionAdapter

class NonEmptyVectorAdapter[T] extends CollectionAdapter[T, NonEmptyVector[T]] {
override def foreach(coll: NonEmptyVector[T])(f: T => Unit): Unit =
coll.iterator.foreach(f)

override def empty: NonEmptyVector[T] =
throw new ValidationException(
"No empty instance available for cats.Data.NonEmptyVector"
)

override def newBuilder: Builder =
Vector
.newBuilder[T]
.mapResult(list =>
NonEmptyVector
.fromVector(list)
.toRight(
new ValidationException("Could not build an empty NonEmptyVector")
)
)

override def concat(
first: NonEmptyVector[T],
second: Iterable[T]
): NonEmptyVector[T] =
first.appendVector(second.toVector)

override def toIterator(value: NonEmptyVector[T]): Iterator[T] =
value.iterator

override def size(value: NonEmptyVector[T]): Int =
Foldable[NonEmptyVector].size(value).toInt
}

object NonEmptyVectorAdapter {
def apply[T](): NonEmptyVectorAdapter[T] = new NonEmptyVectorAdapter[T]
}
34 changes: 34 additions & 0 deletions e2e/src/main/protobuf/cats/non_empty_chain.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
syntax = "proto3";

package e2e.cats;

import "scalapb/scalapb.proto";
import "validate/validate.proto";

option (scalapb.options) = {
scope: FILE
field_transformations: [
{
when: {
options {
[validate.rules] {
repeated: {min_items: 1}
}
}
}
set: {
[scalapb.field] {
collection: {
type: "_root_.cats.data.NonEmptyChain"
adapter: "_root_.scalapb.validate.cats.NonEmptyChainAdapter"
non_empty: true
}
}
}
}
]
};

message NonEmptyChainTest {
repeated bool values = 1 [(validate.rules).repeated = { min_items: 1 }];
}
34 changes: 34 additions & 0 deletions e2e/src/main/protobuf/cats/non_empty_seq.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
syntax = "proto3";

package e2e.cats;

import "scalapb/scalapb.proto";
import "validate/validate.proto";

option (scalapb.options) = {
scope: FILE
field_transformations: [
{
when: {
options {
[validate.rules] {
repeated: {min_items: 1}
}
}
}
set: {
[scalapb.field] {
collection: {
type: "_root_.cats.data.NonEmptySeq"
adapter: "_root_.scalapb.validate.cats.NonEmptySeqAdapter"
non_empty: true
}
}
}
}
]
};

message NonEmptySeqTest {
repeated int32 values = 1 [(validate.rules).repeated = { min_items: 1 }];
}
34 changes: 34 additions & 0 deletions e2e/src/main/protobuf/cats/non_empty_vector.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
syntax = "proto3";

package e2e.cats;

import "scalapb/scalapb.proto";
import "validate/validate.proto";

option (scalapb.options) = {
scope: FILE
field_transformations: [
{
when: {
options {
[validate.rules] {
repeated: {min_items: 1}
}
}
}
set: {
[scalapb.field] {
collection: {
type: "_root_.cats.data.NonEmptyVector"
adapter: "_root_.scalapb.validate.cats.NonEmptyVectorAdapter"
non_empty: true
}
}
}
}
]
};

message NonEmptyVectorTest {
repeated string values = 1 [(validate.rules).repeated = { min_items: 1 }];
}
33 changes: 33 additions & 0 deletions e2e/src/test/scala/scalapb/validate/cats/NonEmptySpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package scalapb.validate.cats

import _root_.cats.data.NonEmptyChain
import _root_.cats.data.NonEmptySeq
import _root_.cats.data.NonEmptyVector
import e2e.cats.non_empty_seq.NonEmptySeqTest
import e2e.cats.non_empty_vector.NonEmptyVectorTest
import e2e.cats.non_empty_chain.NonEmptyChainTest
import scalapb.validate.ValidationHelpers
import scalapb.validate.Validator

class NonEmptySpec extends munit.FunSuite with ValidationHelpers {
test("NonEmptySeq serialize and parse successfully") {
val x = NonEmptySeqTest.of(NonEmptySeq.of(1, 2, 3))
assertEquals(NonEmptySeqTest.parseFrom(x.toByteArray), x)
assertEquals(NonEmptySeqTest.fromAscii(x.toProtoString), x)
assert(Validator[NonEmptySeqTest].validate(x).isSuccess)
}

test("NonEmptyVector serialize and parse successfully") {
val x = NonEmptyVectorTest.of(NonEmptyVector.of("a", "b", "c"))
assertEquals(NonEmptyVectorTest.parseFrom(x.toByteArray), x)
assertEquals(NonEmptyVectorTest.fromAscii(x.toProtoString), x)
assert(Validator[NonEmptyVectorTest].validate(x).isSuccess)
}

test("NonEmptyChain serialize and parse successfully") {
val x = NonEmptyChainTest.of(NonEmptyChain.of(true, false))
assertEquals(NonEmptyChainTest.parseFrom(x.toByteArray), x)
assertEquals(NonEmptyChainTest.fromAscii(x.toProtoString), x)
assert(Validator[NonEmptyChainTest].validate(x).isSuccess)
}
}

0 comments on commit 86d9378

Please sign in to comment.