Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pollux): add Json VC schema meta validation #892

Merged
merged 9 commits into from
Feb 14, 2024
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ lazy val V = new {

val bouncyCastle = "1.70"

val jsonSchemaValidator = "1.0.86"
val jsonSchemaValidator = "1.3.2"

val vaultDriver = "6.2.0"
val micrometer = "1.11.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package io.iohk.atala.pollux.core.model.schema.`type`

import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes
import zio.*
import zio.json.*
import zio.json.ast.Json

case class CredentialJsonSchemaSerDesV1(
$schema: String,
$id: Option[String],
description: Option[String],
properties: Json,
required: Set[String],
additionalProperties: Option[Boolean]
)

object CredentialJsonSchemaSerDesV1 {
val version: String = "CredentialSchemaSchemaV1"

private val schema: String = """
|{
| "$schema": "https://json-schema.org/draft/2020-12/schema",
| "$id": "https://example.com/custom-meta-schema",
| "type": "object",
| "properties": {
| "$id": {
| "type": "string",
| "format": "uri-reference"
| },
| "$schema": {
| "type": "string",
| "format": "uri",
| "enum": ["https://json-schema.org/draft/2020-12/schema"]
| },
| "description": {
| "type": "string",
| "minLength": 1,
| "maxLength": 2000
| },
| "type": {
| "enum": ["object"]
| },
| "properties": {
| "type": "object",
| "patternProperties": {
| ".*": {
| "$ref": "#/$defs/oneOf"
| }
| }
| },
| "required": {
| "type": "array",
| "items": {
| "type": "string",
| "minLength": 1
| },
| "minItems": 0,
| "maxItems": 125,
| "uniqueItems": true
| },
| "additionalProperties": {
| "type": "boolean"
| }
| },
| "required": ["$schema","type","properties"],
| "additionalProperties": false,
| "$defs": {
| "oneOf": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need two oneOf terms?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well pointed out @yshyn-iohk. The top-level one is actually just the name/id for the inner oneOf def declaration. I will indeed give it another name (i.e. oneOfDef) to avoid any confusion 👍

| "oneOf": [
| {
| "type": "object",
| "properties": {
| "type": {
| "type": "string",
| "enum": ["string"]
| },
| "minLength": {
| "type": "integer",
| "minimum": 0
| },
| "maxLength": {
| "type": "integer",
| "minimum": 0
| },
| "format": {
| "type": "string",
| "enum": ["date-time","date","time","duration","ipv4","ipv6","email","uri","uuid"]
| },
| "pattern": {
| "type": "string",
| "format": "regex"
| },
| "enum": {
| "type": "array",
| "items": {
| "type": "string",
| "minItems": 1
| }
| }
| },
| "required": ["type"],
| "additionalProperties": false
| },
| {
| "type": "object",
| "properties": {
| "type": {
| "type": "string",
| "enum": ["integer"]
| },
| "minimum": {
| "type": "integer"
| },
| "exclusiveMinimum": {
| "type": "integer"
| },
| "maximum": {
| "type": "integer"
| },
| "exclusiveMaximum": {
| "type": "integer"
| },
| "enum": {
| "type": "array",
| "items": {
| "type": "integer",
| "minItems": 1
| }
| }
| },
| "required": ["type"],
| "additionalProperties": false
| },
| {
| "type": "object",
| "properties": {
| "type": {
| "type": "string",
| "enum": ["number"]
| },
| "minimum": {
| "type": "number"
| },
| "exclusiveMinimum": {
| "type": "number"
| },
| "maximum": {
| "type": "number"
| },
| "exclusiveMaximum": {
| "type": "number"
| },
| "enum": {
| "type": "array",
| "items": {
| "type": "number",
| "minItems": 1
| }
| }
| },
| "required": ["type"],
| "additionalProperties": false
| },
| {
| "type": "object",
| "properties": {
| "type": {
| "type": "string",
| "enum": ["boolean"]
| }
| },
| "required": ["type"],
| "additionalProperties": false
| },
| {
| "type": "object",
| "properties": {
| "type": {
| "type": "string",
| "enum": ["array"]
| },
| "items": {
| "$ref": "#/$defs/oneOf"
| },
| "minItems": {
| "type": "integer"
| },
| "maxItems": {
| "type": "integer"
| },
| "uniqueItems": {
| "type": "boolean"
| }
| },
| "required": ["type","items"],
| "additionalProperties": false
| },
| {
| "type": "object",
| "properties": {
| "type": {
| "type": "string",
| "enum": ["object"]
| },
| "properties": {
| "type": "object",
| "patternProperties": {
| ".*": {
| "$ref": "#/$defs/oneOf"
| }
| }
| },
| "minProperties": {
| "type": "integer"
| },
| "maxProperties": {
| "type": "integer"
| }
| },
| "required": ["type","properties"],
| "additionalProperties": false
| }
| ]
| }
| }
|}
""".stripMargin

val schemaSerDes: SchemaSerDes[CredentialJsonSchemaSerDesV1] = SchemaSerDes(schema)

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

given JsonDecoder[CredentialJsonSchemaSerDesV1] = DeriveJsonDecoder.gen[CredentialJsonSchemaSerDesV1]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ object CredentialJsonSchemaType extends CredentialSchemaType {

override def validate(schema: Schema): IO[JsonSchemaError, Unit] =
for {
credentialJsonSchema <- CredentialJsonSchemaSerDesV1.schemaSerDes.deserialize(schema.toJson)
_ <- JsonSchemaValidatorImpl.from(schema)
} yield ()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object JsonSchemaUtils {
else ZIO.unit
mapper <- ZIO.attempt(new ObjectMapper()).mapError(t => UnexpectedError(t.getMessage))
factory <- ZIO
.attempt(JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(specVersion)).objectMapper(mapper).build)
.attempt(JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(specVersion)).jsonMapper(mapper).build)
.mapError(t => UnexpectedError(t.getMessage))
jsonSchema <- ZIO.attempt(factory.getSchema(jsonSchemaNode)).mapError(t => UnexpectedError(t.getMessage))
} yield jsonSchema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ object AnoncredSchemaTypeSpec extends ZIOSpecDefault {
val schema: Json = jsonSchema.fromJson[Json].getOrElse(Json.Null)
assertZIO(AnoncredSchemaType.validate(schema).exit)(
failsWithErrors(
Seq("$.attrNames: there must be a minimum of 1 items in the array")
Seq("$.attrNames: expected at least 1 items but found 0")
)
)
},
Expand All @@ -161,7 +161,7 @@ object AnoncredSchemaTypeSpec extends ZIOSpecDefault {
val schema: Json = jsonSchema.fromJson[Json].getOrElse(Json.Null)
assertZIO(AnoncredSchemaType.validate(schema).exit)(
failsWithErrors(
Seq("$.attrNames: there must be a maximum of 125 items in the array")
Seq("$.attrNames: must have a maximum of 125 items in the array")
)
)
}
Expand Down
Loading
Loading