-
Notifications
You must be signed in to change notification settings - Fork 23
Support RecordNameStrategy for SR client #423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
fangnx
wants to merge
2
commits into
master
Choose a base branch
from
recordnamestrategy
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,8 @@ import {RuleRegistry} from "./rule-registry"; | |||||
| import {ClientConfig} from "../rest-service"; | ||||||
| import {BufferWrapper, MAX_VARINT_LEN_64} from "./buffer-wrapper"; | ||||||
| import {IHeaders} from "../../types/kafkajs"; | ||||||
| import {fromBinary} from "@bufbuild/protobuf"; | ||||||
| import {FileDescriptorProtoSchema} from "@bufbuild/protobuf/wkt"; | ||||||
|
|
||||||
| export enum SerdeType { | ||||||
| KEY = 'KEY', | ||||||
|
|
@@ -662,6 +664,112 @@ export type SubjectNameStrategyFunc = ( | |||||
| schema?: SchemaInfo, | ||||||
| ) => string | ||||||
|
|
||||||
| /** | ||||||
| * Extract record name from an Avro schema | ||||||
| * @param schemaString - the Avro schema string | ||||||
| * @returns the fully qualified record name {namespace}.{name} or {name} if namespace is not present | ||||||
| * @throws SerializationError if schema is invalid or name is missing | ||||||
| */ | ||||||
| function getAvroRecordName(schemaString: string): string { | ||||||
| try { | ||||||
| const avroSchema = JSON.parse(schemaString) | ||||||
| if (!avroSchema.name) { | ||||||
| throw new SerializationError('Avro schema is missing required "name" field') | ||||||
| } | ||||||
| // Return fully qualified name if namespace exists | ||||||
| if (avroSchema.namespace) { | ||||||
| return `${avroSchema.namespace}.${avroSchema.name}` | ||||||
| } | ||||||
| return avroSchema.name | ||||||
| } catch (e) { | ||||||
| if (e instanceof SerializationError) { | ||||||
| throw e | ||||||
| } | ||||||
| throw new SerializationError(`Invalid Avro schema: ${e instanceof Error ? e.message : String(e)}`) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Extract record name from a Protobuf schema | ||||||
| * @param schemaString - the Protobuf schema string (base64-encoded) | ||||||
| * @returns the fully qualified record name {package}.{messageName}, or {messageName} if package is not present | ||||||
| * @throws SerializationError if schema is invalid or has no message types | ||||||
| */ | ||||||
| function getProtobufRecordName(schemaString: string): string { | ||||||
| try { | ||||||
| // Protobuf schemas are stored as base64-encoded FileDescriptorProto | ||||||
| const fileDesc = fromBinary(FileDescriptorProtoSchema, Buffer.from(schemaString, 'base64')) | ||||||
|
|
||||||
| // Get first message name | ||||||
| if (!fileDesc.messageType || fileDesc.messageType.length === 0) { | ||||||
| throw new SerializationError('Protobuf schema has no message types defined') | ||||||
| } | ||||||
|
|
||||||
| const messageName = fileDesc.messageType[0].name | ||||||
| if (!messageName) { | ||||||
| throw new SerializationError('Protobuf message type is missing "name" field') | ||||||
| } | ||||||
|
|
||||||
| // Get package name | ||||||
| const packageName = fileDesc.package || null | ||||||
|
|
||||||
| // Return fully qualified name if package exists | ||||||
| return packageName ? `${packageName}.${messageName}` : messageName | ||||||
| } catch (e) { | ||||||
| if (e instanceof SerializationError) { | ||||||
| throw e | ||||||
| } | ||||||
| throw new SerializationError(`Invalid Protobuf schema: ${e instanceof Error ? e.message : String(e)}`) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Extract record name from a JSON schema | ||||||
| * @param schemaString - the JSON schema string | ||||||
| * @returns the title field value | ||||||
| * @throws SerializationError if schema is invalid or title is missing | ||||||
| */ | ||||||
| function getJsonRecordName(schemaString: string): string { | ||||||
| try { | ||||||
| const jsonSchema = JSON.parse(schemaString) | ||||||
| if (!jsonSchema.title) { | ||||||
| throw new SerializationError('JSON schema is missing required "title" field') | ||||||
| } | ||||||
| // JSON Schema uses "title" as the record name | ||||||
| return jsonSchema.title | ||||||
| } catch (e) { | ||||||
| if (e instanceof SerializationError) { | ||||||
| throw e | ||||||
| } | ||||||
| throw new SerializationError(`Invalid JSON schema: ${e instanceof Error ? e.message : String(e)}`) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Helper function to extract the record name from a schema | ||||||
| * @param schema - the schema info (schema and its associated information) | ||||||
| * @returns the record name | ||||||
| * @throws SerializationError if schema is invalid, missing, or lacks required fields | ||||||
| */ | ||||||
| function getRecordName(schema?: SchemaInfo): string { | ||||||
| if (!schema || !schema.schema) { | ||||||
| throw new SerializationError('Schema is required but was not provided') | ||||||
| } | ||||||
|
|
||||||
| const schemaType = schema.schemaType?.toUpperCase() || 'AVRO' | ||||||
|
|
||||||
| switch (schemaType) { | ||||||
| case 'AVRO': | ||||||
| return getAvroRecordName(schema.schema) | ||||||
| case 'PROTOBUF': | ||||||
| return getProtobufRecordName(schema.schema) | ||||||
| case 'JSON': | ||||||
| return getJsonRecordName(schema.schema) | ||||||
| default: | ||||||
| throw new SerializationError(`Unsupported schema type: ${schemaType}`) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * TopicNameStrategy creates a subject name by appending -[key|value] to the topic name. | ||||||
| * @param topic - the topic name | ||||||
|
|
@@ -675,6 +783,22 @@ export const TopicNameStrategy: SubjectNameStrategyFunc = (topic: string, serdeT | |||||
| return topic + suffix | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * RecordNameStrategy creates a subject name using only the record name. | ||||||
| * This allows schemas to be shared across topics, with compatibility per record type. | ||||||
| * @param _topic - the topic name | ||||||
| * @param _serdeType - the serde type | ||||||
| * @param schema - the schema info (used to extract record name) | ||||||
| * @throws SerializationError if schema is invalid or record name cannot be determined | ||||||
| */ | ||||||
| export const RecordNameStrategy: SubjectNameStrategyFunc = ( | ||||||
| _topic: string, | ||||||
| _serdeType: SerdeType, | ||||||
| schema?: SchemaInfo | ||||||
| ) => { | ||||||
| return getRecordName(schema) | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * SchemaIdSerializerFunc serializes a schema ID/GUID | ||||||
| */ | ||||||
|
|
@@ -1013,7 +1137,7 @@ export class ErrorAction implements RuleAction { | |||||
| } | ||||||
|
|
||||||
| async run(ctx: RuleContext, msg: any, err: Error): Promise<void> { | ||||||
| throw new SerializationError(`rule ${ctx.rule.name} failed: ${err.message}`) | ||||||
| throw new SerializationError(err.message) | ||||||
|
||||||
| throw new SerializationError(err.message) | |
| throw new SerializationError(`rule ${ctx.rule.name} failed: ${err.message}`) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assignment
|| nullis unnecessary sincefileDesc.packageis already optional. The ternary on line 717 already handles falsy values correctly. Consider simplifying toconst packageName = fileDesc.package.