Skip to content

Commit

Permalink
Codec for either and java enums
Browse files Browse the repository at this point in the history
The code generation plugin uses java enums.
  • Loading branch information
eikek committed Jan 19, 2024
1 parent 8419f9d commit 04489f9
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 1 deletion.
18 changes: 18 additions & 0 deletions modules/avro-codec/src/main/scala/io/renku/avro/codec/AvroIO.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec
import org.apache.avro.Schema
import scodec.bits.ByteVector
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec

import org.apache.avro.Schema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec

import org.apache.avro.Schema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec

import org.apache.avro.file.SeekableInput
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec.decoders

import io.renku.avro.codec.{AvroCodecException, AvroDecoder}

trait EitherDecoders {

given [A, B](using
da: AvroDecoder[A],
db: AvroDecoder[B],
ta: TypeGuardedDecoding[A],
tb: TypeGuardedDecoding[B]
): AvroDecoder[Either[A, B]] =
AvroDecoder.curried[Either[A, B]] { schema =>
require(schema.isUnion)
require(schema.getTypes.size() == 2)

val leftSchema = schema.getTypes.get(0)
val rightSchema = schema.getTypes.get(1)

{ value =>
if (ta.guard(leftSchema).isDefinedAt(value)) Left(da.decode(schema)(value))
else if (tb.guard(rightSchema).isDefinedAt(value))
Right(db.decode(schema)(value))
else {
val nameA = leftSchema.getFullName
val nameB = rightSchema.getFullName
throw AvroCodecException.decode(
s"Could not decode $value into Either[$nameA, $nameB]"
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec.decoders

import io.renku.avro.codec.AvroDecoder
import org.apache.avro.generic.GenericEnumSymbol

import scala.reflect.ClassTag

trait JavaEnumDecoders {

given [E <: Enum[E]](using ctag: ClassTag[E]): AvroDecoder[E] = AvroDecoder.basic {
case e: Enum[?] => e.asInstanceOf[E]
case e: GenericEnumSymbol[?] =>
Enum.valueOf[E](ctag.runtimeClass.asInstanceOf[Class[E]], e.toString)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec.decoders

import org.apache.avro.Schema
import org.apache.avro.generic.{GenericContainer, GenericFixed}
import org.apache.avro.util.Utf8

import java.nio.ByteBuffer
import java.util.UUID

// Taken from https://github.com/sksamuel/avro4s

trait TypeGuardedDecoding[T] extends Serializable {
def guard(schema: Schema): PartialFunction[Any, Boolean]
}

object TypeGuardedDecoding {

def apply[T](using g: TypeGuardedDecoding[T]): TypeGuardedDecoding[T] = g

given TypeGuardedDecoding[String] = new TypeGuardedDecoding[String]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Utf8 => true
case v: String => true
}

given TypeGuardedDecoding[Boolean] = new TypeGuardedDecoding[Boolean]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Boolean => true
}

given TypeGuardedDecoding[Double] = new TypeGuardedDecoding[Double]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Double => true
case v: Float => true
}

given TypeGuardedDecoding[Float] = new TypeGuardedDecoding[Float]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = { case v: Float =>
true
}

given TypeGuardedDecoding[Long] = new TypeGuardedDecoding[Long]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Long => true
case v: Int => true
case v: Short => true
case v: Byte => true
}

given TypeGuardedDecoding[Int] = new TypeGuardedDecoding[Int]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = { case v: Int =>
true
}

given TypeGuardedDecoding[UUID] = new TypeGuardedDecoding[UUID]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Utf8 => true
case v: String => true
}

given [T]: TypeGuardedDecoding[Map[String, T]] =
new TypeGuardedDecoding[Map[String, T]]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: java.util.Map[_, _] => true
}

given TypeGuardedDecoding[Array[Byte]] = new TypeGuardedDecoding[Array[Byte]]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: ByteBuffer => true
case v: Array[Byte] => true
case v: GenericFixed => true
}

given TypeGuardedDecoding[ByteBuffer] = new TypeGuardedDecoding[ByteBuffer]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: ByteBuffer => true
case v: Array[Byte] => true
case v: GenericFixed => true
}

given [T]: TypeGuardedDecoding[List[T]] = new TypeGuardedDecoding[List[T]]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Array[_] => true
case v: java.util.Collection[_] => true
case v: Iterable[_] => true
}

given [T]: TypeGuardedDecoding[Seq[T]] = new TypeGuardedDecoding[Seq[T]]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: Array[_] => true
case v: java.util.Collection[_] => true
case v: Iterable[_] => true
}

given [T]: TypeGuardedDecoding[T] = new TypeGuardedDecoding[T]:
override def guard(schema: Schema): PartialFunction[Any, Boolean] = {
case v: GenericContainer if v.getSchema.getFullName == schema.getFullName => true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ object all
with OptionDecoders
with CollectionDecoders
with ByteArrayDecoders
with JavaEnumDecoders
with EitherDecoders
with RecordDecoders
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec.encoders

import io.renku.avro.codec.AvroEncoder

trait EitherEncoders {

given [A, B](using ea: AvroEncoder[A], eb: AvroEncoder[B]): AvroEncoder[Either[A, B]] =
AvroEncoder.curried { schema =>
require(schema.isUnion, s"Either must use a union schema. Got: ${schema.getType}")
require(
schema.getTypes.size() == 2,
s"Either must use a UNION of two types. Got: ${schema.getTypes}"
)

{
case Left(a) => ea.encode(schema.getTypes.get(0))(a)
case Right(b) => eb.encode(schema.getTypes.get(1))(b)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.avro.codec.encoders

import io.renku.avro.codec.AvroEncoder
import org.apache.avro.Schema
import org.apache.avro.generic.GenericData

trait JavaEnumEncoders {

given [E <: Enum[E]]: AvroEncoder[E] =
AvroEncoder.curried { schema => e =>
require(
schema.getType == Schema.Type.ENUM,
s"schema is not an enum: $schema (${schema.getType})"
)
GenericData.get().createEnum(e.name(), schema)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ object all
with BigDecimalEncoders
with DateTimeEncoders
with OptionEncoders
with EitherEncoders
with CollectionEncoders
with ByteArrayEncoders
with JavaEnumEncoders
with RecordEncoders
Loading

0 comments on commit 04489f9

Please sign in to comment.