Skip to content

Commit

Permalink
feat: ATL-5575 Generalize and Streamline Json Schema SerDes logic (#653)
Browse files Browse the repository at this point in the history
Signed-off-by: Bassam Riman <[email protected]>
  • Loading branch information
CryptoKnightIOG authored Aug 25, 2023
1 parent 70b2f16 commit eb4f8f4
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package io.iohk.atala.pollux.core.model.error

import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError

sealed trait CredentialSchemaError {
def userMessage: String
}

object CredentialSchemaError {
case class SchemaError(schemaError: JsonSchemaError) extends CredentialSchemaError {
def userMessage: String = schemaError.error
}
case class URISyntaxError(userMessage: String) extends CredentialSchemaError
case class CredentialSchemaParsingError(userMessage: String) extends CredentialSchemaError
case class UnsupportedCredentialSchemaType(userMessage: String) extends CredentialSchemaError
case class JsonSchemaParsingError(userMessage: String) extends CredentialSchemaError
case class UnsupportedJsonSchemaSpecVersion(userMessage: String) extends CredentialSchemaError
case class ClaimsParsingError(userMessage: String) extends CredentialSchemaError
case class ClaimsValidationError(errors: Seq[String]) extends CredentialSchemaError {
def userMessage: String = errors.mkString(";")
}
case class UnexpectedError(userMessage: String) extends CredentialSchemaError
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ package io.iohk.atala.pollux.core.model.schema

import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.*
import io.iohk.atala.pollux.core.model.schema.`type`.{
AnoncredSchemaType,
CredentialJsonSchemaType,
CredentialSchemaType
}
import io.iohk.atala.pollux.core.model.schema.validator.CredentialJsonSchemaValidator
import io.iohk.atala.pollux.core.model.schema.`type`.AnoncredSchemaType
import io.iohk.atala.pollux.core.model.schema.`type`.CredentialJsonSchemaType
import io.iohk.atala.pollux.core.model.schema.`type`.CredentialSchemaType
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaValidatorImpl
import io.iohk.atala.pollux.core.service.URIDereferencer
import zio.*
import zio.json.*
import zio.prelude.Validation

import java.net.URI
import java.time.{OffsetDateTime, ZoneOffset}
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.UUID

type Schema = zio.json.ast.Json
Expand Down Expand Up @@ -133,8 +132,8 @@ object CredentialSchema {
)
)(resolvedSchemaType.`type`)(`type` => `type` == CredentialJsonSchemaType.`type`)
.toZIO
schemaValidator <- CredentialJsonSchemaValidator.from(vcSchema.schema)
_ <- schemaValidator.validate(claims)
schemaValidator <- JsonSchemaValidatorImpl.from(vcSchema.schema).mapError(SchemaError.apply)
_ <- schemaValidator.validate(claims).mapError(SchemaError.apply)
} yield ()
}

Expand All @@ -152,7 +151,7 @@ object CredentialSchema {
def validateCredentialSchema(vcSchema: CredentialSchema): IO[CredentialSchemaError, Unit] = {
for {
resolvedSchemaType <- resolveCredentialSchemaType(vcSchema.`type`)
_ <- resolvedSchemaType.validate(vcSchema.schema)
_ <- resolvedSchemaType.validate(vcSchema.schema).mapError(SchemaError.apply)
} yield ()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package io.iohk.atala.pollux.core.model.schema.`type`

import com.networknt.schema.*
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.schema.Schema
import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.{AnoncredSchemaSchemaV1, AnoncredSchemaSchemaVersion}
import io.iohk.atala.pollux.core.model.schema.common.JsonSchemaUtils
import io.iohk.atala.pollux.core.model.schema.validator.CredentialJsonSchemaValidator
import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1
import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1.*
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaUtils
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaValidatorImpl
import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes
import zio.*
import zio.json.*

object AnoncredSchemaType extends CredentialSchemaType {

private val anoncredSchemaSchemaVersion: AnoncredSchemaSchemaVersion = AnoncredSchemaSchemaV1
val `type`: String = AnoncredSchemaSchemaV1.version
val anondcredShemaBasedSerDes: SchemaSerDes[AnoncredSchemaSerDesV1] = AnoncredSchemaSerDesV1.schemaSerDes
val `type`: String = AnoncredSchemaSerDesV1.version

override def validate(schema: Schema): IO[CredentialSchemaError, Unit] = {
override def validate(schema: Schema): IO[JsonSchemaError, Unit] = {
for {
jsonSchemaSchema <- anoncredSchemaSchemaVersion.initialiseJsonSchema
schemaValidator = CredentialJsonSchemaValidator(jsonSchemaSchema)
jsonSchemaSchema <- anondcredShemaBasedSerDes.initialiseJsonSchema
schemaValidator = JsonSchemaValidatorImpl(jsonSchemaSchema)
jsonSchemaNode <- JsonSchemaUtils.toJsonNode(schema)
_ <- schemaValidator.validate(jsonSchemaNode)
} yield ()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.iohk.atala.pollux.core.model.schema.`type`

import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.schema.Schema
import io.iohk.atala.pollux.core.model.schema.validator.CredentialJsonSchemaValidator
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaValidatorImpl
import zio.*
import zio.json.*

Expand All @@ -11,8 +11,8 @@ object CredentialJsonSchemaType extends CredentialSchemaType {

override val `type`: String = VC_JSON_SCHEMA_URI

override def validate(schema: Schema): IO[CredentialSchemaError, Unit] =
override def validate(schema: Schema): IO[JsonSchemaError, Unit] =
for {
_ <- CredentialJsonSchemaValidator.from(schema)
_ <- JsonSchemaValidatorImpl.from(schema)
} yield ()
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.iohk.atala.pollux.core.model.schema.`type`

import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.schema.Schema
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError
import zio.IO

trait CredentialSchemaType {
val `type`: String
def validate(schema: Schema): IO[CredentialSchemaError, Unit]

def validate(schema: Schema): IO[JsonSchemaError, Unit]
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package io.iohk.atala.pollux.core.model.schema.`type`.anoncred

import com.networknt.schema.*
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.schema.common.JsonSchemaUtils
import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes
import zio.*
import zio.json.*

object AnoncredSchemaSchemaV1 extends AnoncredSchemaSchemaVersion {
val version: String = AnoncredSchemaSchemaV1.getClass.getSimpleName
private val jsonSchemaSchemaStr: String =
case class AnoncredSchemaSerDesV1(
name: String,
version: String,
attrNames: Set[String],
issuerId: String
)

object AnoncredSchemaSerDesV1 {
val version: String = "AnoncredSchemaV1"
private val schema: String =
"""
|{
| "$schema": "http://json-schema.org/draft-07/schema#",
Expand Down Expand Up @@ -40,6 +46,9 @@ object AnoncredSchemaSchemaV1 extends AnoncredSchemaSchemaVersion {
|}
|""".stripMargin

override def initialiseJsonSchema: IO[CredentialSchemaError, JsonSchema] =
JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr)
val schemaSerDes: SchemaSerDes[AnoncredSchemaSerDesV1] = SchemaSerDes(schema)

given JsonEncoder[AnoncredSchemaSerDesV1] = DeriveJsonEncoder.gen[AnoncredSchemaSerDesV1]

given JsonDecoder[AnoncredSchemaSerDesV1] = DeriveJsonDecoder.gen[AnoncredSchemaSerDesV1]
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.iohk.atala.pollux.core.model.schema.validator

sealed trait JsonSchemaError {
def error: String
}

object JsonSchemaError {
case class JsonSchemaParsingError(error: String) extends JsonSchemaError

case class JsonValidationErrors(errors: Seq[String]) extends JsonSchemaError {
def error: String = errors.mkString(";")
}

case class UnsupportedJsonSchemaSpecVersion(error: String) extends JsonSchemaError

case class UnexpectedError(error: String) extends JsonSchemaError
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package io.iohk.atala.pollux.core.model.schema.common
package io.iohk.atala.pollux.core.model.schema.validator

import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.networknt.schema.*
import com.networknt.schema.SpecVersion.VersionFlag
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.*
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError.*
import zio.*
import zio.json.ast.Json

object JsonSchemaUtils {
def jsonSchema(
schema: String,
supportedVersions: IndexedSeq[VersionFlag] = IndexedSeq.empty
): IO[CredentialSchemaError, JsonSchema] = {
): IO[JsonSchemaError, JsonSchema] = {
for {
jsonSchemaNode <- toJsonNode(schema)
specVersion <- ZIO
Expand All @@ -37,15 +37,15 @@ object JsonSchemaUtils {
def from(
schema: Json,
supportedVersions: IndexedSeq[VersionFlag] = IndexedSeq.empty
): IO[CredentialSchemaError, JsonSchema] = {
): IO[JsonSchemaError, JsonSchema] = {
jsonSchema(schema.toString(), supportedVersions)
}

def toJsonNode(json: Json): IO[CredentialSchemaError, JsonNode] = {
def toJsonNode(json: Json): IO[JsonSchemaError, JsonNode] = {
toJsonNode(json.toString())
}

def toJsonNode(json: String): IO[CredentialSchemaError, JsonNode] = {
def toJsonNode(json: String): IO[JsonSchemaError, JsonNode] = {
for {
mapper <- ZIO.attempt(new ObjectMapper()).mapError(t => UnexpectedError(t.getMessage))
jsonSchemaNode <- ZIO
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.iohk.atala.pollux.core.model.schema.validator

import com.fasterxml.jackson.databind.JsonNode
import zio.*

trait JsonSchemaValidator {
def validate(claims: String): IO[JsonSchemaError, Unit]

def validate(claimsJsonNode: JsonNode): IO[JsonSchemaError, Unit] = {
validate(claimsJsonNode.toString)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,32 @@ import com.networknt.schema.*
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.*
import io.iohk.atala.pollux.core.model.schema.Schema
import io.iohk.atala.pollux.core.model.schema.common.JsonSchemaUtils
import zio.*

case class CredentialJsonSchemaValidator(schemaValidator: JsonSchema) extends CredentialSchemaValidator {
override def validate(claims: String): IO[CredentialSchemaError, Unit] = {
case class JsonSchemaValidatorImpl(schemaValidator: JsonSchema) extends JsonSchemaValidator {
override def validate(jsonString: String): IO[JsonSchemaError, Unit] = {
import scala.jdk.CollectionConverters.*
for {
// Convert claims to JsonNode
jsonClaims <- JsonSchemaUtils.toJsonNode(claims)
jsonClaims <- JsonSchemaUtils.toJsonNode(jsonString)

// Validate claims JsonNode
validationMessages <- ZIO
.attempt(schemaValidator.validate(jsonClaims).asScala.toSeq)
.mapError(t => ClaimsValidationError(Seq(t.getMessage)))
.mapError(t => JsonSchemaError.JsonValidationErrors(Seq(t.getMessage)))

validationResult <-
if (validationMessages.isEmpty) ZIO.unit
else ZIO.fail(ClaimsValidationError(validationMessages.map(_.getMessage)))
else ZIO.fail(JsonSchemaError.JsonValidationErrors(validationMessages.map(_.getMessage)))
} yield validationResult
}

}

object CredentialJsonSchemaValidator {
def from(schema: Schema): IO[CredentialSchemaError, CredentialJsonSchemaValidator] = {
object JsonSchemaValidatorImpl {
def from(schema: Schema): IO[JsonSchemaError, JsonSchemaValidatorImpl] = {
for {
jsonSchema <- JsonSchemaUtils.from(schema, IndexedSeq(SpecVersion.VersionFlag.V202012))
} yield CredentialJsonSchemaValidator(jsonSchema)
} yield JsonSchemaValidatorImpl(jsonSchema)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.iohk.atala.pollux.core.model.schema.validator

import com.networknt.schema.JsonSchema
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError.*
import zio.IO
import zio.ZIO
import zio.json.*
import zio.json.JsonDecoder
import zio.json.ast.Json
import zio.json.ast.Json.*

class SchemaSerDes[S](jsonSchemaSchemaStr: String) {

def initialiseJsonSchema: IO[JsonSchemaError, JsonSchema] =
JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr)

def deserialize(
schema: zio.json.ast.Json
)(using decoder: JsonDecoder[S]): IO[JsonSchemaError, S] = {
deserialize(schema.toString())
}

def deserialize(
jsonString: String
)(using decoder: JsonDecoder[S]): IO[JsonSchemaError, S] = {
for {
_ <- validate(jsonString)
anoncredSchema <-
ZIO
.fromEither(decoder.decodeJson(jsonString))
.mapError(JsonSchemaError.JsonSchemaParsingError.apply)
} yield anoncredSchema
}

def deserializeAsJson(jsonString: String): IO[JsonSchemaError, Json] = {
for {
_ <- validate(jsonString)
json <-
ZIO
.fromEither(jsonString.fromJson[Json])
.mapError(JsonSchemaError.JsonSchemaParsingError.apply)
} yield json
}

def validate(jsonString: String): IO[JsonSchemaError, Unit] = {
for {
jsonSchemaSchema <- JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr)
schemaValidator = JsonSchemaValidatorImpl(jsonSchemaSchema)
_ <- schemaValidator.validate(jsonString)
} yield {}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.iohk.atala.pollux.core.model.schema

import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.schema.`type`.AnoncredSchemaType
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError
import zio.*
import zio.json.*
import zio.json.ast.Json
Expand Down Expand Up @@ -154,7 +154,7 @@ object AnoncredSchemaTypeSpec extends ZIOSpecDefault {

def failsWithErrors(errorMessages: Iterable[String]) = {
fails(
isSubtype[CredentialSchemaError.ClaimsValidationError](
isSubtype[JsonSchemaError.JsonValidationErrors](
hasField("errors", _.errors, hasSameElementsDistinct(errorMessages))
)
)
Expand Down
Loading

0 comments on commit eb4f8f4

Please sign in to comment.