confluent-schema-registry 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }