Skip to content

Commit 74107ca

Browse files
committed
fix(schemaregistry): add support for format to validation
1 parent 8071cac commit 74107ca

4 files changed

Lines changed: 141 additions & 17 deletions

File tree

package-lock.json

Lines changed: 41 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schemaregistry/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@types/simple-oauth2": "^5.0.7",
4545
"@types/validator": "^13.12.0",
4646
"ajv": "^8.17.1",
47+
"ajv-formats": "^3.0.1",
4748
"async-mutex": "^0.5.0",
4849
"avsc": "^5.7.7",
4950
"axios": "^1.12.0",

schemaregistry/serde/json.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import Ajv, {ErrorObject} from "ajv";
1414
import Ajv2019 from "ajv/dist/2019";
1515
import Ajv2020 from "ajv/dist/2020";
16+
import addFormats from "ajv-formats";
1617
import * as draft6MetaSchema from 'ajv/dist/refs/json-schema-draft-06.json'
1718
import * as draft7MetaSchema from 'ajv/dist/refs/json-schema-draft-07.json'
1819
import {
@@ -272,13 +273,15 @@ async function toValidateFunction(
272273
if (spec === 'http://json-schema.org/draft/2020-12/schema'
273274
|| spec === 'https://json-schema.org/draft/2020-12/schema') {
274275
const ajv2020 = new Ajv2020({ ...conf as JsonSerdeConfig, allErrors: true })
276+
addFormats(ajv2020)
275277
ajv2020.addKeyword("confluent:tags")
276278
deps.forEach((schema, name) => {
277279
ajv2020.addSchema(JSON.parse(schema), name)
278280
})
279281
fn = ajv2020.compile(json)
280282
} else {
281283
const ajv = new Ajv2019({ ...conf as JsonSerdeConfig, allErrors: true })
284+
addFormats(ajv)
282285
ajv.addKeyword("confluent:tags")
283286
ajv.addMetaSchema(draft6MetaSchema)
284287
ajv.addMetaSchema(draft7MetaSchema)
@@ -504,6 +507,3 @@ function disjoint(tags1: Set<string>, tags2: Set<string>): boolean {
504507
}
505508
return true
506509
}
507-
508-
509-

schemaregistry/test/serde/json.spec.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,34 @@ const messageSchema = `
275275
}
276276
}
277277
`
278+
const schemaWithFormats = `
279+
{
280+
"type": "object",
281+
"properties": {
282+
"email": {
283+
"type": "string",
284+
"format": "email"
285+
},
286+
"website": {
287+
"type": "string",
288+
"format": "uri"
289+
},
290+
"createdAt": {
291+
"type": "string",
292+
"format": "date-time"
293+
},
294+
"ipv4Address": {
295+
"type": "string",
296+
"format": "ipv4"
297+
},
298+
"uuid": {
299+
"type": "string",
300+
"format": "uuid"
301+
}
302+
},
303+
"required": ["email", "createdAt"]
304+
}
305+
`
278306

279307
describe('JsonSerializer', () => {
280308
afterEach(async () => {
@@ -516,6 +544,74 @@ describe('JsonSerializer', () => {
516544

517545
await expect(() => ser.serialize(topic, diffObj)).rejects.toThrow(SerializationError)
518546
})
547+
it('format validation', async () => {
548+
let conf: ClientConfig = {
549+
baseURLs: [baseURL],
550+
cacheCapacity: 1000
551+
}
552+
let client = SchemaRegistryClient.newClient(conf)
553+
let ser = new JsonSerializer(client, SerdeType.VALUE, {
554+
useLatestVersion: true,
555+
validate: true
556+
})
557+
558+
let info: SchemaInfo = {
559+
schemaType: 'JSON',
560+
schema: schemaWithFormats
561+
}
562+
563+
await client.register(subject, info, false)
564+
565+
let validObjectWithCorrectFormats = {
566+
email: 'user@example.com',
567+
website: 'https://example.com',
568+
createdAt: '2024-01-15T10:30:00Z',
569+
ipv4Address: '192.168.1.1',
570+
uuid: '550e8400-e29b-41d4-a716-446655440000'
571+
}
572+
let bytes = await ser.serialize(topic, validObjectWithCorrectFormats)
573+
574+
let deser = new JsonDeserializer(client, SerdeType.VALUE, {})
575+
let obj2 = await deser.deserialize(topic, bytes)
576+
expect(obj2).toEqual(validObjectWithCorrectFormats)
577+
578+
let invalidEmailObj = {
579+
email: 'not-an-email',
580+
website: 'https://example.com',
581+
createdAt: '2024-01-15T10:30:00Z'
582+
}
583+
await expect(() => ser.serialize(topic, invalidEmailObj)).rejects.toThrow(SerializationError)
584+
585+
let invalidUriObj = {
586+
email: 'user@example.com',
587+
website: 'not a uri',
588+
createdAt: '2024-01-15T10:30:00Z'
589+
}
590+
await expect(() => ser.serialize(topic, invalidUriObj)).rejects.toThrow(SerializationError)
591+
592+
let invalidDateObj = {
593+
email: 'user@example.com',
594+
website: 'https://example.com',
595+
createdAt: 'not-a-date'
596+
}
597+
await expect(() => ser.serialize(topic, invalidDateObj)).rejects.toThrow(SerializationError)
598+
599+
let invalidIpv4Obj = {
600+
email: 'user@example.com',
601+
website: 'https://example.com',
602+
createdAt: '2024-01-15T10:30:00Z',
603+
ipv4Address: '999.999.999.999'
604+
}
605+
await expect(() => ser.serialize(topic, invalidIpv4Obj)).rejects.toThrow(SerializationError)
606+
607+
let invalidUuidObj = {
608+
email: 'user@example.com',
609+
website: 'https://example.com',
610+
createdAt: '2024-01-15T10:30:00Z',
611+
uuid: 'not-a-uuid'
612+
}
613+
await expect(() => ser.serialize(topic, invalidUuidObj)).rejects.toThrow(SerializationError)
614+
})
519615
it('cel field transform', async () => {
520616
let conf: ClientConfig = {
521617
baseURLs: [baseURL],

0 commit comments

Comments
 (0)