@confluentinc/schemaregistry
Version:
Node.js client for Confluent Schema Registry
400 lines (399 loc) • 15.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MockClient = void 0;
const schemaregistry_client_1 = require("./schemaregistry-client");
const json_stringify_deterministic_1 = __importDefault(require("json-stringify-deterministic"));
const uuid_1 = require("uuid");
const rest_error_1 = require("./rest-error");
const serde_1 = require("./serde/serde");
class Counter {
constructor() {
this.count = 0;
}
currentValue() {
return this.count;
}
increment() {
this.count++;
return this.count;
}
}
const noSubject = "";
class MockClient {
constructor(config) {
this.clientConfig = config;
this.infoToSchemaCache = new Map();
this.idToSchemaCache = new Map();
this.guidToSchemaCache = new Map();
this.schemaToVersionCache = new Map();
this.configCache = new Map();
this.counter = new Counter();
}
config() {
return this.clientConfig;
}
async register(subject, schema, normalize = false) {
const metadata = await this.registerFullResponse(subject, schema, normalize);
if (!metadata) {
throw new rest_error_1.RestError("Failed to register schema", 422, 42200);
}
return metadata.id;
}
async registerFullResponse(subject, schema, normalize = false) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: (0, schemaregistry_client_1.minimize)(schema) });
const cacheEntry = this.infoToSchemaCache.get(cacheKey);
if (cacheEntry && !cacheEntry.softDeleted) {
return cacheEntry.metadata;
}
const schemaId = await this.getIDFromRegistry(subject, schema);
if (schemaId.id === -1) {
throw new rest_error_1.RestError("Failed to retrieve schema ID from registry", 422, 42200);
}
const metadata = { id: schemaId.id, guid: schemaId.guid, ...schema };
this.infoToSchemaCache.set(cacheKey, { metadata, softDeleted: false });
return metadata;
}
async getIDFromRegistry(subject, schema) {
let id = -1;
for (const [key, value] of this.idToSchemaCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && this.schemasEqual(value.info, schema)) {
id = parsedKey.id;
break;
}
}
let guid = "";
for (const [key, value] of this.guidToSchemaCache.entries()) {
if (this.schemasEqual(value.info, schema)) {
guid = key;
break;
}
}
await this.generateVersion(subject, schema);
if (id < 0) {
id = this.counter.increment();
const idCacheKey = (0, json_stringify_deterministic_1.default)({ subject, id });
this.idToSchemaCache.set(idCacheKey, { info: schema, softDeleted: false });
guid = (0, uuid_1.v4)();
this.guidToSchemaCache.set(guid, { info: schema, softDeleted: false });
}
return new serde_1.SchemaId("", id, guid);
}
async generateVersion(subject, schema) {
const versions = await this.allVersions(subject);
let newVersion;
if (versions.length === 0) {
newVersion = 1;
}
else {
newVersion = versions[versions.length - 1] + 1;
}
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: (0, schemaregistry_client_1.minimize)(schema) });
this.schemaToVersionCache.set(cacheKey, { version: newVersion, softDeleted: false });
}
async getBySubjectAndId(subject, id, format) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, id });
const cacheEntry = this.idToSchemaCache.get(cacheKey);
if (!cacheEntry || cacheEntry.softDeleted) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
return cacheEntry.info;
}
async getByGuid(guid, format) {
const cacheEntry = this.guidToSchemaCache.get(guid);
if (!cacheEntry || cacheEntry.softDeleted) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
return cacheEntry.info;
}
async getId(subject, schema) {
const metadata = await this.getIdFullResponse(subject, schema);
return metadata.id;
}
async getIdFullResponse(subject, schema) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: (0, schemaregistry_client_1.minimize)(schema) });
const cacheEntry = this.infoToSchemaCache.get(cacheKey);
if (!cacheEntry || cacheEntry.softDeleted) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
return cacheEntry.metadata;
}
async getLatestSchemaMetadata(subject, format) {
const version = await this.latestVersion(subject);
if (version === -1) {
throw new rest_error_1.RestError("No versions found for subject", 404, 40400);
}
return this.getSchemaMetadata(subject, version);
}
async getSchemaMetadata(subject, version, deleted = false, format) {
let json;
for (const [key, value] of this.schemaToVersionCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && value.version === version) {
json = parsedKey;
break;
}
}
if (!json) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
let id = -1;
for (const [key, value] of this.idToSchemaCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && value.info.schema === json.schema.schema) {
id = parsedKey.id;
break;
}
}
if (id === -1) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
let guid = "";
for (const [key, value] of this.guidToSchemaCache.entries()) {
if (value.info.schema === json.schema.schema) {
guid = key;
break;
}
}
if (guid === "") {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
return {
id,
guid,
version,
subject,
...json.schema,
};
}
async getLatestWithMetadata(subject, metadata, deleted = false, format) {
let metadataStr = '';
for (const key in metadata) {
const encodedKey = encodeURIComponent(key);
const encodedValue = encodeURIComponent(metadata[key]);
metadataStr += `&key=${encodedKey}&value=${encodedValue}`;
}
let results = [];
for (const [key, value] of this.schemaToVersionCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && (!value.softDeleted || deleted)) {
if (parsedKey.schema.metadata && this.isSubset(metadata, parsedKey.schema.metadata.properties)) {
results.push({
version: value.version,
subject,
...parsedKey.schema
});
}
}
}
if (results.length === 0) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
let latest = results[0];
results.forEach((result) => {
if (result.version > latest.version) {
latest = result;
}
});
let id = -1;
for (const [key, value] of this.idToSchemaCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && value.info.schema === latest.schema) {
id = parsedKey.id;
break;
}
}
if (id === -1) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
latest.id = id;
return latest;
}
isSubset(containee, container) {
for (const key in containee) {
if (containee[key] !== container[key]) {
return false;
}
}
return true;
}
async getAllVersions(subject) {
const results = await this.allVersions(subject);
if (results.length === 0) {
throw new rest_error_1.RestError("No versions found for subject", 404, 40400);
}
return results;
}
async allVersions(subject) {
const versions = [];
for (const [key, value] of this.schemaToVersionCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && !value.softDeleted) {
versions.push(value.version);
}
}
return versions;
}
async latestVersion(subject) {
const versions = await this.allVersions(subject);
if (versions.length === 0) {
return -1;
}
return versions[versions.length - 1];
}
async deleteVersion(cacheKey, version, permanent) {
if (permanent) {
this.schemaToVersionCache.delete(cacheKey);
}
else {
this.schemaToVersionCache.set(cacheKey, { version, softDeleted: true });
}
}
async deleteInfo(cacheKey, info, permanent) {
if (permanent) {
this.idToSchemaCache.delete(cacheKey);
}
else {
this.idToSchemaCache.set(cacheKey, { info, softDeleted: true });
}
}
async deleteMetadata(cacheKey, metadata, permanent) {
if (permanent) {
this.infoToSchemaCache.delete(cacheKey);
}
else {
this.infoToSchemaCache.set(cacheKey, { metadata, softDeleted: true });
}
}
async getVersion(subject, schema, normalize = false, deleted = false) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: (0, schemaregistry_client_1.minimize)(schema) });
const cacheEntry = this.schemaToVersionCache.get(cacheKey);
if (!cacheEntry || cacheEntry.softDeleted) {
throw new rest_error_1.RestError("Schema not found", 404, 40400);
}
return cacheEntry.version;
}
async getAllSubjects() {
const subjects = [];
for (const [key, value] of this.schemaToVersionCache.entries()) {
const parsedKey = JSON.parse(key);
if (!value.softDeleted && !subjects.includes(parsedKey.subject)) {
subjects.push(parsedKey.subject);
}
}
return subjects.sort();
}
async deleteSubject(subject, permanent = false) {
const deletedVersions = [];
for (const [key, value] of this.infoToSchemaCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && (permanent || !value.softDeleted)) {
await this.deleteMetadata(key, value.metadata, permanent);
}
}
for (const [key, value] of this.schemaToVersionCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && (permanent || !value.softDeleted)) {
await this.deleteVersion(key, value.version, permanent);
deletedVersions.push(value.version);
}
}
this.configCache.delete(subject);
if (permanent) {
for (const [key, value] of this.idToSchemaCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && (!value.softDeleted)) {
await this.deleteInfo(key, value.info, permanent);
}
}
}
return deletedVersions;
}
async deleteSubjectVersion(subject, version, permanent = false) {
for (const [key, value] of this.schemaToVersionCache.entries()) {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && value.version === version) {
await this.deleteVersion(key, version, permanent);
const cacheKeySchema = (0, json_stringify_deterministic_1.default)({ subject, schema: (0, schemaregistry_client_1.minimize)(parsedKey.schema) });
const cacheEntry = this.infoToSchemaCache.get(cacheKeySchema);
if (cacheEntry) {
await this.deleteMetadata(cacheKeySchema, cacheEntry.metadata, permanent);
}
if (permanent && cacheEntry) {
const cacheKeyInfo = (0, json_stringify_deterministic_1.default)({ subject, id: cacheEntry.metadata.id });
const cacheSchemaEntry = this.idToSchemaCache.get(cacheKeyInfo);
if (cacheSchemaEntry) {
await this.deleteInfo(cacheKeyInfo, cacheSchemaEntry.info, permanent);
}
}
}
}
return version;
}
async testSubjectCompatibility(subject, schema) {
throw new Error("Unsupported operation");
}
async testCompatibility(subject, version, schema) {
throw new Error("Unsupported operation");
}
async getCompatibility(subject) {
const cacheEntry = this.configCache.get(subject);
if (!cacheEntry) {
throw new rest_error_1.RestError("Subject not found", 404, 40400);
}
return cacheEntry.compatibilityLevel;
}
async updateCompatibility(subject, compatibility) {
this.configCache.set(subject, { compatibilityLevel: compatibility });
return compatibility;
}
async getDefaultCompatibility() {
const cacheEntry = this.configCache.get(noSubject);
if (!cacheEntry) {
throw new rest_error_1.RestError("Default compatibility not found", 404, 40400);
}
return cacheEntry.compatibilityLevel;
}
async updateDefaultCompatibility(compatibility) {
this.configCache.set(noSubject, { compatibilityLevel: compatibility });
return compatibility;
}
async getConfig(subject) {
const cacheEntry = this.configCache.get(subject);
if (!cacheEntry) {
throw new rest_error_1.RestError("Subject not found", 404, 40400);
}
return cacheEntry;
}
async updateConfig(subject, config) {
this.configCache.set(subject, config);
return config;
}
async getDefaultConfig() {
const cacheEntry = this.configCache.get(noSubject);
if (!cacheEntry) {
throw new rest_error_1.RestError("Default config not found", 404, 40400);
}
return cacheEntry;
}
async updateDefaultConfig(config) {
this.configCache.set(noSubject, config);
return config;
}
clearLatestCaches() {
return;
}
clearCaches() {
return;
}
async close() {
return;
}
schemasEqual(schema1, schema2) {
return (0, json_stringify_deterministic_1.default)(schema1) === (0, json_stringify_deterministic_1.default)(schema2);
}
}
exports.MockClient = MockClient;