-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: events in Redis to get all avro spec Header entries
- Loading branch information
Showing
11 changed files
with
342 additions
and
67 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
modules/redis-client/src/main/scala/io/renku/queue/client/Header.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* 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.queue.client | ||
|
||
import java.time.Instant | ||
|
||
final case class Header( | ||
source: Option[MessageSource], | ||
messageType: Option[MessageType], | ||
dataContentType: DataContentType, | ||
schemaVersion: Option[SchemaVersion], | ||
time: Option[CreationTime], | ||
requestId: Option[RequestId] | ||
) | ||
|
||
object Header: | ||
def apply(contentType: DataContentType): Header = | ||
Header( | ||
source = None, | ||
messageType = None, | ||
dataContentType = contentType, | ||
schemaVersion = None, | ||
time = None, | ||
requestId = None | ||
) | ||
|
||
opaque type MessageSource = String | ||
object MessageSource: | ||
def apply(v: String): MessageSource = v | ||
extension (self: MessageSource) def value: String = self | ||
|
||
opaque type MessageType = String | ||
object MessageType: | ||
def apply(v: String): MessageType = v | ||
extension (self: MessageType) def value: String = self | ||
|
||
enum DataContentType(val mimeType: String): | ||
lazy val name: String = productPrefix | ||
case Binary extends DataContentType("application/avro+binary") | ||
case Json extends DataContentType("application/avro+json") | ||
|
||
object DataContentType: | ||
def from(mimeType: String): Either[Throwable, DataContentType] = | ||
DataContentType.values.toList | ||
.find(_.mimeType == mimeType) | ||
.toRight( | ||
new IllegalArgumentException(s"'$mimeType' not a valid 'DataContentType' value") | ||
) | ||
|
||
opaque type SchemaVersion = String | ||
object SchemaVersion: | ||
def apply(v: String): SchemaVersion = v | ||
extension (self: SchemaVersion) def value: String = self | ||
|
||
opaque type CreationTime = Instant | ||
object CreationTime: | ||
def apply(v: Instant): CreationTime = v | ||
extension (self: CreationTime) def value: Instant = self | ||
|
||
opaque type RequestId = String | ||
object RequestId: | ||
def apply(v: String): RequestId = v | ||
extension (self: RequestId) def value: String = self |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
modules/redis-client/src/main/scala/io/renku/redis/client/MessageBodyKeys.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* 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.redis.client | ||
|
||
private object MessageBodyKeys: | ||
val payload = "payload" | ||
val source = "source" | ||
val messageType = "type" | ||
val contentType = "dataContentType" | ||
val schemaVersion = "schemaVersion" | ||
val time = "time" | ||
val requestId = "requestId" |
109 changes: 109 additions & 0 deletions
109
modules/redis-client/src/main/scala/io/renku/redis/client/RedisMessage.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* 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.redis.client | ||
|
||
import cats.syntax.all.* | ||
import dev.profunktor.redis4cats.streams.data.XReadMessage | ||
import io.renku.queue.client.{DataContentType, Header, Message, MessageId} | ||
import scodec.bits.ByteVector | ||
|
||
import java.time.Instant | ||
|
||
private object RedisMessage: | ||
|
||
def bodyFrom( | ||
header: Header, | ||
payload: ByteVector | ||
): Either[Throwable, Map[String, ByteVector]] = | ||
BodyMap() | ||
.add(MessageBodyKeys.payload, payload) | ||
.flatMap(_.add(MessageBodyKeys.contentType, header.dataContentType.mimeType)) | ||
.flatMap(_.maybeAdd(MessageBodyKeys.source, header.source.map(_.value))) | ||
.flatMap(_.maybeAdd(MessageBodyKeys.messageType, header.messageType.map(_.value))) | ||
.flatMap( | ||
_.maybeAdd(MessageBodyKeys.schemaVersion, header.schemaVersion.map(_.value)) | ||
) | ||
.flatMap( | ||
_.add(MessageBodyKeys.time, header.time.map(_.value).getOrElse(Instant.now())) | ||
) | ||
.flatMap(_.maybeAdd(MessageBodyKeys.requestId, header.requestId.map(_.value))) | ||
.map(_.body) | ||
|
||
def toMessage( | ||
rm: XReadMessage[String, ByteVector] | ||
): Either[Throwable, Option[Message]] = | ||
val bodyMap = BodyMap(rm.body) | ||
for | ||
maybeContentType <- bodyMap | ||
.get[String](MessageBodyKeys.contentType) | ||
.flatMap(_.map(DataContentType.from).sequence) | ||
maybePayload <- bodyMap.get[ByteVector](MessageBodyKeys.payload) | ||
yield (maybeContentType, maybePayload) | ||
.mapN(Message(MessageId(rm.id.value), _, _)) | ||
|
||
private case class BodyMap(body: Map[String, ByteVector] = Map.empty): | ||
|
||
def add[V](key: String, value: V)(using | ||
encoder: ValueEncoder[V] | ||
): Either[Throwable, BodyMap] = | ||
encoder | ||
.encode(value) | ||
.map(encV => copy(body = body + (key -> encV))) | ||
|
||
def maybeAdd[V](key: String, maybeV: Option[V])(using | ||
encoder: ValueEncoder[V] | ||
): Either[Throwable, BodyMap] = | ||
maybeV | ||
.map(add(key, _)) | ||
.getOrElse(this.asRight) | ||
|
||
def apply[V](key: String)(using | ||
decoder: ValueDecoder[V] | ||
): Either[Throwable, V] = | ||
get(key).flatMap(_.toRight(new Exception(s"No '$key' in Redis message"))) | ||
|
||
def get[V](key: String)(using | ||
decoder: ValueDecoder[V] | ||
): Either[Throwable, Option[V]] = | ||
body.get(key).map(decoder.decode).sequence | ||
|
||
private trait ValueEncoder[A]: | ||
def encode(v: A): Either[Throwable, ByteVector] | ||
def contramap[B](f: B => A): ValueEncoder[B] = (b: B) => encode(f(b)) | ||
|
||
private object ValueEncoder: | ||
def apply[A](using enc: ValueEncoder[A]): ValueEncoder[A] = enc | ||
|
||
private given ValueEncoder[String] = ByteVector.encodeUtf8(_) | ||
private given ValueEncoder[ByteVector] = identity(_).asRight | ||
private given ValueEncoder[Long] = ByteVector.fromLong(_).asRight | ||
private given ValueEncoder[Instant] = | ||
ValueEncoder[Long].contramap[Instant](_.toEpochMilli) | ||
|
||
private trait ValueDecoder[A]: | ||
def decode(bv: ByteVector): Either[Throwable, A] | ||
def map[B](f: A => B): ValueDecoder[B] = (bv: ByteVector) => decode(bv).map(f) | ||
|
||
private object ValueDecoder: | ||
def apply[A](using dec: ValueDecoder[A]): ValueDecoder[A] = dec | ||
|
||
private given ValueDecoder[String] = _.decodeUtf8 | ||
private given ValueDecoder[ByteVector] = identity(_).asRight | ||
private given ValueDecoder[Long] = _.toLong().asRight | ||
private given ValueDecoder[Instant] = ValueDecoder[Long].map(Instant.ofEpochMilli) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.