confluent-schema-registry 3.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. package/.dockerignore +2 -0
  2. package/.prettierrc.js +8 -0
  3. package/CHANGELOG.md +166 -0
  4. package/Dockerfile +10 -0
  5. package/LICENSE +21 -0
  6. package/README.md +44 -0
  7. package/bin/avdlToAVSC.sh +9 -0
  8. package/dist/@types.d.ts +93 -0
  9. package/dist/@types.js +10 -0
  10. package/dist/@types.js.map +1 -0
  11. package/dist/AvroHelper.d.ts +12 -0
  12. package/dist/AvroHelper.js +67 -0
  13. package/dist/AvroHelper.js.map +1 -0
  14. package/dist/JsonHelper.d.ts +7 -0
  15. package/dist/JsonHelper.js +20 -0
  16. package/dist/JsonHelper.js.map +1 -0
  17. package/dist/JsonSchema.d.ts +31 -0
  18. package/dist/JsonSchema.js +58 -0
  19. package/dist/JsonSchema.js.map +1 -0
  20. package/dist/ProtoHelper.d.ts +7 -0
  21. package/dist/ProtoHelper.js +23 -0
  22. package/dist/ProtoHelper.js.map +1 -0
  23. package/dist/ProtoSchema.d.ts +14 -0
  24. package/dist/ProtoSchema.js +66 -0
  25. package/dist/ProtoSchema.js.map +1 -0
  26. package/dist/SchemaRegistry.d.ts +48 -0
  27. package/dist/SchemaRegistry.js +250 -0
  28. package/dist/SchemaRegistry.js.map +1 -0
  29. package/dist/api/index.d.ts +43 -0
  30. package/dist/api/index.js +90 -0
  31. package/dist/api/index.js.map +1 -0
  32. package/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
  33. package/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
  34. package/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
  35. package/dist/api/middleware/errorMiddleware.d.ts +3 -0
  36. package/dist/api/middleware/errorMiddleware.js +20 -0
  37. package/dist/api/middleware/errorMiddleware.js.map +1 -0
  38. package/dist/api/middleware/userAgent.d.ts +3 -0
  39. package/dist/api/middleware/userAgent.js +18 -0
  40. package/dist/api/middleware/userAgent.js.map +1 -0
  41. package/dist/cache.d.ts +20 -0
  42. package/dist/cache.js +24 -0
  43. package/dist/cache.js.map +1 -0
  44. package/dist/constants.d.ts +11 -0
  45. package/dist/constants.js +15 -0
  46. package/dist/constants.js.map +1 -0
  47. package/dist/errors.d.ts +14 -0
  48. package/dist/errors.js +26 -0
  49. package/dist/errors.js.map +1 -0
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.js +13 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/schemaTypeResolver.d.ts +4 -0
  54. package/dist/schemaTypeResolver.js +80 -0
  55. package/dist/schemaTypeResolver.js.map +1 -0
  56. package/dist/utils/avdlToAVSC.d.ts +2 -0
  57. package/dist/utils/avdlToAVSC.js +85 -0
  58. package/dist/utils/avdlToAVSC.js.map +1 -0
  59. package/dist/utils/index.d.ts +2 -0
  60. package/dist/utils/index.js +9 -0
  61. package/dist/utils/index.js.map +1 -0
  62. package/dist/utils/readAVSC.d.ts +3 -0
  63. package/dist/utils/readAVSC.js +33 -0
  64. package/dist/utils/readAVSC.js.map +1 -0
  65. package/dist/wireDecoder.d.ts +7 -0
  66. package/dist/wireDecoder.js +8 -0
  67. package/dist/wireDecoder.js.map +1 -0
  68. package/dist/wireEncoder.d.ts +3 -0
  69. package/dist/wireEncoder.js +10 -0
  70. package/dist/wireEncoder.js.map +1 -0
  71. package/dockest-error.json +11 -0
  72. package/dockest.ts +30 -0
  73. package/jest.setup.ts +60 -0
  74. package/package.json +56 -0
  75. package/release/CHANGELOG.md +166 -0
  76. package/release/LICENSE +21 -0
  77. package/release/README.md +44 -0
  78. package/release/dist/@types.d.ts +93 -0
  79. package/release/dist/@types.js +10 -0
  80. package/release/dist/@types.js.map +1 -0
  81. package/release/dist/AvroHelper.d.ts +12 -0
  82. package/release/dist/AvroHelper.js +67 -0
  83. package/release/dist/AvroHelper.js.map +1 -0
  84. package/release/dist/JsonHelper.d.ts +7 -0
  85. package/release/dist/JsonHelper.js +20 -0
  86. package/release/dist/JsonHelper.js.map +1 -0
  87. package/release/dist/JsonSchema.d.ts +31 -0
  88. package/release/dist/JsonSchema.js +58 -0
  89. package/release/dist/JsonSchema.js.map +1 -0
  90. package/release/dist/ProtoHelper.d.ts +7 -0
  91. package/release/dist/ProtoHelper.js +23 -0
  92. package/release/dist/ProtoHelper.js.map +1 -0
  93. package/release/dist/ProtoSchema.d.ts +14 -0
  94. package/release/dist/ProtoSchema.js +66 -0
  95. package/release/dist/ProtoSchema.js.map +1 -0
  96. package/release/dist/SchemaRegistry.d.ts +48 -0
  97. package/release/dist/SchemaRegistry.js +250 -0
  98. package/release/dist/SchemaRegistry.js.map +1 -0
  99. package/release/dist/api/index.d.ts +43 -0
  100. package/release/dist/api/index.js +90 -0
  101. package/release/dist/api/index.js.map +1 -0
  102. package/release/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
  103. package/release/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
  104. package/release/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
  105. package/release/dist/api/middleware/errorMiddleware.d.ts +3 -0
  106. package/release/dist/api/middleware/errorMiddleware.js +20 -0
  107. package/release/dist/api/middleware/errorMiddleware.js.map +1 -0
  108. package/release/dist/api/middleware/userAgent.d.ts +3 -0
  109. package/release/dist/api/middleware/userAgent.js +18 -0
  110. package/release/dist/api/middleware/userAgent.js.map +1 -0
  111. package/release/dist/cache.d.ts +20 -0
  112. package/release/dist/cache.js +24 -0
  113. package/release/dist/cache.js.map +1 -0
  114. package/release/dist/constants.d.ts +11 -0
  115. package/release/dist/constants.js +15 -0
  116. package/release/dist/constants.js.map +1 -0
  117. package/release/dist/errors.d.ts +14 -0
  118. package/release/dist/errors.js +26 -0
  119. package/release/dist/errors.js.map +1 -0
  120. package/release/dist/index.d.ts +4 -0
  121. package/release/dist/index.js +13 -0
  122. package/release/dist/index.js.map +1 -0
  123. package/release/dist/schemaTypeResolver.d.ts +4 -0
  124. package/release/dist/schemaTypeResolver.js +80 -0
  125. package/release/dist/schemaTypeResolver.js.map +1 -0
  126. package/release/dist/utils/avdlToAVSC.d.ts +2 -0
  127. package/release/dist/utils/avdlToAVSC.js +85 -0
  128. package/release/dist/utils/avdlToAVSC.js.map +1 -0
  129. package/release/dist/utils/index.d.ts +2 -0
  130. package/release/dist/utils/index.js +9 -0
  131. package/release/dist/utils/index.js.map +1 -0
  132. package/release/dist/utils/readAVSC.d.ts +3 -0
  133. package/release/dist/utils/readAVSC.js +33 -0
  134. package/release/dist/utils/readAVSC.js.map +1 -0
  135. package/release/dist/wireDecoder.d.ts +7 -0
  136. package/release/dist/wireDecoder.js +8 -0
  137. package/release/dist/wireDecoder.js.map +1 -0
  138. package/release/dist/wireEncoder.d.ts +3 -0
  139. package/release/dist/wireEncoder.js +10 -0
  140. package/release/dist/wireEncoder.js.map +1 -0
  141. package/release/package.json +56 -0
  142. package/src/@types.ts +105 -0
  143. package/src/AvroHelper.ts +91 -0
  144. package/src/JsonHelper.ts +35 -0
  145. package/src/JsonSchema.ts +80 -0
  146. package/src/ProtoHelper.ts +38 -0
  147. package/src/ProtoSchema.ts +80 -0
  148. package/src/SchemaRegistry.avro.spec.ts +558 -0
  149. package/src/SchemaRegistry.json.spec.ts +364 -0
  150. package/src/SchemaRegistry.newApi.spec.ts +622 -0
  151. package/src/SchemaRegistry.protobuf.spec.ts +372 -0
  152. package/src/SchemaRegistry.spec.ts +252 -0
  153. package/src/SchemaRegistry.ts +387 -0
  154. package/src/api/index.spec.ts +23 -0
  155. package/src/api/index.ts +121 -0
  156. package/src/api/middleware/confluentEncoderMiddleware.ts +36 -0
  157. package/src/api/middleware/errorMiddleware.spec.ts +67 -0
  158. package/src/api/middleware/errorMiddleware.ts +37 -0
  159. package/src/api/middleware/userAgent.spec.ts +53 -0
  160. package/src/api/middleware/userAgent.ts +19 -0
  161. package/src/cache.ts +34 -0
  162. package/src/constants.ts +13 -0
  163. package/src/errors.ts +26 -0
  164. package/src/index.ts +4 -0
  165. package/src/schemaTypeResolver.ts +101 -0
  166. package/src/utils/avdlToAVSC.spec.ts +79 -0
  167. package/src/utils/avdlToAVSC.ts +106 -0
  168. package/src/utils/index.ts +2 -0
  169. package/src/utils/readAVSC.spec.ts +23 -0
  170. package/src/utils/readAVSC.ts +36 -0
  171. package/src/wireDecoder.ts +5 -0
  172. package/src/wireEncoder.ts +10 -0
  173. package/tsconfig.json +22 -0
@@ -0,0 +1,53 @@
1
+ import { Request } from 'mappersmith'
2
+
3
+ import UserAgentMiddleware from './userAgent'
4
+
5
+ const middlewareParams = (clientId?: string) => ({
6
+ resourceName: 'resourceNameMock',
7
+ resourceMethod: 'resourceMethodMock',
8
+ context: { context: 'contextMock' },
9
+ clientId,
10
+ })
11
+
12
+ describe('UserAgentMiddleware', () => {
13
+ let next, request
14
+
15
+ beforeEach(() => {
16
+ request = ({
17
+ enhance: jest.fn(),
18
+ } as unknown) as jest.Mocked<Request>
19
+ next = jest.fn().mockResolvedValue(request)
20
+ })
21
+
22
+ describe('When the user has provided a clientId', () => {
23
+ const params = middlewareParams('some-client-id')
24
+
25
+ it('should add the client id as a user agent comment', async () => {
26
+ const middleware = UserAgentMiddleware(params)
27
+
28
+ await middleware.prepareRequest(next, jest.fn())
29
+
30
+ expect(request.enhance).toHaveBeenCalledWith({
31
+ headers: {
32
+ 'User-Agent': `@kafkajs/confluent-schema-registry (${params.clientId})`,
33
+ },
34
+ })
35
+ })
36
+ })
37
+
38
+ describe('When the user has not provided a clientId', () => {
39
+ const params = middlewareParams()
40
+
41
+ it('should not include a comment in the user agent', async () => {
42
+ const middleware = UserAgentMiddleware(params)
43
+
44
+ await middleware.prepareRequest(next, jest.fn())
45
+
46
+ expect(request.enhance).toHaveBeenCalledWith({
47
+ headers: {
48
+ 'User-Agent': `@kafkajs/confluent-schema-registry`,
49
+ },
50
+ })
51
+ })
52
+ })
53
+ })
@@ -0,0 +1,19 @@
1
+ import { Middleware } from 'mappersmith'
2
+ import { DEFAULT_API_CLIENT_ID } from '../../constants'
3
+
4
+ const product = '@kafkajs/confluent-schema-registry'
5
+
6
+ const userAgentMiddleware: Middleware = ({ clientId }) => {
7
+ const comment = clientId !== DEFAULT_API_CLIENT_ID ? clientId : undefined
8
+ const userAgent = comment ? `${product} (${comment})` : product
9
+ const headers = {
10
+ 'User-Agent': userAgent,
11
+ }
12
+ return {
13
+ prepareRequest: next => {
14
+ return next().then(req => req.enhance({ headers }))
15
+ },
16
+ }
17
+ }
18
+
19
+ export default userAgentMiddleware
package/src/cache.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { AvroSchema, Schema, SchemaType } from './@types'
2
+
3
+ type CacheEntry = { type: SchemaType; schema: Schema | AvroSchema }
4
+
5
+ export default class Cache {
6
+ registryIdBySubject: { [key: string]: number }
7
+ schemasByRegistryId: { [key: string]: CacheEntry }
8
+
9
+ constructor() {
10
+ this.registryIdBySubject = {}
11
+ this.schemasByRegistryId = {}
12
+ }
13
+
14
+ getLatestRegistryId = (subject: string): number | undefined => this.registryIdBySubject[subject]
15
+
16
+ setLatestRegistryId = (subject: string, id: number): number => {
17
+ this.registryIdBySubject[subject] = id
18
+
19
+ return this.registryIdBySubject[subject]
20
+ }
21
+
22
+ getSchema = (registryId: number): CacheEntry | undefined => this.schemasByRegistryId[registryId]
23
+
24
+ setSchema = (registryId: number, type: SchemaType, schema: Schema): CacheEntry => {
25
+ this.schemasByRegistryId[registryId] = { type, schema }
26
+
27
+ return this.schemasByRegistryId[registryId]
28
+ }
29
+
30
+ clear = (): void => {
31
+ this.registryIdBySubject = {}
32
+ this.schemasByRegistryId = {}
33
+ }
34
+ }
@@ -0,0 +1,13 @@
1
+ export enum COMPATIBILITY {
2
+ NONE = 'NONE',
3
+ FULL = 'FULL',
4
+ BACKWARD = 'BACKWARD',
5
+ FORWARD = 'FORWARD',
6
+ BACKWARD_TRANSITIVE = 'BACKWARD_TRANSITIVE',
7
+ FORWARD_TRANSITIVE = 'FORWARD_TRANSITIVE',
8
+ FULL_TRANSITIVE = 'FULL_TRANSITIVE',
9
+ }
10
+
11
+ export const DEFAULT_SEPERATOR = '.'
12
+
13
+ export const DEFAULT_API_CLIENT_ID = 'Confluent_Schema_Registry'
package/src/errors.ts ADDED
@@ -0,0 +1,26 @@
1
+ class ConfluentSchemaRegistryError extends Error {
2
+ constructor(error: any) {
3
+ super(error.message || error)
4
+ this.name = this.constructor.name
5
+ }
6
+ }
7
+
8
+ class ConfluentSchemaRegistryArgumentError extends ConfluentSchemaRegistryError {}
9
+ class ConfluentSchemaRegistryCompatibilityError extends ConfluentSchemaRegistryError {}
10
+ class ConfluentSchemaRegistryInvalidSchemaError extends ConfluentSchemaRegistryError {}
11
+ class ConfluentSchemaRegistryValidationError extends ConfluentSchemaRegistryError {
12
+ public paths: string[][]
13
+
14
+ constructor(error: any, paths: string[][]) {
15
+ super(error)
16
+ this.paths = paths
17
+ }
18
+ }
19
+
20
+ export {
21
+ ConfluentSchemaRegistryError,
22
+ ConfluentSchemaRegistryArgumentError,
23
+ ConfluentSchemaRegistryCompatibilityError,
24
+ ConfluentSchemaRegistryInvalidSchemaError,
25
+ ConfluentSchemaRegistryValidationError,
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { default as SchemaRegistry } from './SchemaRegistry'
2
+ export * from './utils'
3
+ export { SchemaType } from './@types'
4
+ export { COMPATIBILITY } from './constants'
@@ -0,0 +1,101 @@
1
+ import AvroHelper from './AvroHelper'
2
+ import JsonHelper from './JsonHelper'
3
+ import JsonSchema from './JsonSchema'
4
+ import ProtoHelper from './ProtoHelper'
5
+ import ProtoSchema from './ProtoSchema'
6
+ import {
7
+ SchemaType,
8
+ SchemaHelper,
9
+ ConfluentSchema,
10
+ SchemaRegistryAPIClientOptions,
11
+ LegacyOptions,
12
+ ProtocolOptions,
13
+ AvroOptions,
14
+ JsonOptions,
15
+ ProtoOptions,
16
+ Schema,
17
+ AvroSchema,
18
+ } from './@types'
19
+ import { ConfluentSchemaRegistryArgumentError } from './errors'
20
+
21
+ const helperTypeFromSchemaTypeMap: Record<string, SchemaHelper> = {}
22
+
23
+ export const schemaTypeFromString = (schemaTypeString: string) => {
24
+ switch (schemaTypeString) {
25
+ case 'AVRO':
26
+ case undefined:
27
+ return SchemaType.AVRO
28
+ case 'JSON':
29
+ return SchemaType.JSON
30
+ case 'PROTOBUF':
31
+ return SchemaType.PROTOBUF
32
+ default:
33
+ return SchemaType.UNKNOWN
34
+ }
35
+ }
36
+
37
+ export const helperTypeFromSchemaType = (
38
+ schemaType: SchemaType = SchemaType.AVRO,
39
+ ): SchemaHelper => {
40
+ const schemaTypeStr = schemaType.toString()
41
+
42
+ if (!helperTypeFromSchemaTypeMap[schemaTypeStr]) {
43
+ let helper
44
+ switch (schemaType) {
45
+ case SchemaType.AVRO: {
46
+ helper = new AvroHelper()
47
+ break
48
+ }
49
+ case SchemaType.JSON: {
50
+ helper = new JsonHelper()
51
+ break
52
+ }
53
+ case SchemaType.PROTOBUF: {
54
+ helper = new ProtoHelper()
55
+ break
56
+ }
57
+ default:
58
+ throw new ConfluentSchemaRegistryArgumentError('invalid schemaType')
59
+ }
60
+ helperTypeFromSchemaTypeMap[schemaTypeStr] = helper
61
+ }
62
+ return helperTypeFromSchemaTypeMap[schemaTypeStr]
63
+ }
64
+
65
+ export const schemaFromConfluentSchema = (
66
+ confluentSchema: ConfluentSchema,
67
+ options?: SchemaRegistryAPIClientOptions,
68
+ ): Schema | AvroSchema => {
69
+ try {
70
+ let schema: Schema
71
+
72
+ switch (confluentSchema.type) {
73
+ case SchemaType.AVRO: {
74
+ const opts: AvroOptions | undefined =
75
+ (options as LegacyOptions)?.forSchemaOptions ||
76
+ (options as ProtocolOptions)?.[SchemaType.AVRO]
77
+ schema = (helperTypeFromSchemaType(confluentSchema.type) as AvroHelper).getAvroSchema(
78
+ confluentSchema,
79
+ opts,
80
+ )
81
+ break
82
+ }
83
+ case SchemaType.JSON: {
84
+ const opts: JsonOptions | undefined = (options as ProtocolOptions)?.[SchemaType.JSON]
85
+ schema = new JsonSchema(confluentSchema, opts)
86
+ break
87
+ }
88
+ case SchemaType.PROTOBUF: {
89
+ const opts: ProtoOptions | undefined = (options as ProtocolOptions)?.[SchemaType.PROTOBUF]
90
+ schema = new ProtoSchema(confluentSchema, opts)
91
+ break
92
+ }
93
+ default:
94
+ throw new ConfluentSchemaRegistryArgumentError('invalid schemaType')
95
+ }
96
+
97
+ return schema
98
+ } catch (err) {
99
+ throw new ConfluentSchemaRegistryArgumentError(err.message)
100
+ }
101
+ }
@@ -0,0 +1,79 @@
1
+ import path from 'path'
2
+ import fs from 'fs-extra'
3
+ import execa from 'execa'
4
+ import avro from 'avsc'
5
+
6
+ import SchemaRegistry from '../SchemaRegistry'
7
+ import { avdlToAVSCAsync } from './avdlToAVSC'
8
+
9
+ const registry = new SchemaRegistry({ host: 'http://localhost:8982' })
10
+ const absolutePath = (...paths: string[]) => path.join(__dirname, '../..', ...paths)
11
+
12
+ const compareWithJavaImplementation = (avdlPath: string, name: string) => async () => {
13
+ const absolutePathToAvdlToAVSC = absolutePath('./bin/avdlToAVSC.sh')
14
+ const execaArgs = [`./fixtures/avdl/${avdlPath}`, name]
15
+
16
+ let expectedAVSC
17
+ try {
18
+ const { stdout: result } = await execa(absolutePathToAvdlToAVSC, execaArgs)
19
+ expectedAVSC = JSON.parse(result)
20
+ } catch (error) {
21
+ console.error(`Error when running ${absolutePathToAvdlToAVSC}`, error) // eslint-disable-line no-console
22
+ throw error
23
+ }
24
+
25
+ const avsc = await avdlToAVSCAsync(absolutePath('./fixtures/avdl', avdlPath))
26
+
27
+ expect(avsc).toEqual(expectedAVSC)
28
+ expect(avro.Type.forSchema(avsc)).toBeTruthy()
29
+ expect(await registry.register(avsc)).toBeTruthy()
30
+ }
31
+
32
+ beforeAll(async () => {
33
+ jest.setTimeout(10000)
34
+ await fs.emptyDir(absolutePath('./tmp'))
35
+ })
36
+
37
+ test('simple protocol', compareWithJavaImplementation('simple.avdl', 'Simple'))
38
+
39
+ test('protocol with two levels', compareWithJavaImplementation('two.avdl', 'Two'))
40
+
41
+ test('protocol with multiple levels', compareWithJavaImplementation('multiple.avdl', 'Multiple'))
42
+
43
+ test('protocol with union', compareWithJavaImplementation('union.avdl', 'Union'))
44
+
45
+ test(
46
+ 'protocol with multiple union levels',
47
+ compareWithJavaImplementation('multiple_union.avdl', 'MultipleUnion'),
48
+ )
49
+
50
+ test('protocol with enum', compareWithJavaImplementation('enum.avdl', 'Enum'))
51
+
52
+ test('protocol with enum & union', compareWithJavaImplementation('enum_union.avdl', 'EnumUnion'))
53
+
54
+ test('protocol with array', compareWithJavaImplementation('array.avdl', 'Array'))
55
+
56
+ test('protocol with array & union', compareWithJavaImplementation('array_union.avdl', 'ArrayUnion'))
57
+
58
+ test('protocol with really complex stuff', compareWithJavaImplementation('complex.avdl', 'Complex'))
59
+
60
+ test(
61
+ 'protocol with multiple namespaces',
62
+ compareWithJavaImplementation('multiple_namespaces.avdl', 'MultipleNamespaces'),
63
+ )
64
+
65
+ /*
66
+ * AVSC includes the namespace of imported records even if they are being imported
67
+ * into the same namespace, causing a difference against the Java version.
68
+ *
69
+ * @issue: https://github.com/mtth/avsc/issues/281
70
+ */
71
+ test.skip(
72
+ 'protocol with import from same namespace',
73
+ compareWithJavaImplementation('import.avdl', 'Import'),
74
+ )
75
+
76
+ test(
77
+ 'protocol with import from different namespace',
78
+ compareWithJavaImplementation('import_multiple_namespaces.avdl', 'ImportMultipleNamespaces'),
79
+ )
@@ -0,0 +1,106 @@
1
+ import * as fs from 'fs'
2
+ import { assembleProtocol, readProtocol } from 'avsc'
3
+
4
+ import { ConfluentSchemaRegistryError } from '../errors'
5
+
6
+ interface AssembleProtocolError extends Error {
7
+ path: string
8
+ }
9
+ interface Obj {
10
+ [key: string]: any
11
+ }
12
+ interface Iterable extends Obj {
13
+ map: any
14
+ }
15
+ interface Field {
16
+ type: {
17
+ type: string
18
+ items: any
19
+ }
20
+ }
21
+
22
+ let cache: any
23
+ const merge = Object.assign
24
+ const isObject = (obj: unknown): obj is Obj => obj && typeof obj === 'object'
25
+ const isIterable = (obj: unknown): obj is Iterable =>
26
+ isObject(obj) && typeof obj.map !== 'undefined'
27
+ const isFieldArray = (field: unknown): field is Field =>
28
+ isObject(field) && isObject(field.type) && field.type.type === 'array'
29
+
30
+ const combine = (rootType: any, types: any) => {
31
+ if (!rootType.fields) {
32
+ return rootType
33
+ }
34
+
35
+ const find = (name: any) => {
36
+ if (typeof name === 'string') {
37
+ name = name.toLowerCase()
38
+ }
39
+
40
+ const typeToCombine = types.find((t: any) => {
41
+ const names = []
42
+ if (t.namespace) {
43
+ names.push(`${t.namespace}.`)
44
+ }
45
+ names.push(t.name.toLowerCase())
46
+
47
+ return names.join('') === name
48
+ })
49
+
50
+ if (!typeToCombine || cache[typeToCombine.name]) {
51
+ return null
52
+ }
53
+
54
+ cache[typeToCombine.name] = 1
55
+
56
+ return combine(typeToCombine, types)
57
+ }
58
+
59
+ const combinedFields = rootType.fields.map((field: any) => {
60
+ if (isFieldArray(field)) {
61
+ const typeToCombine = find(field.type.items)
62
+ return typeToCombine
63
+ ? merge(field, { type: merge(field.type, { items: typeToCombine }) })
64
+ : field
65
+ } else if (isIterable(field.type)) {
66
+ const type = field.type.map((unionType: any) => {
67
+ if (isObject(unionType)) {
68
+ const typeToCombine = find(unionType.items)
69
+ return typeToCombine ? merge(unionType, { items: typeToCombine }) : unionType
70
+ } else {
71
+ return find(unionType) || unionType
72
+ }
73
+ })
74
+
75
+ return merge(field, { type })
76
+ }
77
+
78
+ const typeToCombine = find(field.type)
79
+ return typeToCombine ? merge(field, { type: typeToCombine }) : field
80
+ })
81
+
82
+ return merge(rootType, { fields: combinedFields })
83
+ }
84
+
85
+ export function avdlToAVSC(path: any) {
86
+ cache = {}
87
+ const protocol = readProtocol(fs.readFileSync(path, 'utf8'))
88
+
89
+ return merge({ namespace: protocol.namespace }, combine(protocol.types.pop(), protocol.types))
90
+ }
91
+
92
+ export async function avdlToAVSCAsync(path: string) {
93
+ cache = {}
94
+
95
+ const protocol: { [key: string]: any } = await new Promise((resolve, reject) => {
96
+ assembleProtocol(path, (err: AssembleProtocolError, schema) => {
97
+ if (err) {
98
+ reject(new ConfluentSchemaRegistryError(`${err.message}. Caused by: ${err.path}`))
99
+ } else {
100
+ resolve(schema)
101
+ }
102
+ })
103
+ })
104
+
105
+ return merge({ namespace: protocol.namespace }, combine(protocol.types.pop(), protocol.types))
106
+ }
@@ -0,0 +1,2 @@
1
+ export { avdlToAVSC, avdlToAVSCAsync } from './avdlToAVSC'
2
+ export { readAVSC, readAVSCAsync } from './readAVSC'
@@ -0,0 +1,23 @@
1
+ import path from 'path'
2
+
3
+ import { readAVSC, readAVSCAsync } from './readAVSC'
4
+ import { ConfluentSchemaRegistryInvalidSchemaError } from '../errors'
5
+
6
+ describe('readAVSC', () => {
7
+ const invalidSchemaFiles = ['invalidType', 'missingFields', 'missingName', 'missingType']
8
+ invalidSchemaFiles.forEach(schemaName => {
9
+ it(`throws an exception for invalid schema definitions - ${schemaName}`, () => {
10
+ expect(() =>
11
+ readAVSC(path.join(__dirname, `../../fixtures/avsc/invalid/${schemaName}.avsc`)),
12
+ ).toThrow(ConfluentSchemaRegistryInvalidSchemaError)
13
+ })
14
+ })
15
+ })
16
+
17
+ describe('readAVSCAsync', () => {
18
+ it('returns a validated schema asynchronously', async () => {
19
+ return expect(
20
+ readAVSCAsync(path.join(__dirname, `../../fixtures/avsc/person.avsc`)),
21
+ ).resolves.toHaveProperty('name', 'Person')
22
+ })
23
+ })
@@ -0,0 +1,36 @@
1
+ import fs from 'fs'
2
+ import { promisify } from 'util'
3
+
4
+ import { RawAvroSchema } from '../@types'
5
+ import { ConfluentSchemaRegistryInvalidSchemaError } from '../errors'
6
+
7
+ const readFileAsync = promisify(fs.readFile)
8
+ const ENCODING = 'utf-8'
9
+
10
+ function isValidSchema(rawSchema: any): rawSchema is RawAvroSchema {
11
+ return (
12
+ 'name' in rawSchema &&
13
+ 'type' in rawSchema &&
14
+ rawSchema.type === 'record' &&
15
+ 'fields' in rawSchema
16
+ )
17
+ }
18
+
19
+ function validatedSchema(path: string, rawSchema: any): RawAvroSchema {
20
+ if (!isValidSchema(rawSchema)) {
21
+ throw new ConfluentSchemaRegistryInvalidSchemaError(
22
+ `${path} is not recognized as a valid AVSC file (expecting valid top-level name, type and fields attributes)`,
23
+ )
24
+ }
25
+ return rawSchema
26
+ }
27
+
28
+ export function readAVSC(path: string): RawAvroSchema {
29
+ const rawSchema = JSON.parse(fs.readFileSync(path, ENCODING))
30
+ return validatedSchema(path, rawSchema)
31
+ }
32
+
33
+ export async function readAVSCAsync(path: string): Promise<RawAvroSchema> {
34
+ const rawSchema = JSON.parse(await readFileAsync(path, ENCODING))
35
+ return validatedSchema(path, rawSchema)
36
+ }
@@ -0,0 +1,5 @@
1
+ export default (buffer: Buffer) => ({
2
+ magicByte: buffer.slice(0, 1),
3
+ registryId: buffer.slice(1, 5).readInt32BE(0),
4
+ payload: buffer.slice(5, buffer.length),
5
+ })
@@ -0,0 +1,10 @@
1
+ const DEFAULT_OFFSET = 0
2
+
3
+ export const MAGIC_BYTE = Buffer.alloc(1)
4
+
5
+ export const encode = (registryId: number, payload: Buffer) => {
6
+ const registryIdBuffer = Buffer.alloc(4)
7
+ registryIdBuffer.writeInt32BE(registryId, DEFAULT_OFFSET)
8
+
9
+ return Buffer.concat([MAGIC_BYTE, registryIdBuffer, payload])
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "*": ["src/*", "declarations/*"]
6
+ },
7
+ "outDir": "dist",
8
+ "lib": ["es2019", "dom"],
9
+ "module": "commonjs",
10
+ "moduleResolution": "node",
11
+ "sourceMap": true,
12
+ "strict": true,
13
+ "target": "es2019",
14
+ "noUnusedParameters": true,
15
+ "noUnusedLocals": true,
16
+ "esModuleInterop": true,
17
+ "declaration": true,
18
+ "resolveJsonModule": true
19
+ },
20
+ "include": ["./src/**/*.ts", "declarations/**/*.ts"],
21
+ "exclude": ["dist", "node_modules", "**/*.spec.ts"]
22
+ }