Skip to content

Commit

Permalink
feat: migrate mercury protocols and repositories from Circe to ZIO Js…
Browse files Browse the repository at this point in the history
…on (#1471)

Signed-off-by: Benjamin Voiturier <[email protected]>
  • Loading branch information
bvoiturier authored Dec 6, 2024
1 parent c6a3e0c commit d0c3506
Show file tree
Hide file tree
Showing 94 changed files with 929 additions and 1,124 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
package org.hyperledger.identus.castor.core.model

import com.google.protobuf.ByteString
import io.circe.Json
import io.iohk.atala.prism.protos.{common_models, node_api, node_models}
import io.iohk.atala.prism.protos.{node_api, node_models}
import io.iohk.atala.prism.protos.common_models.OperationStatus
import io.iohk.atala.prism.protos.node_models.KeyUsage
import io.iohk.atala.prism.protos.node_models.PublicKey.KeyData
import org.hyperledger.identus.castor.core.model.did.{
DIDData,
EllipticCurve,
InternalKeyPurpose,
InternalPublicKey,
PrismDID,
PrismDIDOperation,
PublicKey,
PublicKeyData,
ScheduledDIDOperationDetail,
ScheduledDIDOperationStatus,
Service,
ServiceEndpoint,
ServiceType,
SignedPrismDIDOperation,
UpdateDIDAction,
VerificationRelationship
}
import org.hyperledger.identus.castor.core.model.did.*
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{value, UriOrJsonEndpoint}
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}
import org.hyperledger.identus.shared.utils.Traverse.*
import zio.*
import zio.json.{DecoderOps, EncoderOps}
import zio.json.ast.Json

import java.time.Instant
import scala.language.implicitConversions
Expand Down Expand Up @@ -191,8 +174,8 @@ private[castor] trait ProtoModelHelper {
serviceType match {
case ServiceType.Single(name) => name.value
case ts: ServiceType.Multiple =>
val names = ts.values.map(_.value).map(Json.fromString)
Json.arr(names*).noSpaces
val names = ts.values.map(_.value).map(Json.Str(_))
Json.Arr(names*).toJson
}
}
}
Expand All @@ -203,14 +186,14 @@ private[castor] trait ProtoModelHelper {
case ServiceEndpoint.Single(value) =>
value match {
case UriOrJsonEndpoint.Uri(uri) => uri.value
case UriOrJsonEndpoint.Json(json) => Json.fromJsonObject(json).noSpaces
case UriOrJsonEndpoint.Json(json) => json.toJson
}
case endpoints: ServiceEndpoint.Multiple =>
val uris = endpoints.values.map {
case UriOrJsonEndpoint.Uri(uri) => Json.fromString(uri.value)
case UriOrJsonEndpoint.Json(json) => Json.fromJsonObject(json)
case UriOrJsonEndpoint.Uri(uri) => Json.Str(uri.value)
case UriOrJsonEndpoint.Json(json) => json
}
Json.arr(uris*).noSpaces
Json.Arr(uris*).toJson
}
}
}
Expand Down Expand Up @@ -356,24 +339,24 @@ private[castor] trait ProtoModelHelper {

def parseServiceType(s: String): Either[String, ServiceType] = {
// The type field MUST be a string or a non-empty JSON array of strings.
val parsedJson: Option[Either[String, ServiceType.Multiple]] = io.circe.parser
.parse(s)
.toOption // it's OK to let parsing fail (e.g. LinkedDomains without quote is not a JSON string)
.flatMap(_.asArray)
.map { jsonArr =>
jsonArr
.traverse(_.asString.toRight("the service type is not a JSON array of strings"))
.flatMap(_.traverse(ServiceType.Name.fromString))
.map(_.toList)
.flatMap {
case head :: tail => Right(ServiceType.Multiple(head, tail))
case Nil => Left("the service type cannot be an empty JSON array")
}
.filterOrElse(
_ => s == io.circe.Json.arr(jsonArr*).noSpaces,
"the service type is a valid JSON array of strings, but not conform to the ABNF"
)
}
val parsedJson: Option[Either[String, ServiceType.Multiple]] =
s.fromJson[Json]
.toOption // it's OK to let parsing fail (e.g. LinkedDomains without quote is not a JSON string)
.flatMap(_.asArray)
.map { jsonArr =>
jsonArr
.traverse(_.asString.toRight("the service type is not a JSON array of strings"))
.flatMap(_.traverse(ServiceType.Name.fromString))
.map(_.toList)
.flatMap {
case head :: tail => Right(ServiceType.Multiple(head, tail))
case Nil => Left("the service type cannot be an empty JSON array")
}
.filterOrElse(
_ => s == Json.Arr(jsonArr*).toJson,
"the service type is a valid JSON array of strings, but not conform to the ABNF"
)
}

parsedJson match {
// serviceType is a valid JSON array of strings
Expand All @@ -391,22 +374,22 @@ private[castor] trait ProtoModelHelper {
* 2. a JSON object
* 3. a non-empty JSON array of URIs and/or JSON objects
*/
val parsedJson: Option[Either[String, ServiceEndpoint]] = io.circe.parser
.parse(s)
.toOption // it's OK to let parsing fail (e.g. http://example.com without quote is not a JSON string)
.flatMap { json =>
val parsedObject = json.asObject.map(obj => Right(ServiceEndpoint.Single(obj)))
val parsedArray = json.asArray.map(_.traverse[String, UriOrJsonEndpoint] { js =>
val obj = js.asObject.map(obj => Right(obj: UriOrJsonEndpoint))
val str = js.asString.map(str => ServiceEndpoint.UriValue.fromString(str).map[UriOrJsonEndpoint](i => i))
obj.orElse(str).getOrElse(Left("the service endpoint is not a JSON array of URIs and/or JSON objects"))
}.map(_.toList).flatMap {
case head :: tail => Right(ServiceEndpoint.Multiple(head, tail))
case Nil => Left("the service endpoint cannot be an empty JSON array")
})
val parsedJson: Option[Either[String, ServiceEndpoint]] =
s.fromJson[Json]
.toOption // it's OK to let parsing fail (e.g. http://example.com without quote is not a JSON string)
.flatMap { json =>
val parsedObject = json.asObject.map(obj => Right(ServiceEndpoint.Single(obj)))
val parsedArray = json.asArray.map(_.traverse[String, UriOrJsonEndpoint] { js =>
val obj = js.asObject.map(obj => Right(obj: UriOrJsonEndpoint))
val str = js.asString.map(str => ServiceEndpoint.UriValue.fromString(str).map[UriOrJsonEndpoint](i => i))
obj.orElse(str).getOrElse(Left("the service endpoint is not a JSON array of URIs and/or JSON objects"))
}.map(_.toList).flatMap {
case head :: tail => Right(ServiceEndpoint.Multiple(head, tail))
case Nil => Left("the service endpoint cannot be an empty JSON array")
})

parsedObject.orElse(parsedArray)
}
parsedObject.orElse(parsedArray)
}

parsedJson match {
// serviceEndpoint is a valid JSON object or array
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.hyperledger.identus.castor.core.model.did

import io.circe.JsonObject
import org.hyperledger.identus.castor.core.util.UriUtils
import zio.json.ast.Json as ZioJson

sealed trait ServiceEndpoint {
def normalize(): ServiceEndpoint
Expand Down Expand Up @@ -35,12 +35,12 @@ object ServiceEndpoint {
override def normalize(): UriOrJsonEndpoint = copy(uri = uri.normalize())
}

final case class Json(json: JsonObject) extends UriOrJsonEndpoint {
final case class Json(json: ZioJson.Obj) extends UriOrJsonEndpoint {
override def normalize(): UriOrJsonEndpoint = this
}

given Conversion[UriValue, UriOrJsonEndpoint] = Uri(_)
given Conversion[JsonObject, UriOrJsonEndpoint] = Json(_)
given Conversion[ZioJson.Obj, UriOrJsonEndpoint] = Json(_)
}

final case class Single(value: UriOrJsonEndpoint) extends ServiceEndpoint {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.hyperledger.identus.castor.core.model.did.w3c

import io.circe.Json
import zio.json.ast.Json

/** A projection of DIDDocument data model to W3C compliant DID representation */
final case class DIDDocumentRepr(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.hyperledger.identus.castor.core.model.did.w3c

import io.circe.Json
import org.hyperledger.identus.castor.core.model.did.*
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.UriOrJsonEndpoint
import org.hyperledger.identus.shared.crypto.Apollo
import org.hyperledger.identus.shared.models.{Base64UrlString, HexString}
import zio.json.ast.Json

import java.time.{Instant, ZoneOffset}
import java.time.format.DateTimeFormatter
Expand Down Expand Up @@ -95,15 +95,15 @@ private[castor] trait W3CModelHelper {
serviceEndpoint match {
case ServiceEndpoint.Single(uri) =>
uri match {
case UriOrJsonEndpoint.Uri(uri) => Json.fromString(uri.value)
case UriOrJsonEndpoint.Json(json) => Json.fromJsonObject(json)
case UriOrJsonEndpoint.Uri(uri) => Json.Str(uri.value)
case UriOrJsonEndpoint.Json(json) => json
}
case ep: ServiceEndpoint.Multiple =>
val uris = ep.values.map {
case UriOrJsonEndpoint.Uri(uri) => Json.fromString(uri.value)
case UriOrJsonEndpoint.Json(json) => Json.fromJsonObject(json)
case UriOrJsonEndpoint.Uri(uri) => Json.Str(uri.value)
case UriOrJsonEndpoint.Json(json) => json
}
Json.arr(uris*)
Json.Arr(uris*)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package org.hyperledger.identus.castor.core.model

import com.google.protobuf.timestamp.Timestamp
import io.circe.{Json, JsonObject}
import io.iohk.atala.prism.protos.common_models.Ledger
import io.iohk.atala.prism.protos.node_models
import org.hyperledger.identus.castor.core.model.did.{ServiceEndpoint, ServiceType}
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{UriOrJsonEndpoint, UriValue}
import org.hyperledger.identus.castor.core.util.GenUtils
import zio.*
import zio.json.ast.Json
import zio.test.*
import zio.test.Assertion.*

Expand All @@ -20,9 +20,9 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {

given Conversion[String, ServiceType.Name] = ServiceType.Name.fromStringUnsafe
given Conversion[String, UriOrJsonEndpoint] = s => UriOrJsonEndpoint.Uri(UriValue.fromString(s).toOption.get)
given Conversion[JsonObject, UriOrJsonEndpoint] = UriOrJsonEndpoint.Json(_)
given Conversion[Json.Obj, UriOrJsonEndpoint] = UriOrJsonEndpoint.Json(_)
given Conversion[String, ServiceEndpoint] = s => ServiceEndpoint.Single(s)
given Conversion[JsonObject, ServiceEndpoint] = json => ServiceEndpoint.Single(UriOrJsonEndpoint.Json(json))
given Conversion[Json.Obj, ServiceEndpoint] = json => ServiceEndpoint.Single(UriOrJsonEndpoint.Json(json))

private def makePublicKey(id: String, revokedOn: Option[node_models.LedgerData] = None): node_models.PublicKey =
node_models.PublicKey(
Expand Down Expand Up @@ -351,7 +351,7 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {
test("parse valid json object") {
val serviceEndpoint = """{"uri": "https://example.com"}"""
val result = ProtoModelHelper.parseServiceEndpoint(serviceEndpoint)
val expected: ServiceEndpoint = Json.obj("uri" -> Json.fromString("https://example.com")).asObject.get
val expected: ServiceEndpoint = Json.Obj("uri" -> Json.Str("https://example.com")).asObject.get
assert(result)(isRight(equalTo(expected)))
},
test("parse invalid endpoint that is not a string or object") {
Expand All @@ -362,7 +362,7 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {
test("parse empty json object") {
val serviceEndpoint = "{}"
val result = ProtoModelHelper.parseServiceEndpoint(serviceEndpoint)
val expected: ServiceEndpoint = Json.obj().asObject.get
val expected: ServiceEndpoint = Json.Obj().asObject.get
assert(result)(isRight(equalTo(expected)))
},
test("parse empty json array") {
Expand All @@ -389,9 +389,9 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {
val serviceEndpoint = """[{"uri": "https://example.com"}, {"uri": "https://example2.com"}]"""
val result = ProtoModelHelper.parseServiceEndpoint(serviceEndpoint)
val expected = ServiceEndpoint.Multiple(
Json.obj("uri" -> Json.fromString("https://example.com")).asObject.get,
Json.Obj("uri" -> Json.Str("https://example.com")).asObject.get,
Seq(
Json.obj("uri" -> Json.fromString("https://example2.com")).asObject.get
Json.Obj("uri" -> Json.Str("https://example2.com")).asObject.get
)
)
assert(result)(isRight(equalTo(expected)))
Expand All @@ -400,7 +400,7 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {
val serviceEndpoint = """[{"uri": "https://example.com"}, "https://example2.com"]"""
val result = ProtoModelHelper.parseServiceEndpoint(serviceEndpoint)
val expected = ServiceEndpoint.Multiple(
Json.obj("uri" -> Json.fromString("https://example.com")).asObject.get,
Json.Obj("uri" -> Json.Str("https://example.com")).asObject.get,
Seq("https://example2.com")
)
assert(result)(isRight(equalTo(expected)))
Expand Down Expand Up @@ -428,7 +428,7 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {
assert(encoded)(equalTo("http://example.com"))
},
test("encode single endoint JSON object") {
val uri: UriOrJsonEndpoint = JsonObject("uri" -> Json.fromString("http://example.com"))
val uri: UriOrJsonEndpoint = Json.Obj("uri" -> Json.Str("http://example.com"))
val serviceEndpoint = ServiceEndpoint.Single(uri)
val encoded = serviceEndpoint.toProto
assert(encoded)(equalTo("""{"uri":"http://example.com"}"""))
Expand All @@ -441,8 +441,8 @@ object ProtoModelHelperSpec extends ZIOSpecDefault {
assert(encoded)(equalTo("""["http://example.com","http://example2.com"]"""))
},
test("encode multiple endoints JSON object") {
val uri: UriOrJsonEndpoint = JsonObject("uri" -> Json.fromString("http://example.com"))
val uri2: UriOrJsonEndpoint = JsonObject("uri" -> Json.fromString("http://example2.com"))
val uri: UriOrJsonEndpoint = Json.Obj("uri" -> Json.Str("http://example.com"))
val uri2: UriOrJsonEndpoint = Json.Obj("uri" -> Json.Str("http://example2.com"))
val serviceEndpoint = ServiceEndpoint.Multiple(uri, Seq(uri2))
val encoded = serviceEndpoint.toProto
assert(encoded)(equalTo("""[{"uri":"http://example.com"},{"uri":"http://example2.com"}]"""))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.hyperledger.identus.castor.core.util

import io.circe.Json
import org.hyperledger.identus.castor.core.model.did.*
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{UriOrJsonEndpoint, UriValue}
import org.hyperledger.identus.shared.crypto.Apollo
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}
import zio.*
import zio.json.ast.Json
import zio.test.Gen

import scala.language.implicitConversions
Expand Down Expand Up @@ -62,7 +62,7 @@ object GenUtils {
)
sampleUri = "https://example.com"
uriEndpointGen = Gen.const(UriOrJsonEndpoint.Uri(UriValue.fromString(sampleUri).toOption.get))
jsonEndpointGen = Gen.const(UriOrJsonEndpoint.Json(Json.obj("uri" -> Json.fromString(sampleUri)).asObject.get))
jsonEndpointGen = Gen.const(UriOrJsonEndpoint.Json(Json.Obj("uri" -> Json.Str(sampleUri)).asObject.get))
endpoints <- Gen.oneOf[Any, ServiceEndpoint](
uriEndpointGen.map(ServiceEndpoint.Single(_)),
jsonEndpointGen.map(ServiceEndpoint.Single(_)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.hyperledger.identus.agent.server.http

import io.circe.*
import io.circe.generic.semiauto.*
import io.circe.syntax.*
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
import org.http4s.*
import org.http4s.blaze.server.BlazeServerBuilder
Expand All @@ -18,6 +15,7 @@ import sttp.tapir.server.metrics.MetricLabels
import sttp.tapir.ztapir.ZServerEndpoint
import zio.*
import zio.interop.catz.*
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, EncoderOps, JsonDecoder, JsonEncoder}

class ZHttp4sBlazeServer(micrometerRegistry: PrometheusMeterRegistry, metricsNamespace: String) {

Expand All @@ -34,9 +32,8 @@ class ZHttp4sBlazeServer(micrometerRegistry: PrometheusMeterRegistry, metricsNam
secChUaPlatform: Option[String],
)
object FingerPrintData {
given encoder: Encoder[FingerPrintData] = deriveEncoder[FingerPrintData]

given decoder: Decoder[FingerPrintData] = deriveDecoder[FingerPrintData]
given encoder: JsonEncoder[FingerPrintData] = DeriveJsonEncoder.gen
given decoder: JsonDecoder[FingerPrintData] = DeriveJsonDecoder.gen
}

val headers = sr.headers
Expand All @@ -52,7 +49,7 @@ class ZHttp4sBlazeServer(micrometerRegistry: PrometheusMeterRegistry, metricsNam
headers.find(_.name.toLowerCase == "sec-ch-ua-platform").map(_.value),
)

val jsonStr = fingerPrintData.asJson.dropNullValues.spaces2
val jsonStr = fingerPrintData.toJson
val canonicalized = Json.canonicalizeToJcs(jsonStr).toOption

canonicalized.map(x => Sha256Hash.compute(x.getBytes))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.hyperledger.identus.agent.server.jobs

import cats.syntax.all.*
import io.circe.parser.*
import io.circe.syntax.*
import org.hyperledger.identus.agent.server.config.AppConfig
import org.hyperledger.identus.agent.server.jobs.BackgroundJobError.{
ErrorResponseReceivedFromPeerAgent,
Expand Down Expand Up @@ -37,6 +35,7 @@ import org.hyperledger.identus.shared.models.{Failure, *}
import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect
import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds
import zio.*
import zio.json.{DecoderOps, EncoderOps}
import zio.metrics.*
import zio.prelude.{Validation, ZValidation}
import zio.prelude.ZValidation.{Failure as ZFailure, *}
Expand Down Expand Up @@ -1105,13 +1104,12 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
val maybePresentationOptions: Either[PresentationError, Option[Options]] =
requestPresentation.attachments.headOption
.map(attachment =>
decode[JsonData](
attachment.data.asJson.noSpaces
)
attachment.data.toJson
.fromJson[JsonData]
.leftMap(err => PresentationDecodingError(s"JsonData decoding error: $err"))
.flatMap(data =>
org.hyperledger.identus.pollux.core.model.presentation.PresentationAttachment.given_Decoder_PresentationAttachment
.decodeJson(data.json.asJson)
org.hyperledger.identus.pollux.core.model.presentation.PresentationAttachment.given_JsonDecoder_PresentationAttachment
.decodeJson(data.json.toJson)
.map(_.options)
.leftMap(err => PresentationDecodingError(s"PresentationAttachment decoding error: $err"))
)
Expand Down
Loading

0 comments on commit d0c3506

Please sign in to comment.