confluent-schema-registry 3.3.6 → 3.6.2

Sign up to get free protection for your applications and to get access to all the features.
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
- -field for error validation , and the issue of failing
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 ${PWD}:/share coderfi/avro-tools:1.7.7 idl2schemata ${avdl_path} tmp && cat tmp/${avsc_name}.avsc
9
+ docker run --rm -v "$(pwd)":/avro kpnnl/avro-tools:1.12.0 idl2schemata ${avdl_path} tmp && cat tmp/${avsc_name}.avsc
@@ -1,11 +1,7 @@
1
1
  {
2
2
  "errorPayload": {
3
- "trap": "unhandledRejection",
4
- "reason": {
5
- "payload": {},
6
- "name": "DockestError"
7
- },
8
- "promise": {}
3
+ "trap": "SIGINT",
4
+ "signal": "SIGINT"
9
5
  },
10
- "timestamp": "2024-09-03T14:42:35.241Z"
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 = context => (received, { payload: expectedPayload }) => {
5
- const { printExpected, printReceived, printWithType } = context.utils
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
- 'Received value must be a Buffer',
24
- printWithType('Received', received, printReceived),
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: context.equals(payload, expectedMessage.payload),
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(...args) {
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.6",
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": "ts-node ./dockest.ts",
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.30.1 < 3",
29
- "protobufjs": "^6.11.4"
28
+ "mappersmith": ">= 2.44.0 < 3",
29
+ "protobufjs": ">= 6.11.4 < 8"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/execa": "^2.0.0",
33
- "@types/fs-extra": "^8.0.0",
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
- "execa": "^2.0.4",
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": "^24.0.2",
45
+ "ts-jest": "^29.2.5",
52
46
  "ts-node": "^8.3.0",
53
- "typescript": "^3.6.2",
54
- "uuid": "^3.3.3"
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
- addSchema: Ajv['addSchema']
28
- compile: (schema: any) => ValidateFunction
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
- // @ts-ignore
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: string[][] = []
45
- if (!this.isValid(payload, { errorHook: path => paths.push(path) })) {
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
- public isValid(payload: object, opts?: { errorHook: (path: Array<string>, value: any, type?: any) => void }): boolean {
61
- if (!this.validate(payload)) {
62
- if (opts === null || opts === void 0 ? void 0 : opts.errorHook) {
63
- for (const err of (this.validate.errors as any)) {
64
- const path = this.isOldAjvValidationError(err) ? err.dataPath : err.instancePath;
65
- opts.errorHook([err], err.data, err.schema);
66
- }
67
- }
68
- return false;
69
- }
70
- return true;
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([['/fullName']])
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
+ })
@@ -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, DEFAULT_SEPERATOR } from './constants'
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: DEFAULT_SEPERATOR,
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.status && error.status === 404) {
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
- if (this.cacheMissRequests[registryId]) {
376
- return this.cacheMissRequests[registryId]
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
- this.cacheMissRequests[registryId] = request
378
+ const request = this.api.Schema.find({ id: registryId }).finally(() => {
379
+ delete this.cacheMissRequests[registryId]
380
+ })
383
381
 
384
- return request
385
- }
382
+ this.cacheMissRequests[registryId] = request
383
+
384
+ return request
386
385
  }
387
386
  }
@@ -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 client = API({ clientId: 'test-client', host: 'http://example.com' })
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, { Authorization, Client, Options, GatewayConfiguration } from 'mappersmith'
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: Options<any> = {
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
@@ -8,6 +8,6 @@ export enum COMPATIBILITY {
8
8
  FULL_TRANSITIVE = 'FULL_TRANSITIVE',
9
9
  }
10
10
 
11
- export const DEFAULT_SEPERATOR = '.'
11
+ export const DEFAULT_SEPARATOR = '.'
12
12
 
13
13
  export const DEFAULT_API_CLIENT_ID = 'Confluent_Schema_Registry'
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { default as SchemaRegistry } from './SchemaRegistry'
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
  }
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
- import fs from 'fs-extra'
3
- import execa from 'execa'
4
2
  import avro from 'avsc'
3
+ import { exec } from 'child_process'
4
+ import fs from 'node:fs'
5
5
 
6
6
  import SchemaRegistry from '../SchemaRegistry'
7
7
  import { avdlToAVSCAsync } from './avdlToAVSC'
@@ -9,13 +9,26 @@ import { avdlToAVSCAsync } from './avdlToAVSC'
9
9
  const registry = new SchemaRegistry({ host: 'http://localhost:8982' })
10
10
  const absolutePath = (...paths: string[]) => path.join(__dirname, '../..', ...paths)
11
11
 
12
+ const promisifiedExec = async (command: string): Promise<string> => {
13
+ return new Promise((resolve, reject) => {
14
+ exec(command, (error, stdout) => {
15
+ if (error) {
16
+ return reject(error)
17
+ }
18
+
19
+ return resolve(stdout)
20
+ })
21
+ })
22
+ }
23
+
12
24
  const compareWithJavaImplementation = (avdlPath: string, name: string) => async () => {
13
25
  const absolutePathToAvdlToAVSC = absolutePath('./bin/avdlToAVSC.sh')
14
- const execaArgs = [`./fixtures/avdl/${avdlPath}`, name]
15
26
 
16
27
  let expectedAVSC
17
28
  try {
18
- const { stdout: result } = await execa(absolutePathToAvdlToAVSC, execaArgs)
29
+ const result = await promisifiedExec(
30
+ `${absolutePathToAvdlToAVSC} ./fixtures/avdl/${avdlPath} ${name}`,
31
+ )
19
32
  expectedAVSC = JSON.parse(result)
20
33
  } catch (error) {
21
34
  console.error(`Error when running ${absolutePathToAvdlToAVSC}`, error) // eslint-disable-line no-console
@@ -31,7 +44,17 @@ const compareWithJavaImplementation = (avdlPath: string, name: string) => async
31
44
 
32
45
  beforeAll(async () => {
33
46
  jest.setTimeout(10000)
34
- await fs.emptyDir(absolutePath('./tmp'))
47
+
48
+ // deletes all the files from tmp dir
49
+ const tmpDirectory = absolutePath('./tmp')
50
+ try {
51
+ fs.statSync(tmpDirectory)
52
+ } catch (e) {
53
+ fs.mkdirSync(tmpDirectory)
54
+ }
55
+ for (const file of fs.readdirSync(tmpDirectory)) {
56
+ fs.unlinkSync(path.join(tmpDirectory, file))
57
+ }
35
58
  })
36
59
 
37
60
  test('simple protocol', compareWithJavaImplementation('simple.avdl', 'Simple'))
@@ -21,7 +21,7 @@ interface Field {
21
21
 
22
22
  let cache: any
23
23
  const merge = Object.assign
24
- const isObject = (obj: unknown): obj is Obj => obj && typeof obj === 'object'
24
+ const isObject = (obj: unknown): obj is Obj => !!obj && typeof obj === 'object'
25
25
  const isIterable = (obj: unknown): obj is Iterable =>
26
26
  isObject(obj) && typeof obj.map !== 'undefined'
27
27
  const isFieldArray = (field: unknown): field is Field =>
@@ -92,12 +92,12 @@ export function avdlToAVSC(path: any) {
92
92
  export async function avdlToAVSCAsync(path: string) {
93
93
  cache = {}
94
94
 
95
- const protocol: { [key: string]: any } = await new Promise((resolve, reject) => {
95
+ const protocol: Record<string, any> = await new Promise((resolve, reject) => {
96
96
  assembleProtocol(path, (err: AssembleProtocolError, schema) => {
97
97
  if (err) {
98
98
  reject(new ConfluentSchemaRegistryError(`${err.message}. Caused by: ${err.path}`))
99
99
  } else {
100
- resolve(schema)
100
+ resolve(schema as Record<string, any>)
101
101
  }
102
102
  })
103
103
  })
package/tmp/Array.avsc ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Array",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "properties",
7
+ "type" : {
8
+ "type" : "array",
9
+ "items" : {
10
+ "type" : "record",
11
+ "name" : "KeyValue",
12
+ "fields" : [ {
13
+ "name" : "key",
14
+ "type" : "string"
15
+ }, {
16
+ "name" : "value",
17
+ "type" : "string"
18
+ } ]
19
+ }
20
+ }
21
+ } ]
22
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Array1",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "array2",
7
+ "type" : [ "null", {
8
+ "type" : "array",
9
+ "items" : {
10
+ "type" : "record",
11
+ "name" : "Array2",
12
+ "fields" : [ {
13
+ "name" : "array2_name",
14
+ "type" : "string"
15
+ }, {
16
+ "name" : "foos",
17
+ "type" : [ "null", {
18
+ "type" : "enum",
19
+ "name" : "Foos",
20
+ "symbols" : [ "foo", "bar", "baz" ]
21
+ } ],
22
+ "default" : null
23
+ } ]
24
+ }
25
+ } ],
26
+ "default" : null
27
+ } ]
28
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Array2",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "array2_name",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "foos",
10
+ "type" : [ "null", {
11
+ "type" : "enum",
12
+ "name" : "Foos",
13
+ "symbols" : [ "foo", "bar", "baz" ]
14
+ } ],
15
+ "default" : null
16
+ } ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Array3",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "bam",
7
+ "type" : [ "null", {
8
+ "type" : "record",
9
+ "name" : "Bam",
10
+ "fields" : [ {
11
+ "name" : "bam",
12
+ "type" : "string"
13
+ } ]
14
+ } ],
15
+ "default" : null
16
+ } ]
17
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "ArrayUnion",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "properties",
7
+ "type" : [ "null", {
8
+ "type" : "array",
9
+ "items" : {
10
+ "type" : "record",
11
+ "name" : "KeyValue",
12
+ "fields" : [ {
13
+ "name" : "key",
14
+ "type" : "string"
15
+ }, {
16
+ "name" : "value",
17
+ "type" : "string"
18
+ } ]
19
+ }
20
+ } ],
21
+ "default" : null
22
+ } ]
23
+ }
package/tmp/Bam.avsc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Bam",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "bam",
7
+ "type" : "string"
8
+ } ]
9
+ }
package/tmp/Bar.avsc ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Bar",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "baz",
7
+ "type" : {
8
+ "type" : "record",
9
+ "name" : "Baz",
10
+ "fields" : [ {
11
+ "name" : "array1",
12
+ "type" : [ "null", {
13
+ "type" : "array",
14
+ "items" : {
15
+ "type" : "record",
16
+ "name" : "Array1",
17
+ "fields" : [ {
18
+ "name" : "array2",
19
+ "type" : [ "null", {
20
+ "type" : "array",
21
+ "items" : {
22
+ "type" : "record",
23
+ "name" : "Array2",
24
+ "fields" : [ {
25
+ "name" : "array2_name",
26
+ "type" : "string"
27
+ }, {
28
+ "name" : "foos",
29
+ "type" : [ "null", {
30
+ "type" : "enum",
31
+ "name" : "Foos",
32
+ "symbols" : [ "foo", "bar", "baz" ]
33
+ } ],
34
+ "default" : null
35
+ } ]
36
+ }
37
+ } ],
38
+ "default" : null
39
+ } ]
40
+ }
41
+ } ],
42
+ "default" : null
43
+ } ]
44
+ }
45
+ } ]
46
+ }
package/tmp/Baz.avsc ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Baz",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "array1",
7
+ "type" : [ "null", {
8
+ "type" : "array",
9
+ "items" : {
10
+ "type" : "record",
11
+ "name" : "Array1",
12
+ "fields" : [ {
13
+ "name" : "array2",
14
+ "type" : [ "null", {
15
+ "type" : "array",
16
+ "items" : {
17
+ "type" : "record",
18
+ "name" : "Array2",
19
+ "fields" : [ {
20
+ "name" : "array2_name",
21
+ "type" : "string"
22
+ }, {
23
+ "name" : "foos",
24
+ "type" : [ "null", {
25
+ "type" : "enum",
26
+ "name" : "Foos",
27
+ "symbols" : [ "foo", "bar", "baz" ]
28
+ } ],
29
+ "default" : null
30
+ } ]
31
+ }
32
+ } ],
33
+ "default" : null
34
+ } ]
35
+ }
36
+ } ],
37
+ "default" : null
38
+ } ]
39
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Complex",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "array1",
7
+ "type" : [ "null", {
8
+ "type" : "array",
9
+ "items" : {
10
+ "type" : "record",
11
+ "name" : "Array1",
12
+ "fields" : [ {
13
+ "name" : "array2",
14
+ "type" : [ "null", {
15
+ "type" : "array",
16
+ "items" : {
17
+ "type" : "record",
18
+ "name" : "Array2",
19
+ "fields" : [ {
20
+ "name" : "array2_name",
21
+ "type" : "string"
22
+ }, {
23
+ "name" : "foos",
24
+ "type" : [ "null", {
25
+ "type" : "enum",
26
+ "name" : "Foos",
27
+ "symbols" : [ "foo", "bar", "baz" ]
28
+ } ],
29
+ "default" : null
30
+ } ]
31
+ }
32
+ } ],
33
+ "default" : null
34
+ } ]
35
+ }
36
+ } ],
37
+ "default" : null
38
+ }, {
39
+ "name" : "array3",
40
+ "type" : {
41
+ "type" : "array",
42
+ "items" : {
43
+ "type" : "record",
44
+ "name" : "Array3",
45
+ "fields" : [ {
46
+ "name" : "bam",
47
+ "type" : [ "null", {
48
+ "type" : "record",
49
+ "name" : "Bam",
50
+ "fields" : [ {
51
+ "name" : "bam",
52
+ "type" : "string"
53
+ } ]
54
+ } ],
55
+ "default" : null
56
+ } ]
57
+ }
58
+ }
59
+ }, {
60
+ "name" : "bar",
61
+ "type" : {
62
+ "type" : "record",
63
+ "name" : "Bar",
64
+ "fields" : [ {
65
+ "name" : "baz",
66
+ "type" : {
67
+ "type" : "record",
68
+ "name" : "Baz",
69
+ "fields" : [ {
70
+ "name" : "array1",
71
+ "type" : [ "null", {
72
+ "type" : "array",
73
+ "items" : "Array1"
74
+ } ],
75
+ "default" : null
76
+ } ]
77
+ }
78
+ } ]
79
+ }
80
+ } ]
81
+ }
package/tmp/Enum.avsc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Enum",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "foos",
7
+ "type" : {
8
+ "type" : "enum",
9
+ "name" : "Foos",
10
+ "symbols" : [ "foo", "bar", "baz" ]
11
+ }
12
+ } ]
13
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "EnumUnion",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "foos",
7
+ "type" : [ "null", {
8
+ "type" : "enum",
9
+ "name" : "Foos",
10
+ "symbols" : [ "foo", "bar", "baz" ]
11
+ } ],
12
+ "default" : null
13
+ } ]
14
+ }
package/tmp/Foos.avsc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "type" : "enum",
3
+ "name" : "Foos",
4
+ "namespace" : "com.org.app.track",
5
+ "symbols" : [ "foo", "bar", "baz" ]
6
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "ImportMultipleNamespaces",
4
+ "namespace" : "com.org.domain.fixtures",
5
+ "fields" : [ {
6
+ "name" : "metadata",
7
+ "type" : {
8
+ "type" : "record",
9
+ "name" : "Metadata",
10
+ "namespace" : "com.org.messaging",
11
+ "fields" : [ {
12
+ "name" : "event_id",
13
+ "type" : "string"
14
+ }, {
15
+ "name" : "publisher_system_id",
16
+ "type" : "string"
17
+ }, {
18
+ "name" : "occurred_at",
19
+ "type" : {
20
+ "type" : "long",
21
+ "logicalType" : "timestamp-millis"
22
+ }
23
+ }, {
24
+ "name" : "published_at",
25
+ "type" : {
26
+ "type" : "long",
27
+ "logicalType" : "timestamp-millis"
28
+ }
29
+ }, {
30
+ "name" : "correlation_id",
31
+ "type" : "string"
32
+ } ]
33
+ }
34
+ } ]
35
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "KeyValue",
4
+ "namespace" : "com.org.app.track",
5
+ "fields" : [ {
6
+ "name" : "key",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "value",
10
+ "type" : "string"
11
+ } ]
12
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Metadata",
4
+ "namespace" : "com.org.messaging",
5
+ "fields" : [ {
6
+ "name" : "event_id",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "publisher_system_id",
10
+ "type" : "string"
11
+ }, {
12
+ "name" : "occurred_at",
13
+ "type" : {
14
+ "type" : "long",
15
+ "logicalType" : "timestamp-millis"
16
+ }
17
+ }, {
18
+ "name" : "published_at",
19
+ "type" : {
20
+ "type" : "long",
21
+ "logicalType" : "timestamp-millis"
22
+ }
23
+ }, {
24
+ "name" : "correlation_id",
25
+ "type" : "string"
26
+ } ]
27
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Multiple",
4
+ "namespace" : "com.org.domain.fixtures",
5
+ "fields" : [ {
6
+ "name" : "foo",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "bar",
10
+ "type" : {
11
+ "type" : "record",
12
+ "name" : "Bar",
13
+ "fields" : [ {
14
+ "name" : "baz",
15
+ "type" : {
16
+ "type" : "record",
17
+ "name" : "Baz",
18
+ "fields" : [ {
19
+ "name" : "bam",
20
+ "type" : "string"
21
+ } ]
22
+ }
23
+ } ]
24
+ }
25
+ } ]
26
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "MultipleNamespaces",
4
+ "namespace" : "com.org.confluentschemaregistry",
5
+ "fields" : [ {
6
+ "name" : "metadata",
7
+ "type" : {
8
+ "type" : "record",
9
+ "name" : "Metadata",
10
+ "namespace" : "com.org.messaging",
11
+ "fields" : [ {
12
+ "name" : "event_id",
13
+ "type" : "string"
14
+ }, {
15
+ "name" : "publisher_system_id",
16
+ "type" : "string"
17
+ }, {
18
+ "name" : "occurred_at",
19
+ "type" : {
20
+ "type" : "long",
21
+ "logicalType" : "timestamp-millis"
22
+ }
23
+ }, {
24
+ "name" : "published_at",
25
+ "type" : {
26
+ "type" : "long",
27
+ "logicalType" : "timestamp-millis"
28
+ }
29
+ }, {
30
+ "name" : "correlation_id",
31
+ "type" : "string"
32
+ } ]
33
+ }
34
+ }, {
35
+ "name" : "id",
36
+ "type" : "string"
37
+ }, {
38
+ "name" : "amount",
39
+ "type" : "int"
40
+ }, {
41
+ "name" : "description",
42
+ "type" : "string"
43
+ } ]
44
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "MultipleUnion",
4
+ "namespace" : "com.org.domain.fixtures",
5
+ "fields" : [ {
6
+ "name" : "foo",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "bar",
10
+ "type" : [ "null", {
11
+ "type" : "record",
12
+ "name" : "Bar",
13
+ "fields" : [ {
14
+ "name" : "baz",
15
+ "type" : [ "null", {
16
+ "type" : "record",
17
+ "name" : "Baz",
18
+ "fields" : [ {
19
+ "name" : "bam",
20
+ "type" : "string"
21
+ } ]
22
+ } ],
23
+ "default" : null
24
+ } ]
25
+ } ],
26
+ "default" : null
27
+ } ]
28
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Simple",
4
+ "namespace" : "com.org.domain.fixtures",
5
+ "fields" : [ {
6
+ "name" : "foo",
7
+ "type" : "string"
8
+ } ]
9
+ }
package/tmp/Two.avsc ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Two",
4
+ "namespace" : "com.org.domain.fixtures",
5
+ "fields" : [ {
6
+ "name" : "foo",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "bar",
10
+ "type" : {
11
+ "type" : "record",
12
+ "name" : "Bar",
13
+ "fields" : [ {
14
+ "name" : "baz",
15
+ "type" : "string"
16
+ } ]
17
+ }
18
+ } ]
19
+ }
package/tmp/Union.avsc ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "type" : "record",
3
+ "name" : "Union",
4
+ "namespace" : "com.org.domain.fixtures",
5
+ "fields" : [ {
6
+ "name" : "foo",
7
+ "type" : "string"
8
+ }, {
9
+ "name" : "bar",
10
+ "type" : [ "null", {
11
+ "type" : "record",
12
+ "name" : "Bar",
13
+ "fields" : [ {
14
+ "name" : "baz",
15
+ "type" : "string"
16
+ } ]
17
+ } ],
18
+ "default" : null
19
+ } ]
20
+ }
package/dockest.ts DELETED
@@ -1,30 +0,0 @@
1
- import { Dockest, sleepWithLog, logLevel } from 'dockest'
2
- import { DockestService } from 'dockest/dist/@types'
3
-
4
- const dockest = new Dockest({
5
- composeFile: 'docker-compose.yml',
6
- dumpErrors: true,
7
- jestLib: require('jest'),
8
- jestOpts: {
9
- updateSnapshot: true,
10
- },
11
- logLevel: logLevel.DEBUG,
12
- })
13
-
14
- const dockestServices: DockestService[] = [
15
- {
16
- serviceName: 'zooKeeper',
17
- dependents: [
18
- {
19
- serviceName: 'kafka',
20
- readinessCheck: () => sleepWithLog(10, `Sleeping for Kafka`),
21
- },
22
- ],
23
- },
24
- {
25
- serviceName: 'schemaRegistry',
26
- readinessCheck: () => sleepWithLog(35, `Sleeping for Schema Registry`),
27
- },
28
- ]
29
-
30
- dockest.run(dockestServices)