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.
- package/.dockerignore +2 -0
- package/.prettierrc.js +8 -0
- package/CHANGELOG.md +166 -0
- package/Dockerfile +10 -0
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/bin/avdlToAVSC.sh +9 -0
- package/dist/@types.d.ts +93 -0
- package/dist/@types.js +10 -0
- package/dist/@types.js.map +1 -0
- package/dist/AvroHelper.d.ts +12 -0
- package/dist/AvroHelper.js +67 -0
- package/dist/AvroHelper.js.map +1 -0
- package/dist/JsonHelper.d.ts +7 -0
- package/dist/JsonHelper.js +20 -0
- package/dist/JsonHelper.js.map +1 -0
- package/dist/JsonSchema.d.ts +31 -0
- package/dist/JsonSchema.js +58 -0
- package/dist/JsonSchema.js.map +1 -0
- package/dist/ProtoHelper.d.ts +7 -0
- package/dist/ProtoHelper.js +23 -0
- package/dist/ProtoHelper.js.map +1 -0
- package/dist/ProtoSchema.d.ts +14 -0
- package/dist/ProtoSchema.js +66 -0
- package/dist/ProtoSchema.js.map +1 -0
- package/dist/SchemaRegistry.d.ts +48 -0
- package/dist/SchemaRegistry.js +250 -0
- package/dist/SchemaRegistry.js.map +1 -0
- package/dist/api/index.d.ts +43 -0
- package/dist/api/index.js +90 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
- package/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
- package/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
- package/dist/api/middleware/errorMiddleware.d.ts +3 -0
- package/dist/api/middleware/errorMiddleware.js +20 -0
- package/dist/api/middleware/errorMiddleware.js.map +1 -0
- package/dist/api/middleware/userAgent.d.ts +3 -0
- package/dist/api/middleware/userAgent.js +18 -0
- package/dist/api/middleware/userAgent.js.map +1 -0
- package/dist/cache.d.ts +20 -0
- package/dist/cache.js +24 -0
- package/dist/cache.js.map +1 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +15 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/schemaTypeResolver.d.ts +4 -0
- package/dist/schemaTypeResolver.js +80 -0
- package/dist/schemaTypeResolver.js.map +1 -0
- package/dist/utils/avdlToAVSC.d.ts +2 -0
- package/dist/utils/avdlToAVSC.js +85 -0
- package/dist/utils/avdlToAVSC.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/readAVSC.d.ts +3 -0
- package/dist/utils/readAVSC.js +33 -0
- package/dist/utils/readAVSC.js.map +1 -0
- package/dist/wireDecoder.d.ts +7 -0
- package/dist/wireDecoder.js +8 -0
- package/dist/wireDecoder.js.map +1 -0
- package/dist/wireEncoder.d.ts +3 -0
- package/dist/wireEncoder.js +10 -0
- package/dist/wireEncoder.js.map +1 -0
- package/dockest-error.json +11 -0
- package/dockest.ts +30 -0
- package/jest.setup.ts +60 -0
- package/package.json +56 -0
- package/release/CHANGELOG.md +166 -0
- package/release/LICENSE +21 -0
- package/release/README.md +44 -0
- package/release/dist/@types.d.ts +93 -0
- package/release/dist/@types.js +10 -0
- package/release/dist/@types.js.map +1 -0
- package/release/dist/AvroHelper.d.ts +12 -0
- package/release/dist/AvroHelper.js +67 -0
- package/release/dist/AvroHelper.js.map +1 -0
- package/release/dist/JsonHelper.d.ts +7 -0
- package/release/dist/JsonHelper.js +20 -0
- package/release/dist/JsonHelper.js.map +1 -0
- package/release/dist/JsonSchema.d.ts +31 -0
- package/release/dist/JsonSchema.js +58 -0
- package/release/dist/JsonSchema.js.map +1 -0
- package/release/dist/ProtoHelper.d.ts +7 -0
- package/release/dist/ProtoHelper.js +23 -0
- package/release/dist/ProtoHelper.js.map +1 -0
- package/release/dist/ProtoSchema.d.ts +14 -0
- package/release/dist/ProtoSchema.js +66 -0
- package/release/dist/ProtoSchema.js.map +1 -0
- package/release/dist/SchemaRegistry.d.ts +48 -0
- package/release/dist/SchemaRegistry.js +250 -0
- package/release/dist/SchemaRegistry.js.map +1 -0
- package/release/dist/api/index.d.ts +43 -0
- package/release/dist/api/index.js +90 -0
- package/release/dist/api/index.js.map +1 -0
- package/release/dist/api/middleware/confluentEncoderMiddleware.d.ts +3 -0
- package/release/dist/api/middleware/confluentEncoderMiddleware.js +31 -0
- package/release/dist/api/middleware/confluentEncoderMiddleware.js.map +1 -0
- package/release/dist/api/middleware/errorMiddleware.d.ts +3 -0
- package/release/dist/api/middleware/errorMiddleware.js +20 -0
- package/release/dist/api/middleware/errorMiddleware.js.map +1 -0
- package/release/dist/api/middleware/userAgent.d.ts +3 -0
- package/release/dist/api/middleware/userAgent.js +18 -0
- package/release/dist/api/middleware/userAgent.js.map +1 -0
- package/release/dist/cache.d.ts +20 -0
- package/release/dist/cache.js +24 -0
- package/release/dist/cache.js.map +1 -0
- package/release/dist/constants.d.ts +11 -0
- package/release/dist/constants.js +15 -0
- package/release/dist/constants.js.map +1 -0
- package/release/dist/errors.d.ts +14 -0
- package/release/dist/errors.js +26 -0
- package/release/dist/errors.js.map +1 -0
- package/release/dist/index.d.ts +4 -0
- package/release/dist/index.js +13 -0
- package/release/dist/index.js.map +1 -0
- package/release/dist/schemaTypeResolver.d.ts +4 -0
- package/release/dist/schemaTypeResolver.js +80 -0
- package/release/dist/schemaTypeResolver.js.map +1 -0
- package/release/dist/utils/avdlToAVSC.d.ts +2 -0
- package/release/dist/utils/avdlToAVSC.js +85 -0
- package/release/dist/utils/avdlToAVSC.js.map +1 -0
- package/release/dist/utils/index.d.ts +2 -0
- package/release/dist/utils/index.js +9 -0
- package/release/dist/utils/index.js.map +1 -0
- package/release/dist/utils/readAVSC.d.ts +3 -0
- package/release/dist/utils/readAVSC.js +33 -0
- package/release/dist/utils/readAVSC.js.map +1 -0
- package/release/dist/wireDecoder.d.ts +7 -0
- package/release/dist/wireDecoder.js +8 -0
- package/release/dist/wireDecoder.js.map +1 -0
- package/release/dist/wireEncoder.d.ts +3 -0
- package/release/dist/wireEncoder.js +10 -0
- package/release/dist/wireEncoder.js.map +1 -0
- package/release/package.json +56 -0
- package/src/@types.ts +105 -0
- package/src/AvroHelper.ts +91 -0
- package/src/JsonHelper.ts +35 -0
- package/src/JsonSchema.ts +80 -0
- package/src/ProtoHelper.ts +38 -0
- package/src/ProtoSchema.ts +80 -0
- package/src/SchemaRegistry.avro.spec.ts +558 -0
- package/src/SchemaRegistry.json.spec.ts +364 -0
- package/src/SchemaRegistry.newApi.spec.ts +622 -0
- package/src/SchemaRegistry.protobuf.spec.ts +372 -0
- package/src/SchemaRegistry.spec.ts +252 -0
- package/src/SchemaRegistry.ts +387 -0
- package/src/api/index.spec.ts +23 -0
- package/src/api/index.ts +121 -0
- package/src/api/middleware/confluentEncoderMiddleware.ts +36 -0
- package/src/api/middleware/errorMiddleware.spec.ts +67 -0
- package/src/api/middleware/errorMiddleware.ts +37 -0
- package/src/api/middleware/userAgent.spec.ts +53 -0
- package/src/api/middleware/userAgent.ts +19 -0
- package/src/cache.ts +34 -0
- package/src/constants.ts +13 -0
- package/src/errors.ts +26 -0
- package/src/index.ts +4 -0
- package/src/schemaTypeResolver.ts +101 -0
- package/src/utils/avdlToAVSC.spec.ts +79 -0
- package/src/utils/avdlToAVSC.ts +106 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/readAVSC.spec.ts +23 -0
- package/src/utils/readAVSC.ts +36 -0
- package/src/wireDecoder.ts +5 -0
- package/src/wireEncoder.ts +10 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import SchemaRegistry, { RegisteredSchema } from './SchemaRegistry'
|
|
2
|
+
import API from './api'
|
|
3
|
+
import { ProtoConfluentSchema, SchemaType } from './@types'
|
|
4
|
+
|
|
5
|
+
const REGISTRY_HOST = 'http://localhost:8982'
|
|
6
|
+
const schemaRegistryAPIClientArgs = { host: REGISTRY_HOST }
|
|
7
|
+
const schemaRegistryArgs = { host: REGISTRY_HOST }
|
|
8
|
+
|
|
9
|
+
const TestSchemas = {
|
|
10
|
+
FirstLevelSchema: {
|
|
11
|
+
type: SchemaType.PROTOBUF,
|
|
12
|
+
schema: `
|
|
13
|
+
syntax = "proto3";
|
|
14
|
+
package test;
|
|
15
|
+
import "test/second_level_A.proto";
|
|
16
|
+
import "test/second_level_B.proto";
|
|
17
|
+
|
|
18
|
+
message FirstLevel {
|
|
19
|
+
int32 id1 = 1;
|
|
20
|
+
SecondLevelA level1a = 2;
|
|
21
|
+
SecondLevelB level1b = 3;
|
|
22
|
+
}`,
|
|
23
|
+
references: [
|
|
24
|
+
{
|
|
25
|
+
name: 'test/second_level_A.proto',
|
|
26
|
+
subject: 'Proto:SecondLevelA',
|
|
27
|
+
version: undefined,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'test/second_level_B.proto',
|
|
31
|
+
subject: 'Proto:SecondLevelB',
|
|
32
|
+
version: undefined,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
} as ProtoConfluentSchema,
|
|
36
|
+
|
|
37
|
+
SecondLevelASchema: {
|
|
38
|
+
type: SchemaType.PROTOBUF,
|
|
39
|
+
schema: `
|
|
40
|
+
syntax = "proto3";
|
|
41
|
+
package test;
|
|
42
|
+
import "test/third_level.proto";
|
|
43
|
+
|
|
44
|
+
message SecondLevelA {
|
|
45
|
+
int32 id2a = 1;
|
|
46
|
+
ThirdLevel level2a = 2;
|
|
47
|
+
}`,
|
|
48
|
+
references: [
|
|
49
|
+
{
|
|
50
|
+
name: 'test/third_level.proto',
|
|
51
|
+
subject: 'Proto:ThirdLevel',
|
|
52
|
+
version: undefined,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
} as ProtoConfluentSchema,
|
|
56
|
+
|
|
57
|
+
SecondLevelBSchema: {
|
|
58
|
+
type: SchemaType.PROTOBUF,
|
|
59
|
+
schema: `
|
|
60
|
+
syntax = "proto3";
|
|
61
|
+
package test;
|
|
62
|
+
import "test/third_level.proto";
|
|
63
|
+
|
|
64
|
+
message SecondLevelB {
|
|
65
|
+
int32 id2b = 1;
|
|
66
|
+
ThirdLevel level2b = 2;
|
|
67
|
+
}`,
|
|
68
|
+
references: [
|
|
69
|
+
{
|
|
70
|
+
name: 'test/third_level.proto',
|
|
71
|
+
subject: 'Proto:ThirdLevel',
|
|
72
|
+
version: undefined,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
} as ProtoConfluentSchema,
|
|
76
|
+
|
|
77
|
+
ThirdLevelSchema: {
|
|
78
|
+
type: SchemaType.PROTOBUF,
|
|
79
|
+
schema: `
|
|
80
|
+
syntax = "proto3";
|
|
81
|
+
package test;
|
|
82
|
+
|
|
83
|
+
message ThirdLevel {
|
|
84
|
+
int32 id3 = 1;
|
|
85
|
+
}`,
|
|
86
|
+
} as ProtoConfluentSchema,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function apiResponse(result) {
|
|
90
|
+
return JSON.parse(result.responseData)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe('SchemaRegistry', () => {
|
|
94
|
+
let schemaRegistry: SchemaRegistry
|
|
95
|
+
let registeredSchema: RegisteredSchema
|
|
96
|
+
let api
|
|
97
|
+
|
|
98
|
+
beforeEach(async () => {
|
|
99
|
+
api = API(schemaRegistryAPIClientArgs)
|
|
100
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('when register', () => {
|
|
104
|
+
describe('when no reference', () => {
|
|
105
|
+
beforeEach(async () => {
|
|
106
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
|
107
|
+
subject: 'Proto:ThirdLevel',
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
it('should return schema id', async () => {
|
|
111
|
+
expect(registeredSchema.id).toEqual(expect.any(Number))
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should be able to encode/decode', async () => {
|
|
115
|
+
const obj = { id3: 3 }
|
|
116
|
+
|
|
117
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
|
118
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
|
119
|
+
|
|
120
|
+
expect(resultObj).toEqual(obj)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('with reference', () => {
|
|
125
|
+
let schemaId
|
|
126
|
+
let referenceSchema
|
|
127
|
+
|
|
128
|
+
beforeEach(async () => {
|
|
129
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
|
130
|
+
subject: 'Proto:ThirdLevel',
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
|
134
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
|
135
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
|
136
|
+
subject: 'Proto:SecondLevelA',
|
|
137
|
+
})
|
|
138
|
+
schemaId = registeredSchema.id
|
|
139
|
+
|
|
140
|
+
const schemaRaw = apiResponse(await api.Schema.find({ id: schemaId }))
|
|
141
|
+
referenceSchema = schemaRaw.references[0].subject
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should return schema id', async () => {
|
|
145
|
+
expect(schemaId).toEqual(expect.any(Number))
|
|
146
|
+
})
|
|
147
|
+
it('should create a schema with reference', async () => {
|
|
148
|
+
expect(referenceSchema).toEqual('Proto:ThirdLevel')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should be able to encode/decode', async () => {
|
|
152
|
+
const obj = { id2a: 2, level2a: { id3: 3 } }
|
|
153
|
+
|
|
154
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
|
155
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
|
156
|
+
|
|
157
|
+
expect(resultObj).toEqual(obj)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('with multiple reference', () => {
|
|
162
|
+
beforeEach(async () => {
|
|
163
|
+
let latest
|
|
164
|
+
|
|
165
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
|
166
|
+
subject: 'Proto:ThirdLevel',
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
|
170
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
|
171
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
|
172
|
+
subject: 'Proto:SecondLevelA',
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
|
176
|
+
TestSchemas.SecondLevelBSchema.references[0].version = latest.version
|
|
177
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelBSchema, {
|
|
178
|
+
subject: 'Proto:SecondLevelB',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelA' }))
|
|
182
|
+
TestSchemas.FirstLevelSchema.references[0].version = latest.version
|
|
183
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelB' }))
|
|
184
|
+
TestSchemas.FirstLevelSchema.references[1].version = latest.version
|
|
185
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.FirstLevelSchema, {
|
|
186
|
+
subject: 'Proto:FirstLevel',
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should be able to encode/decode', async () => {
|
|
191
|
+
const obj = {
|
|
192
|
+
id1: 1,
|
|
193
|
+
level1a: { id2a: 2, level2a: { id3: 3 } },
|
|
194
|
+
level1b: { id2b: 4, level2b: { id3: 5 } },
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
|
198
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
|
199
|
+
|
|
200
|
+
expect(resultObj).toEqual(obj)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should be able to encode/decode independent', async () => {
|
|
204
|
+
const obj = {
|
|
205
|
+
id1: 1,
|
|
206
|
+
level1a: { id2a: 2, level2a: { id3: 3 } },
|
|
207
|
+
level1b: { id2b: 4, level2b: { id3: 5 } },
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
|
211
|
+
const buffer = await schemaRegistry.encode(registeredSchema.id, obj)
|
|
212
|
+
|
|
213
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
|
214
|
+
const resultObj = await schemaRegistry.decode(buffer)
|
|
215
|
+
|
|
216
|
+
expect(resultObj).toEqual(obj)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('_getSchema', () => {
|
|
222
|
+
let schema
|
|
223
|
+
|
|
224
|
+
describe('no references', () => {
|
|
225
|
+
beforeEach(async () => {
|
|
226
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
|
227
|
+
subject: 'Proto:ThirdLevel',
|
|
228
|
+
})
|
|
229
|
+
;({ schema } = await schemaRegistry['_getSchema'](registeredSchema.id))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should return schema that match message', async () => {
|
|
233
|
+
expect(schema.message.name).toEqual('ThirdLevel')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should be able to encode/decode', async () => {
|
|
237
|
+
const obj = { id3: 3 }
|
|
238
|
+
|
|
239
|
+
const buffer = await schema.toBuffer(obj)
|
|
240
|
+
const resultObj = await schema.fromBuffer(buffer)
|
|
241
|
+
|
|
242
|
+
expect(resultObj).toEqual(obj)
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
describe('with references', () => {
|
|
247
|
+
beforeEach(async () => {
|
|
248
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, { subject: 'Proto:ThirdLevel' })
|
|
249
|
+
|
|
250
|
+
const latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
|
251
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
|
252
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
|
253
|
+
subject: 'Proto:SecondLevelA',
|
|
254
|
+
})
|
|
255
|
+
;({ schema } = await schemaRegistry['_getSchema'](registeredSchema.id))
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should return schema that match message', async () => {
|
|
259
|
+
expect(schema.message.name).toEqual('SecondLevelA')
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('should be able to encode/decode', async () => {
|
|
263
|
+
const obj = { id2a: 2, level2a: { id3: 3 } }
|
|
264
|
+
|
|
265
|
+
const buffer = await schema.toBuffer(obj)
|
|
266
|
+
const resultObj = await schema.fromBuffer(buffer)
|
|
267
|
+
|
|
268
|
+
expect(resultObj).toEqual(obj)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
describe('with multi references', () => {
|
|
273
|
+
beforeEach(async () => {
|
|
274
|
+
let latest
|
|
275
|
+
|
|
276
|
+
await schemaRegistry.register(TestSchemas.ThirdLevelSchema, {
|
|
277
|
+
subject: 'Proto:ThirdLevel',
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
|
281
|
+
TestSchemas.SecondLevelASchema.references[0].version = latest.version
|
|
282
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelASchema, {
|
|
283
|
+
subject: 'Proto:SecondLevelA',
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:ThirdLevel' }))
|
|
287
|
+
TestSchemas.SecondLevelBSchema.references[0].version = latest.version
|
|
288
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.SecondLevelBSchema, {
|
|
289
|
+
subject: 'Proto:SecondLevelB',
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelA' }))
|
|
293
|
+
TestSchemas.FirstLevelSchema.references[0].version = latest.version
|
|
294
|
+
latest = apiResponse(await api.Subject.latestVersion({ subject: 'Proto:SecondLevelB' }))
|
|
295
|
+
TestSchemas.FirstLevelSchema.references[1].version = latest.version
|
|
296
|
+
registeredSchema = await schemaRegistry.register(TestSchemas.FirstLevelSchema, {
|
|
297
|
+
subject: 'Proto:FirstLevel',
|
|
298
|
+
})
|
|
299
|
+
;({ schema } = await schemaRegistry['_getSchema'](registeredSchema.id))
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should return schema that match message', async () => {
|
|
303
|
+
expect(schema.message.name).toEqual('FirstLevel')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should be able to encode/decode', async () => {
|
|
307
|
+
const obj = {
|
|
308
|
+
id1: 1,
|
|
309
|
+
level1a: { id2a: 2, level2a: { id3: 3 } },
|
|
310
|
+
level1b: { id2b: 4, level2b: { id3: 5 } },
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const buffer = await schema.toBuffer(obj)
|
|
314
|
+
const resultObj = await schema.fromBuffer(buffer)
|
|
315
|
+
|
|
316
|
+
expect(resultObj).toEqual(obj)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
describe('when document example', () => {
|
|
322
|
+
it('should encode/decode', async () => {
|
|
323
|
+
const schemaA = `
|
|
324
|
+
syntax = "proto3";
|
|
325
|
+
package test;
|
|
326
|
+
import "test/B.proto";
|
|
327
|
+
|
|
328
|
+
message A {
|
|
329
|
+
int32 id = 1;
|
|
330
|
+
B b = 2;
|
|
331
|
+
}`
|
|
332
|
+
|
|
333
|
+
const schemaB = `
|
|
334
|
+
syntax = "proto3";
|
|
335
|
+
package test;
|
|
336
|
+
|
|
337
|
+
message B {
|
|
338
|
+
int32 id = 1;
|
|
339
|
+
}`
|
|
340
|
+
|
|
341
|
+
await schemaRegistry.register(
|
|
342
|
+
{ type: SchemaType.PROTOBUF, schema: schemaB },
|
|
343
|
+
{ subject: 'Proto:B' },
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
const response = await schemaRegistry.api.Subject.latestVersion({ subject: 'Proto:B' })
|
|
347
|
+
const { version } = JSON.parse(response.responseData)
|
|
348
|
+
|
|
349
|
+
const { id } = await schemaRegistry.register(
|
|
350
|
+
{
|
|
351
|
+
type: SchemaType.PROTOBUF,
|
|
352
|
+
schema: schemaA,
|
|
353
|
+
references: [
|
|
354
|
+
{
|
|
355
|
+
name: 'test/B.proto',
|
|
356
|
+
subject: 'Proto:B',
|
|
357
|
+
version,
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
{ subject: 'Proto:A' },
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const obj = { id: 1, b: { id: 2 } }
|
|
365
|
+
|
|
366
|
+
const buffer = await schemaRegistry.encode(id, obj)
|
|
367
|
+
const decodedObj = await schemaRegistry.decode(buffer)
|
|
368
|
+
|
|
369
|
+
expect(decodedObj).toEqual(obj)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
})
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { v4 as uuid } from 'uuid'
|
|
3
|
+
|
|
4
|
+
import { readAVSC } from './utils'
|
|
5
|
+
import SchemaRegistry from './SchemaRegistry'
|
|
6
|
+
import API, { SchemaRegistryAPIClient } from './api'
|
|
7
|
+
import { COMPATIBILITY, DEFAULT_API_CLIENT_ID } from './constants'
|
|
8
|
+
import encodedAnotherPersonV2 from '../fixtures/avro/encodedAnotherPersonV2'
|
|
9
|
+
import wrongMagicByte from '../fixtures/wrongMagicByte'
|
|
10
|
+
import { RawAvroSchema } from './@types'
|
|
11
|
+
|
|
12
|
+
const REGISTRY_HOST = 'http://localhost:8982'
|
|
13
|
+
const schemaRegistryAPIClientArgs = { host: REGISTRY_HOST }
|
|
14
|
+
const schemaRegistryArgs = { host: REGISTRY_HOST }
|
|
15
|
+
|
|
16
|
+
const personSchema = readAVSC(path.join(__dirname, '../fixtures/avsc/person.avsc'))
|
|
17
|
+
const payload = { fullName: 'John Doe' } // eslint-disable-line @typescript-eslint/camelcase
|
|
18
|
+
|
|
19
|
+
describe('SchemaRegistry - old AVRO api', () => {
|
|
20
|
+
let schemaRegistry: SchemaRegistry
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
schemaRegistry = new SchemaRegistry(schemaRegistryArgs)
|
|
24
|
+
await schemaRegistry.register(personSchema)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('#register', () => {
|
|
28
|
+
let namespace: string, Schema: RawAvroSchema, subject: string, api: SchemaRegistryAPIClient
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
api = API(schemaRegistryAPIClientArgs)
|
|
32
|
+
namespace = `N${uuid().replace(/-/g, '_')}`
|
|
33
|
+
subject = `${namespace}.RandomTest`
|
|
34
|
+
Schema = {
|
|
35
|
+
namespace,
|
|
36
|
+
type: 'record',
|
|
37
|
+
name: 'RandomTest',
|
|
38
|
+
fields: [{ type: 'string', name: 'fullName' }],
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('uploads the new schema', async () => {
|
|
43
|
+
await expect(api.Subject.latestVersion({ subject })).rejects.toHaveProperty(
|
|
44
|
+
'message',
|
|
45
|
+
`${DEFAULT_API_CLIENT_ID} - Subject '${namespace}.${Schema.name}' not found.`,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
await expect(schemaRegistry.register(Schema)).resolves.toEqual({ id: expect.any(Number) })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('automatically cache the id and schema', async () => {
|
|
52
|
+
const { id } = await schemaRegistry.register(Schema)
|
|
53
|
+
|
|
54
|
+
expect(schemaRegistry.cache.getSchema(id)).toBeTruthy()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('fetch and validate the latest schema id after registering a new schema', async () => {
|
|
58
|
+
const { id } = await schemaRegistry.register(Schema)
|
|
59
|
+
const latestSchemaId = await schemaRegistry.getLatestSchemaId(subject)
|
|
60
|
+
|
|
61
|
+
expect(id).toBe(latestSchemaId)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('set the default compatibility to BACKWARD', async () => {
|
|
65
|
+
await schemaRegistry.register(Schema)
|
|
66
|
+
const response = await api.Subject.config({ subject })
|
|
67
|
+
expect(response.data()).toEqual({ compatibilityLevel: COMPATIBILITY.BACKWARD })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('sets the compatibility according to param', async () => {
|
|
71
|
+
await schemaRegistry.register(Schema, { compatibility: COMPATIBILITY.NONE })
|
|
72
|
+
const response = await api.Subject.config({ subject })
|
|
73
|
+
expect(response.data()).toEqual({ compatibilityLevel: COMPATIBILITY.NONE })
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('throws an error when schema does not have a name', async () => {
|
|
77
|
+
delete Schema.name
|
|
78
|
+
await expect(schemaRegistry.register(Schema)).rejects.toHaveProperty(
|
|
79
|
+
'message',
|
|
80
|
+
'Invalid name: undefined',
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('throws an error when schema does not have a namespace', async () => {
|
|
85
|
+
delete Schema.namespace
|
|
86
|
+
await expect(schemaRegistry.register(Schema)).rejects.toHaveProperty(
|
|
87
|
+
'message',
|
|
88
|
+
'Invalid namespace: undefined',
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('accepts schema without a namespace when subject is specified', async () => {
|
|
93
|
+
delete Schema.namespace
|
|
94
|
+
const nonNamespaced = readAVSC(path.join(__dirname, '../fixtures/avsc/non_namespaced.avsc'))
|
|
95
|
+
await expect(schemaRegistry.register(nonNamespaced, { subject })).resolves.toEqual({
|
|
96
|
+
id: expect.any(Number),
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('throws an error when the configured compatibility is different than defined in the client', async () => {
|
|
101
|
+
await schemaRegistry.register(Schema)
|
|
102
|
+
await api.Subject.updateConfig({ subject, body: { compatibility: COMPATIBILITY.FULL } })
|
|
103
|
+
await expect(schemaRegistry.register(Schema)).rejects.toHaveProperty(
|
|
104
|
+
'message',
|
|
105
|
+
'Compatibility does not match the configuration (BACKWARD != FULL)',
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('#encode', () => {
|
|
111
|
+
beforeEach(async () => {
|
|
112
|
+
await schemaRegistry.register(personSchema)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('throws an error if registryId is empty', async () => {
|
|
116
|
+
await expect(schemaRegistry.encode(undefined, payload)).rejects.toHaveProperty(
|
|
117
|
+
'message',
|
|
118
|
+
'Invalid registryId: undefined',
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('encodes using a defined registryId', async () => {
|
|
123
|
+
const SchemaV1 = Object.assign({}, personSchema, {
|
|
124
|
+
name: 'AnotherPerson',
|
|
125
|
+
fields: [{ type: 'string', name: 'fullName' }],
|
|
126
|
+
})
|
|
127
|
+
const SchemaV2 = Object.assign({}, SchemaV1, {
|
|
128
|
+
fields: [
|
|
129
|
+
{ type: 'string', name: 'fullName' },
|
|
130
|
+
{ type: 'string', name: 'city', default: 'Stockholm' },
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const schema1 = await schemaRegistry.register(SchemaV1)
|
|
135
|
+
const schema2 = await schemaRegistry.register(SchemaV2)
|
|
136
|
+
expect(schema2.id).not.toEqual(schema1.id)
|
|
137
|
+
|
|
138
|
+
const data = await schemaRegistry.encode(schema2.id, payload)
|
|
139
|
+
expect(data).toMatchConfluentEncodedPayload({
|
|
140
|
+
registryId: schema2.id,
|
|
141
|
+
payload: Buffer.from(encodedAnotherPersonV2),
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('#decode', () => {
|
|
147
|
+
let registryId: number
|
|
148
|
+
|
|
149
|
+
beforeEach(async () => {
|
|
150
|
+
registryId = (await schemaRegistry.register(personSchema)).id
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('decodes data', async () => {
|
|
154
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
|
155
|
+
const data = await schemaRegistry.decode(buffer)
|
|
156
|
+
|
|
157
|
+
expect(data).toEqual(payload)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('throws an error if the magic byte is not supported', async () => {
|
|
161
|
+
const buffer = Buffer.from(wrongMagicByte)
|
|
162
|
+
await expect(schemaRegistry.decode(buffer)).rejects.toHaveProperty(
|
|
163
|
+
'message',
|
|
164
|
+
'Message encoded with magic byte {"type":"Buffer","data":[48]}, expected {"type":"Buffer","data":[0]}',
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('caches the schema', async () => {
|
|
169
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
|
170
|
+
|
|
171
|
+
schemaRegistry.cache.clear()
|
|
172
|
+
await schemaRegistry.decode(buffer)
|
|
173
|
+
|
|
174
|
+
expect(schemaRegistry.cache.getSchema(registryId)).toBeTruthy()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('creates a single origin request for a schema cache-miss', async () => {
|
|
178
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
|
179
|
+
|
|
180
|
+
schemaRegistry.cache.clear()
|
|
181
|
+
|
|
182
|
+
const spy = jest.spyOn((schemaRegistry as any).api.Schema, 'find')
|
|
183
|
+
|
|
184
|
+
await Promise.all([
|
|
185
|
+
schemaRegistry.decode(buffer),
|
|
186
|
+
schemaRegistry.decode(buffer),
|
|
187
|
+
schemaRegistry.decode(buffer),
|
|
188
|
+
])
|
|
189
|
+
|
|
190
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('when the cache is populated', () => {
|
|
194
|
+
it('uses the cache data', async () => {
|
|
195
|
+
const buffer = Buffer.from(await schemaRegistry.encode(registryId, payload))
|
|
196
|
+
expect(schemaRegistry.cache.getSchema(registryId)).toBeTruthy()
|
|
197
|
+
|
|
198
|
+
jest.spyOn(schemaRegistry.cache, 'setSchema')
|
|
199
|
+
await schemaRegistry.decode(buffer)
|
|
200
|
+
|
|
201
|
+
expect(schemaRegistry.cache.setSchema).not.toHaveBeenCalled()
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('#getRegistryIdBySchema', () => {
|
|
207
|
+
let namespace: string, Schema: RawAvroSchema, subject: string
|
|
208
|
+
|
|
209
|
+
beforeEach(() => {
|
|
210
|
+
namespace = `N${uuid().replace(/-/g, '_')}`
|
|
211
|
+
subject = `${namespace}.RandomTest`
|
|
212
|
+
Schema = JSON.parse(`
|
|
213
|
+
{
|
|
214
|
+
"type": "record",
|
|
215
|
+
"name": "RandomTest",
|
|
216
|
+
"namespace": "${namespace}",
|
|
217
|
+
"fields": [{ "type": "string", "name": "fullName" }]
|
|
218
|
+
}
|
|
219
|
+
`)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('returns the registry id if the schema has already been registered under that subject', async () => {
|
|
223
|
+
const { id } = await schemaRegistry.register(Schema, { subject })
|
|
224
|
+
|
|
225
|
+
await expect(schemaRegistry.getRegistryIdBySchema(subject, Schema)).resolves.toEqual(id)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('throws an error if the subject does not exist', async () => {
|
|
229
|
+
await expect(schemaRegistry.getRegistryIdBySchema(subject, Schema)).rejects.toHaveProperty(
|
|
230
|
+
'message',
|
|
231
|
+
`Confluent_Schema_Registry - Subject '${namespace}.${Schema.name}' not found.`,
|
|
232
|
+
)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('throws an error if the schema has not been registered under that subject', async () => {
|
|
236
|
+
const otherSchema = JSON.parse(`
|
|
237
|
+
{
|
|
238
|
+
"type": "record",
|
|
239
|
+
"name": "RandomTest",
|
|
240
|
+
"namespace": "${namespace}",
|
|
241
|
+
"fields": [{ "type": "string", "name": "notFullName" }]
|
|
242
|
+
}
|
|
243
|
+
`)
|
|
244
|
+
await schemaRegistry.register(otherSchema, { subject })
|
|
245
|
+
|
|
246
|
+
await expect(schemaRegistry.getRegistryIdBySchema(subject, Schema)).rejects.toHaveProperty(
|
|
247
|
+
'message',
|
|
248
|
+
'Confluent_Schema_Registry - Schema not found',
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
})
|