confluent-schema-registry 3.3.5 → 3.6.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +42 -1
- package/bin/avdlToAVSC.sh +1 -1
- package/dist/JsonSchema.js +1 -1
- package/dist/JsonSchema.js.map +1 -1
- package/dockest-error.json +3 -7
- package/jest.setup.ts +23 -13
- package/package.json +11 -17
- package/src/@types.ts +6 -13
- package/src/AvroHelper.ts +2 -3
- package/src/JsonSchema.ts +27 -15
- package/src/SchemaRegistry.json.spec.ts +18 -1
- package/src/SchemaRegistry.newApi.spec.ts +1 -1
- package/src/SchemaRegistry.spec.ts +15 -0
- package/src/SchemaRegistry.ts +16 -17
- package/src/api/index.spec.ts +24 -2
- package/src/api/index.ts +11 -2
- package/src/api/middleware/errorMiddleware.ts +1 -1
- package/src/constants.ts +1 -1
- package/src/index.ts +2 -1
- package/src/schemaTypeResolver.ts +2 -1
- package/src/utils/avdlToAVSC.spec.ts +28 -5
- package/src/utils/avdlToAVSC.ts +3 -3
- package/tmp/Array.avsc +22 -0
- package/tmp/Array1.avsc +28 -0
- package/tmp/Array2.avsc +17 -0
- package/tmp/Array3.avsc +17 -0
- package/tmp/ArrayUnion.avsc +23 -0
- package/tmp/Bam.avsc +9 -0
- package/tmp/Bar.avsc +46 -0
- package/tmp/Baz.avsc +39 -0
- package/tmp/Complex.avsc +81 -0
- package/tmp/Enum.avsc +13 -0
- package/tmp/EnumUnion.avsc +14 -0
- package/tmp/Foos.avsc +6 -0
- package/tmp/ImportMultipleNamespaces.avsc +35 -0
- package/tmp/KeyValue.avsc +12 -0
- package/tmp/Metadata.avsc +27 -0
- package/tmp/Multiple.avsc +26 -0
- package/tmp/MultipleNamespaces.avsc +44 -0
- package/tmp/MultipleUnion.avsc +28 -0
- package/tmp/Simple.avsc +9 -0
- package/tmp/Two.avsc +19 -0
- package/tmp/Union.avsc +20 -0
- package/dockest.ts +0 -30
package/CHANGELOG.md
CHANGED
@@ -9,7 +9,48 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
9
9
|
|
10
10
|
### Added
|
11
11
|
|
12
|
-
-
|
12
|
+
- Support [schema references](https://docs.confluent.io/platform/current/schema-registry/serdes-develop/index.html#schema-references) for Avro, Protocol Buffer, and JSON schema [#197](https://github.com/kafkajs/confluent-schema-registry/pull/197)
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
|
16
|
+
- Fix Apicurio compatibility with register function [#201](https://github.com/kafkajs/confluent-schema-registry/pull/201)
|
17
|
+
|
18
|
+
## [3.2.1] - 2022-01-28
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
- Don't swallow error message from client-side errors from registry requests [#176](https://github.com/kafkajs/confluent-schema-registry/pull/176)
|
23
|
+
|
24
|
+
## [3.2.0] - 2021-11-22
|
25
|
+
|
26
|
+
### Added
|
27
|
+
|
28
|
+
- Add reader schema option when decoding Avro messages [#166](https://github.com/kafkajs/confluent-schema-registry/pull/166)
|
29
|
+
|
30
|
+
## [3.1.1] - 2021-11-03
|
31
|
+
|
32
|
+
### Fixed
|
33
|
+
|
34
|
+
- Support backwards incompatible changes in Ajv 8 when passing in Ajv instance in JSON Schema options [#163](https://github.com/kafkajs/confluent-schema-registry/pull/163)
|
35
|
+
|
36
|
+
## [3.1.0] - 2021-11-03
|
37
|
+
|
38
|
+
### Added
|
39
|
+
|
40
|
+
- Allow passing in Ajv instance in JSON Schema options [#133](https://github.com/kafkajs/confluent-schema-registry/pull/133)
|
41
|
+
|
42
|
+
### Fixed
|
43
|
+
|
44
|
+
- Fix backwards compatibility with older Schema Registry versions [#158](https://github.com/kafkajs/confluent-schema-registry/pull/158)
|
45
|
+
|
46
|
+
### Fixed
|
47
|
+
|
48
|
+
- Fix gateway config for when setting HTTP agent [#127](https://github.com/kafkajs/confluent-schema-registry/pull/127)
|
49
|
+
|
50
|
+
## [3.0.1] - 2021-06-11
|
51
|
+
### Fixed
|
52
|
+
|
53
|
+
- Fix gateway config for when setting HTTP agent [#127](https://github.com/kafkajs/confluent-schema-registry/pull/127)
|
13
54
|
|
14
55
|
## [3.0.0] - 2021-05-20
|
15
56
|
|
package/bin/avdlToAVSC.sh
CHANGED
@@ -6,4 +6,4 @@ if [ -z "${avdl_path}" ]; then
|
|
6
6
|
exit;
|
7
7
|
fi
|
8
8
|
|
9
|
-
docker run --rm -v $
|
9
|
+
docker run --rm -v "$(pwd)":/avro kpnnl/avro-tools:1.12.0 idl2schemata ${avdl_path} tmp && cat tmp/${avsc_name}.avsc
|
package/dist/JsonSchema.js
CHANGED
@@ -42,7 +42,7 @@ class JsonSchema {
|
|
42
42
|
if (opts === null || opts === void 0 ? void 0 : opts.errorHook) {
|
43
43
|
for (const err of this.validate.errors) {
|
44
44
|
const path = this.isOldAjvValidationError(err) ? err.dataPath : err.instancePath;
|
45
|
-
opts
|
45
|
+
opts.errorHook([err], err.data, err.schema);
|
46
46
|
}
|
47
47
|
}
|
48
48
|
return false;
|
package/dist/JsonSchema.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"JsonSchema.js","sourceRoot":"","sources":["../src/JsonSchema.ts"],"names":[],"mappings":";;;;;AACA,8CAAqB;AACrB,qCAAiE;AAoBjE,MAAqB,UAAU;IAG7B,YAAY,MAA2B,EAAE,IAAkB;QACzD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAClD,CAAC;IAEO,aAAa,CAAC,MAA2B,EAAE,IAAkB;;QACnE,MAAM,GAAG,SAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,WAAW,mCAAI,IAAI,aAAG,CAAC,IAAI,CAAC,CAAA;QAC9C,MAAM,iBAAiB,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,iBAAiB,CAAA;QACjD,IAAI,iBAAiB,EAAE;YACrB,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;gBACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;gBAC5C,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;YACxC,CAAC,CAAC,CAAA;SACH;QACD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QACvD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,eAAe,CAAC,OAAY;QAClC,MAAM,KAAK,GAAe,EAAE,CAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YACnE,MAAM,IAAI,+CAAsC,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;SAC3E;IACH,CAAC;IAEM,QAAQ,CAAC,OAAe;QAC7B,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;QAC7B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IAC7C,CAAC;IAEM,UAAU,CAAC,MAAc;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7C,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;QAC7B,OAAO,OAAO,CAAA;IAChB,CAAC;IACM,OAAO,CAAC,OAAe,EAAE,IAA2E;QACzG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;YAC3B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE;gBAC9D,KAAK,MAAM,GAAG,IAAK,IAAI,CAAC,QAAQ,CAAC,MAAc,EAAE;oBAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;oBACjF,IAAI,
|
1
|
+
{"version":3,"file":"JsonSchema.js","sourceRoot":"","sources":["../src/JsonSchema.ts"],"names":[],"mappings":";;;;;AACA,8CAAqB;AACrB,qCAAiE;AAoBjE,MAAqB,UAAU;IAG7B,YAAY,MAA2B,EAAE,IAAkB;QACzD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAClD,CAAC;IAEO,aAAa,CAAC,MAA2B,EAAE,IAAkB;;QACnE,MAAM,GAAG,SAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,WAAW,mCAAI,IAAI,aAAG,CAAC,IAAI,CAAC,CAAA;QAC9C,MAAM,iBAAiB,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,iBAAiB,CAAA;QACjD,IAAI,iBAAiB,EAAE;YACrB,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;gBACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;gBAC5C,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;YACxC,CAAC,CAAC,CAAA;SACH;QACD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QACvD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,eAAe,CAAC,OAAY;QAClC,MAAM,KAAK,GAAe,EAAE,CAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YACnE,MAAM,IAAI,+CAAsC,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;SAC3E;IACH,CAAC;IAEM,QAAQ,CAAC,OAAe;QAC7B,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;QAC7B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IAC7C,CAAC;IAEM,UAAU,CAAC,MAAc;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7C,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;QAC7B,OAAO,OAAO,CAAA;IAChB,CAAC;IACM,OAAO,CAAC,OAAe,EAAE,IAA2E;QACzG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;YAC3B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE;gBAC9D,KAAK,MAAM,GAAG,IAAK,IAAI,CAAC,QAAQ,CAAC,MAAc,EAAE;oBAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;oBACjF,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;iBAC7C;aACF;YACD,OAAO,KAAK,CAAC;SACd;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,uBAAuB,CAAC,KAAyB;QACvD,OAAQ,KAA+B,CAAC,QAAQ,IAAI,IAAI,CAAA;IAC1D,CAAC;CACF;AArDD,6BAqDC"}
|
package/dockest-error.json
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"errorPayload": {
|
3
|
-
"trap": "
|
4
|
-
"
|
5
|
-
"payload": {},
|
6
|
-
"name": "DockestError"
|
7
|
-
},
|
8
|
-
"promise": {}
|
3
|
+
"trap": "SIGINT",
|
4
|
+
"signal": "SIGINT"
|
9
5
|
},
|
10
|
-
"timestamp": "2024-
|
6
|
+
"timestamp": "2024-12-18T22:39:59.117Z"
|
11
7
|
}
|
package/jest.setup.ts
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
import { MAGIC_BYTE } from './src/wireEncoder'
|
2
2
|
import decode from './src/wireDecoder'
|
3
|
+
import { MatcherFunction } from 'expect'
|
3
4
|
|
4
|
-
const toMatchConfluentEncodedPayload
|
5
|
-
|
5
|
+
const toMatchConfluentEncodedPayload: MatcherFunction<[{ payload: Buffer }]> = function(
|
6
|
+
received,
|
7
|
+
{ payload: expectedPayload },
|
8
|
+
) {
|
9
|
+
const { printExpected, printReceived, printWithType } = this.utils
|
6
10
|
|
7
11
|
if (!Buffer.isBuffer(expectedPayload)) {
|
8
12
|
const error = [
|
@@ -13,16 +17,17 @@ const toMatchConfluentEncodedPayload = context => (received, { payload: expected
|
|
13
17
|
throw new Error(error)
|
14
18
|
}
|
15
19
|
|
16
|
-
const { magicByte, payload } = decode(received)
|
20
|
+
const { magicByte, payload } = decode(received as Buffer)
|
17
21
|
const expectedMessage = decode(expectedPayload)
|
18
22
|
|
19
23
|
if (!Buffer.isBuffer(received)) {
|
20
24
|
return {
|
21
25
|
pass: false,
|
22
|
-
message: () =>
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
message: () =>
|
27
|
+
[
|
28
|
+
'Received value must be a Buffer',
|
29
|
+
printWithType('Received', received, printReceived),
|
30
|
+
].join('\n'),
|
26
31
|
}
|
27
32
|
}
|
28
33
|
|
@@ -40,7 +45,7 @@ const toMatchConfluentEncodedPayload = context => (received, { payload: expected
|
|
40
45
|
}
|
41
46
|
|
42
47
|
return {
|
43
|
-
pass:
|
48
|
+
pass: this.equals(payload, expectedMessage.payload),
|
44
49
|
message: () =>
|
45
50
|
[
|
46
51
|
'expected payload',
|
@@ -52,9 +57,14 @@ const toMatchConfluentEncodedPayload = context => (received, { payload: expected
|
|
52
57
|
}
|
53
58
|
|
54
59
|
expect.extend({
|
55
|
-
toMatchConfluentEncodedPayload
|
56
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
57
|
-
// @ts-ignore
|
58
|
-
return toMatchConfluentEncodedPayload(this)(...args)
|
59
|
-
},
|
60
|
+
toMatchConfluentEncodedPayload,
|
60
61
|
})
|
62
|
+
|
63
|
+
declare global {
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
65
|
+
namespace jest {
|
66
|
+
interface Matchers<R, T = {}> {
|
67
|
+
toMatchConfluentEncodedPayload(args: { registryId: number; payload: Buffer }): R
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "confluent-schema-registry",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.6.2",
|
4
4
|
"main": "dist/index.js",
|
5
5
|
"description": "ConfluentSchemaRegistry is a library that makes it easier to interact with the Confluent schema registry, it provides convenient methods to encode, decode and register new schemas using the Apache Avro serialization format.",
|
6
6
|
"keywords": [
|
@@ -17,40 +17,34 @@
|
|
17
17
|
"build:watch": "rm -rf ./dist && tsc --watch",
|
18
18
|
"test:unit:watch": "yarn test:unit --watch",
|
19
19
|
"test:unit": "jest",
|
20
|
-
"test": "
|
21
|
-
"test:debug": "ts-node ./dockest.ts debug",
|
20
|
+
"test": "docker compose up -d --wait schemaRegistry && jest",
|
22
21
|
"lint": "eslint './src/**/*.ts'",
|
22
|
+
"check:types": "tsc --noEmit",
|
23
23
|
"format": "yarn lint --fix"
|
24
24
|
},
|
25
25
|
"dependencies": {
|
26
26
|
"ajv": "^7.1.0",
|
27
27
|
"avsc": ">= 5.4.13 < 6",
|
28
|
-
"mappersmith": ">= 2.
|
29
|
-
"protobufjs": "
|
28
|
+
"mappersmith": ">= 2.44.0 < 3",
|
29
|
+
"protobufjs": ">= 6.11.4 < 8"
|
30
30
|
},
|
31
31
|
"devDependencies": {
|
32
|
-
"@types/
|
33
|
-
"@types/
|
34
|
-
"@types/jest": "^25.2.1",
|
35
|
-
"@types/node": "^12.7.3",
|
32
|
+
"@types/jest": "^29.5.14",
|
33
|
+
"@types/node": "^18.19.70",
|
36
34
|
"@types/prettier": "^1.18.2",
|
37
|
-
"@types/uuid": "^3.4.5",
|
38
35
|
"@typescript-eslint/eslint-plugin": "^2.1.0",
|
39
36
|
"@typescript-eslint/parser": "^2.1.0",
|
40
37
|
"@typescript-eslint/typescript-estree": "^2.1.0",
|
41
38
|
"ajv8": "npm:ajv@^8.6.3",
|
42
|
-
"dockest": "^2.1.0",
|
43
39
|
"eslint": "^6.3.0",
|
44
40
|
"eslint-config-prettier": "^6.1.0",
|
45
41
|
"eslint-plugin-no-only-tests": "^2.3.1",
|
46
42
|
"eslint-plugin-prettier": "^3.1.0",
|
47
|
-
"
|
48
|
-
"fs-extra": "^8.1.0",
|
49
|
-
"jest": "^25.2.7",
|
43
|
+
"jest": "^29.7.0",
|
50
44
|
"prettier": "^1.18.2",
|
51
|
-
"ts-jest": "^
|
45
|
+
"ts-jest": "^29.2.5",
|
52
46
|
"ts-node": "^8.3.0",
|
53
|
-
"typescript": "^
|
54
|
-
"uuid": "^
|
47
|
+
"typescript": "^5.7.3",
|
48
|
+
"uuid": "^11.0.5"
|
55
49
|
}
|
56
50
|
}
|
package/src/@types.ts
CHANGED
@@ -23,10 +23,12 @@ export type AvroOptions = Partial<ForSchemaOptions> & {
|
|
23
23
|
}
|
24
24
|
|
25
25
|
export type JsonOptions = ConstructorParameters<typeof Ajv>[0] & {
|
26
|
-
ajvInstance?:
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
ajvInstance?:
|
27
|
+
| {
|
28
|
+
addSchema: Ajv['addSchema']
|
29
|
+
compile: (schema: any) => ValidateFunction
|
30
|
+
}
|
31
|
+
| Ajv
|
30
32
|
referencedSchemas?: JsonConfluentSchema[]
|
31
33
|
}
|
32
34
|
export type ProtoOptions = { messageName?: string; referencedSchemas?: ProtoConfluentSchema[] }
|
@@ -94,12 +96,3 @@ export interface SchemaResponse {
|
|
94
96
|
}
|
95
97
|
|
96
98
|
export type ConfluentSchema = AvroConfluentSchema | ProtoConfluentSchema | JsonConfluentSchema
|
97
|
-
|
98
|
-
declare global {
|
99
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
100
|
-
namespace jest {
|
101
|
-
interface Matchers<R, T = {}> {
|
102
|
-
toMatchConfluentEncodedPayload(args: { registryId: number; payload: Buffer }): R
|
103
|
-
}
|
104
|
-
}
|
105
|
-
}
|
package/src/AvroHelper.ts
CHANGED
@@ -12,7 +12,7 @@ import { ConfluentSchemaRegistryArgumentError } from './errors'
|
|
12
12
|
import avro, { ForSchemaOptions, Schema, Type } from 'avsc'
|
13
13
|
import { SchemaResponse, SchemaType } from './@types'
|
14
14
|
|
15
|
-
type TypeHook = (schema: Schema, opts: ForSchemaOptions) => Type
|
15
|
+
type TypeHook = (schema: Schema, opts: ForSchemaOptions) => Type | undefined
|
16
16
|
export default class AvroHelper implements SchemaHelper {
|
17
17
|
private getRawAvroSchema(schema: ConfluentSchema): RawAvroSchema {
|
18
18
|
return (typeof schema.schema === 'string'
|
@@ -57,8 +57,7 @@ export default class AvroHelper implements SchemaHelper {
|
|
57
57
|
|
58
58
|
public getSubject(
|
59
59
|
schema: AvroConfluentSchema,
|
60
|
-
|
61
|
-
avroSchema: AvroSchema,
|
60
|
+
_avroSchema: AvroSchema,
|
62
61
|
separator: string,
|
63
62
|
): ConfluentSubject {
|
64
63
|
const rawSchema: RawAvroSchema = this.getRawAvroSchema(schema)
|
package/src/JsonSchema.ts
CHANGED
@@ -5,6 +5,7 @@ import { ConfluentSchemaRegistryValidationError } from './errors'
|
|
5
5
|
interface BaseAjvValidationError {
|
6
6
|
data?: unknown
|
7
7
|
schema?: unknown
|
8
|
+
message?: string
|
8
9
|
}
|
9
10
|
interface OldAjvValidationError extends BaseAjvValidationError {
|
10
11
|
dataPath: string
|
@@ -41,8 +42,15 @@ export default class JsonSchema implements Schema {
|
|
41
42
|
}
|
42
43
|
|
43
44
|
private validatePayload(payload: any) {
|
44
|
-
const paths:
|
45
|
-
|
45
|
+
const paths: any[] = []
|
46
|
+
|
47
|
+
if (
|
48
|
+
!this.isValid(payload, {
|
49
|
+
errorHook: (path, message) => {
|
50
|
+
paths.push({ path, message })
|
51
|
+
},
|
52
|
+
})
|
53
|
+
) {
|
46
54
|
throw new ConfluentSchemaRegistryValidationError('invalid payload', paths)
|
47
55
|
}
|
48
56
|
}
|
@@ -57,19 +65,23 @@ export default class JsonSchema implements Schema {
|
|
57
65
|
this.validatePayload(payload)
|
58
66
|
return payload
|
59
67
|
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
68
|
+
|
69
|
+
public isValid(
|
70
|
+
payload: object,
|
71
|
+
opts?: { errorHook: (path: Array<string>, value: any, type?: any) => void },
|
72
|
+
): boolean {
|
73
|
+
if (!this.validate(payload)) {
|
74
|
+
if (opts?.errorHook) {
|
75
|
+
for (const err of this.validate.errors as AjvValidationError[]) {
|
76
|
+
const path = this.isOldAjvValidationError(err) ? err.dataPath : err.instancePath
|
77
|
+
opts.errorHook([path], err.message ?? err.data, err.schema)
|
78
|
+
}
|
79
|
+
return false
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return true
|
83
|
+
}
|
84
|
+
|
73
85
|
private isOldAjvValidationError(error: AjvValidationError): error is OldAjvValidationError {
|
74
86
|
return (error as OldAjvValidationError).dataPath != null
|
75
87
|
}
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import SchemaRegistry, { RegisteredSchema } from './SchemaRegistry'
|
2
2
|
import API from './api'
|
3
3
|
import { JsonConfluentSchema, SchemaType } from './@types'
|
4
|
+
import Ajv from 'ajv'
|
5
|
+
import { ConfluentSchemaRegistryValidationError } from './errors'
|
4
6
|
|
5
7
|
const REGISTRY_HOST = 'http://localhost:8982'
|
6
8
|
const schemaRegistryAPIClientArgs = { host: REGISTRY_HOST }
|
@@ -100,8 +102,13 @@ describe('SchemaRegistry', () => {
|
|
100
102
|
let api
|
101
103
|
|
102
104
|
beforeEach(async () => {
|
105
|
+
const options = {
|
106
|
+
[SchemaType.JSON]: {
|
107
|
+
allErrors: true,
|
108
|
+
},
|
109
|
+
}
|
103
110
|
api = API(schemaRegistryAPIClientArgs)
|
104
|
-
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
111
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs, options)
|
105
112
|
})
|
106
113
|
|
107
114
|
describe('when register', () => {
|
@@ -161,6 +168,16 @@ describe('SchemaRegistry', () => {
|
|
161
168
|
|
162
169
|
expect(resultObj).toEqual(obj)
|
163
170
|
})
|
171
|
+
|
172
|
+
it('should return error message', async () => {
|
173
|
+
const obj = { id2a: 'sdfsdfsdf', level2a: 1 }
|
174
|
+
try {
|
175
|
+
await schemaRegistry.encode(registeredSchema.id, obj)
|
176
|
+
} catch (ex) {
|
177
|
+
expect(ex.paths[0].message).toBeDefined()
|
178
|
+
expect(ex.paths[0].message).toEqual('should be number')
|
179
|
+
}
|
180
|
+
})
|
164
181
|
})
|
165
182
|
|
166
183
|
describe('with multiple reference', () => {
|
@@ -587,7 +587,7 @@ describe('SchemaRegistry - new Api', () => {
|
|
587
587
|
} catch (error) {
|
588
588
|
expect(error).toBeInstanceOf(ConfluentSchemaRegistryValidationError)
|
589
589
|
expect(error.message).toEqual('invalid payload')
|
590
|
-
expect(error.paths).toEqual([
|
590
|
+
expect(error.paths[0].path).toEqual(['/fullName'])
|
591
591
|
}
|
592
592
|
},
|
593
593
|
)
|
@@ -250,3 +250,18 @@ describe('SchemaRegistry - old AVRO api', () => {
|
|
250
250
|
})
|
251
251
|
})
|
252
252
|
})
|
253
|
+
|
254
|
+
describe('SchemaRegistry - Custom Middleware', () => {
|
255
|
+
const customMiddleware = jest.fn()
|
256
|
+
|
257
|
+
const schemaRegistry = new SchemaRegistry({
|
258
|
+
...schemaRegistryArgs,
|
259
|
+
middlewares: [customMiddleware],
|
260
|
+
})
|
261
|
+
|
262
|
+
it('should have called the custom middleware', async () => {
|
263
|
+
await schemaRegistry.register(personSchema)
|
264
|
+
|
265
|
+
expect(customMiddleware).toHaveBeenCalled()
|
266
|
+
})
|
267
|
+
})
|
package/src/SchemaRegistry.ts
CHANGED
@@ -3,7 +3,7 @@ import { Response } from 'mappersmith'
|
|
3
3
|
|
4
4
|
import { encode, MAGIC_BYTE } from './wireEncoder'
|
5
5
|
import decode from './wireDecoder'
|
6
|
-
import { COMPATIBILITY,
|
6
|
+
import { COMPATIBILITY, DEFAULT_SEPARATOR } from './constants'
|
7
7
|
import API, { SchemaRegistryAPIClientArgs, SchemaRegistryAPIClient } from './api'
|
8
8
|
import Cache from './cache'
|
9
9
|
import {
|
@@ -46,13 +46,13 @@ interface Opts {
|
|
46
46
|
interface AvroDecodeOptions {
|
47
47
|
readerSchema?: RawAvroSchema | AvroSchema | Schema
|
48
48
|
}
|
49
|
-
interface DecodeOptions {
|
49
|
+
export interface DecodeOptions {
|
50
50
|
[SchemaType.AVRO]?: AvroDecodeOptions
|
51
51
|
}
|
52
52
|
|
53
53
|
const DEFAULT_OPTS = {
|
54
54
|
compatibility: COMPATIBILITY.BACKWARD,
|
55
|
-
separator:
|
55
|
+
separator: DEFAULT_SEPARATOR,
|
56
56
|
}
|
57
57
|
export default class SchemaRegistry {
|
58
58
|
private api: SchemaRegistryAPIClient
|
@@ -62,10 +62,10 @@ export default class SchemaRegistry {
|
|
62
62
|
public cache: Cache
|
63
63
|
|
64
64
|
constructor(
|
65
|
-
{ auth, clientId, host, retry, agent }: SchemaRegistryAPIClientArgs,
|
65
|
+
{ auth, clientId, host, retry, agent, middlewares }: SchemaRegistryAPIClientArgs,
|
66
66
|
options?: SchemaRegistryAPIClientOptions,
|
67
67
|
) {
|
68
|
-
this.api = API({ auth, clientId, host, retry, agent })
|
68
|
+
this.api = API({ auth, clientId, host, retry, agent, middlewares })
|
69
69
|
this.cache = new Cache()
|
70
70
|
this.options = options
|
71
71
|
}
|
@@ -138,7 +138,7 @@ export default class SchemaRegistry {
|
|
138
138
|
)
|
139
139
|
}
|
140
140
|
} catch (error) {
|
141
|
-
if (error.status !== 404) {
|
141
|
+
if (!error || typeof error !== 'object' || !('status' in error) || error.status !== 404) {
|
142
142
|
throw error
|
143
143
|
} else {
|
144
144
|
isFirstTimeRegistration = true
|
@@ -355,7 +355,7 @@ export default class SchemaRegistry {
|
|
355
355
|
|
356
356
|
return id
|
357
357
|
} catch (error) {
|
358
|
-
if (error
|
358
|
+
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
|
359
359
|
throw new ConfluentSchemaRegistryError(error)
|
360
360
|
}
|
361
361
|
|
@@ -370,18 +370,17 @@ export default class SchemaRegistry {
|
|
370
370
|
return id
|
371
371
|
}
|
372
372
|
|
373
|
-
private getSchemaOriginRequest(registryId: number) {
|
373
|
+
private async getSchemaOriginRequest(registryId: number): Promise<Response> {
|
374
374
|
// ensure that cache-misses result in a single origin request
|
375
|
-
|
376
|
-
|
377
|
-
} else {
|
378
|
-
const request = this.api.Schema.find({ id: registryId }).finally(() => {
|
379
|
-
delete this.cacheMissRequests[registryId]
|
380
|
-
})
|
375
|
+
const req = this.cacheMissRequests[registryId]
|
376
|
+
if (req) return req
|
381
377
|
|
382
|
-
|
378
|
+
const request = this.api.Schema.find({ id: registryId }).finally(() => {
|
379
|
+
delete this.cacheMissRequests[registryId]
|
380
|
+
})
|
383
381
|
|
384
|
-
|
385
|
-
|
382
|
+
this.cacheMissRequests[registryId] = request
|
383
|
+
|
384
|
+
return request
|
386
385
|
}
|
387
386
|
}
|
package/src/api/index.spec.ts
CHANGED
@@ -1,7 +1,27 @@
|
|
1
|
+
import { Middleware } from 'mappersmith'
|
1
2
|
import API from '.'
|
2
3
|
import { mockClient, install, uninstall } from 'mappersmith/test'
|
3
4
|
|
4
|
-
const
|
5
|
+
const customMiddleware: Middleware = jest.fn(() => {
|
6
|
+
return {
|
7
|
+
async request(request) {
|
8
|
+
return request.enhance({
|
9
|
+
headers: {
|
10
|
+
Authorization: 'Bearer Random',
|
11
|
+
},
|
12
|
+
})
|
13
|
+
},
|
14
|
+
async response(next) {
|
15
|
+
return next()
|
16
|
+
},
|
17
|
+
}
|
18
|
+
})
|
19
|
+
|
20
|
+
const client = API({
|
21
|
+
clientId: 'test-client',
|
22
|
+
host: 'http://example.com',
|
23
|
+
middlewares: [customMiddleware],
|
24
|
+
})
|
5
25
|
const mock = mockClient<typeof client>(client)
|
6
26
|
.resource('Schema')
|
7
27
|
.method('find')
|
@@ -14,10 +34,12 @@ describe('API Client', () => {
|
|
14
34
|
|
15
35
|
afterEach(() => uninstall())
|
16
36
|
|
17
|
-
it('should include a user agent header', async () => {
|
37
|
+
it('should include a user agent header and call custom middleware', async () => {
|
18
38
|
const response = await client.Schema.find({ id: 'abc' })
|
19
39
|
|
20
40
|
expect(mock.callsCount()).toBe(1)
|
21
41
|
expect(response.request().header('User-Agent')).not.toBeUndefined()
|
42
|
+
expect(response.request().header('Authorization')).toBe('Bearer Random')
|
43
|
+
expect(customMiddleware).toHaveBeenCalled()
|
22
44
|
})
|
23
45
|
})
|
package/src/api/index.ts
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
import { Agent } from 'http'
|
2
|
-
import forge, {
|
2
|
+
import forge, {
|
3
|
+
Authorization,
|
4
|
+
Client,
|
5
|
+
GatewayConfiguration,
|
6
|
+
Middleware,
|
7
|
+
ManifestOptions,
|
8
|
+
} from 'mappersmith'
|
3
9
|
import RetryMiddleware, { RetryMiddlewareOptions } from 'mappersmith/middleware/retry/v2'
|
4
10
|
import BasicAuthMiddleware from 'mappersmith/middleware/basic-auth'
|
5
11
|
|
@@ -23,6 +29,7 @@ export interface SchemaRegistryAPIClientArgs {
|
|
23
29
|
retry?: Partial<RetryMiddlewareOptions>
|
24
30
|
/** HTTP Agent that will be passed to underlying API calls */
|
25
31
|
agent?: Agent
|
32
|
+
middlewares?: Middleware[]
|
26
33
|
}
|
27
34
|
|
28
35
|
// TODO: Improve typings
|
@@ -48,10 +55,11 @@ export default ({
|
|
48
55
|
host,
|
49
56
|
retry = {},
|
50
57
|
agent,
|
58
|
+
middlewares = [],
|
51
59
|
}: SchemaRegistryAPIClientArgs): SchemaRegistryAPIClient => {
|
52
60
|
const clientId = userClientId || DEFAULT_API_CLIENT_ID
|
53
61
|
// FIXME: ResourcesType typings is not exposed by mappersmith
|
54
|
-
const manifest:
|
62
|
+
const manifest: ManifestOptions<any> = {
|
55
63
|
clientId,
|
56
64
|
ignoreGlobalMiddleware: true,
|
57
65
|
host,
|
@@ -61,6 +69,7 @@ export default ({
|
|
61
69
|
RetryMiddleware(Object.assign(DEFAULT_RETRY, retry)),
|
62
70
|
errorMiddleware,
|
63
71
|
...(auth ? [BasicAuthMiddleware(auth)] : []),
|
72
|
+
...middlewares,
|
64
73
|
],
|
65
74
|
resources: {
|
66
75
|
Schema: {
|
@@ -30,7 +30,7 @@ const errorMiddleware: Middleware = ({ clientId }) => ({
|
|
30
30
|
new Promise((resolve, reject) =>
|
31
31
|
next()
|
32
32
|
.then(resolve)
|
33
|
-
.catch((response: Response) => reject(new ResponseError(clientId, response))),
|
33
|
+
.catch((response: Response) => reject(new ResponseError(clientId ?? '', response))),
|
34
34
|
),
|
35
35
|
})
|
36
36
|
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
import { default as SchemaRegistry, DecodeOptions } from './SchemaRegistry'
|
2
|
+
export { SchemaRegistry, DecodeOptions }
|
2
3
|
export * from './utils'
|
3
4
|
export { SchemaType } from './@types'
|
4
5
|
export { COMPATIBILITY } from './constants'
|
@@ -96,6 +96,7 @@ export const schemaFromConfluentSchema = (
|
|
96
96
|
|
97
97
|
return schema
|
98
98
|
} catch (err) {
|
99
|
-
throw new ConfluentSchemaRegistryArgumentError(err.message)
|
99
|
+
if (err instanceof Error) throw new ConfluentSchemaRegistryArgumentError(err.message)
|
100
|
+
throw err
|
100
101
|
}
|
101
102
|
}
|