Files
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Mario
a739cb2805 Bump github.com/twmb/franz-go from 1.18.1 to 1.20.0 (#5766)
* Bump github.com/twmb/franz-go from 1.18.1 to 1.20.0

Bumps [github.com/twmb/franz-go](https://github.com/twmb/franz-go) from 1.18.1 to 1.20.0.
- [Changelog](https://github.com/twmb/franz-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/twmb/franz-go/compare/v1.18.1...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/twmb/franz-go
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>

* Update kfake

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mario <[email protected]>
2025-10-20 13:55:55 +02:00

468 lines
15 KiB
Go

// Package kmsg contains Kafka request and response types and autogenerated
// serialization and deserialization functions.
//
// This package may bump major versions whenever Kafka makes a backwards
// incompatible protocol change, per the types chosen for this package. For
// example, Kafka can change a field from non-nullable to nullable, which would
// require changing a field from a non-pointer to a pointer. We could get
// around this by making everything an opaque struct and having getters, but
// that is more tedious than having a few rare major version bumps.
//
// If you are using this package directly with kgo, you should either always
// use New functions, or Default functions after creating structs, or you
// should pin the max supported version. If you use New functions, you will
// have safe defaults as new fields are added. If you pin versions, you will
// avoid new fields being used. If you do neither of these, you may opt in to
// new fields that do not have safe zero value defaults, and this may lead to
// errors or unexpected results.
//
// Thus, whenever you initialize a struct from this package, do the following:
//
// struct := kmsg.NewFoo()
// struct.Field = "value I want to set"
//
// Most of this package is generated, but a few things are manual. What is
// manual: all interfaces, the RequestFormatter, record / message / record
// batch reading, and sticky member metadata serialization.
package kmsg
import (
"context"
"sort"
"github.com/twmb/franz-go/pkg/kmsg/internal/kbin"
)
//go:generate cp ../kbin/primitives.go internal/kbin/
// Requestor issues requests. Notably, the kgo.Client and kgo.Broker implements
// Requestor. All Requests in this package have a RequestWith function to have
// type-safe requests.
type Requestor interface {
// Request issues a Request and returns either a Response or an error.
Request(context.Context, Request) (Response, error)
}
// Request represents a type that can be requested to Kafka.
type Request interface {
// Key returns the protocol key for this message kind.
Key() int16
// MaxVersion returns the maximum protocol version this message
// supports.
//
// This function allows one to implement a client that chooses message
// versions based off of the max of a message's max version in the
// client and the broker's max supported version.
MaxVersion() int16
// SetVersion sets the version to use for this request and response.
SetVersion(int16)
// GetVersion returns the version currently set to use for the request
// and response.
GetVersion() int16
// IsFlexible returns whether the request at its current version is
// "flexible" as per the KIP-482.
IsFlexible() bool
// AppendTo appends this message in wire protocol form to a slice and
// returns the slice.
AppendTo([]byte) []byte
// ReadFrom parses all of the input slice into the response type.
//
// This should return an error if too little data is input.
ReadFrom([]byte) error
// ResponseKind returns an empty Response that is expected for
// this message request.
ResponseKind() Response
}
// AdminRequest represents a request that must be issued to Kafka controllers.
type AdminRequest interface {
// IsAdminRequest is a method attached to requests that must be
// issed to Kafka controllers.
IsAdminRequest()
Request
}
// GroupCoordinatorRequest represents a request that must be issued to a
// group coordinator.
type GroupCoordinatorRequest interface {
// IsGroupCoordinatorRequest is a method attached to requests that
// must be issued to group coordinators.
IsGroupCoordinatorRequest()
Request
}
// TxnCoordinatorRequest represents a request that must be issued to a
// transaction coordinator.
type TxnCoordinatorRequest interface {
// IsTxnCoordinatorRequest is a method attached to requests that
// must be issued to transaction coordinators.
IsTxnCoordinatorRequest()
Request
}
// Response represents a type that Kafka responds with.
type Response interface {
// Key returns the protocol key for this message kind.
Key() int16
// MaxVersion returns the maximum protocol version this message
// supports.
MaxVersion() int16
// SetVersion sets the version to use for this request and response.
SetVersion(int16)
// GetVersion returns the version currently set to use for the request
// and response.
GetVersion() int16
// IsFlexible returns whether the request at its current version is
// "flexible" as per the KIP-482.
IsFlexible() bool
// AppendTo appends this message in wire protocol form to a slice and
// returns the slice.
AppendTo([]byte) []byte
// ReadFrom parses all of the input slice into the response type.
//
// This should return an error if too little data is input.
ReadFrom([]byte) error
// RequestKind returns an empty Request that is expected for
// this message request.
RequestKind() Request
}
// UnsafeReadFrom, implemented by all requests and responses generated in this
// package, switches to using unsafe slice-to-string conversions when reading.
// This can be used to avoid a lot of garbage, but it means to have to be
// careful when using any strings in structs: if you hold onto the string, the
// underlying response slice will not be garbage collected.
type UnsafeReadFrom interface {
UnsafeReadFrom([]byte) error
}
// ThrottleResponse represents a response that could have a throttle applied by
// Kafka. Any response that implements ThrottleResponse also implements
// SetThrottleResponse.
//
// Kafka 2.0.0 switched throttles from being applied before responses to being
// applied after responses.
type ThrottleResponse interface {
// Throttle returns the response's throttle millis value and
// whether Kafka applies the throttle after the response.
Throttle() (int32, bool)
}
// SetThrottleResponse sets the throttle in a response that can have a throttle
// applied. Any kmsg interface that implements ThrottleResponse also implements
// SetThrottleResponse.
type SetThrottleResponse interface {
// SetThrottle sets the response's throttle millis value.
SetThrottle(int32)
}
// TimeoutRequest represents a request that has a TimeoutMillis field.
// Any request that implements TimeoutRequest also implements SetTimeoutRequest.
type TimeoutRequest interface {
// Timeout returns the request's timeout millis value.
Timeout() int32
}
// SetTimeoutRequest sets the timeout in a request that can have a timeout
// applied. Any kmsg interface that implements ThrottleRequest also implements
// SetThrottleRequest.
type SetTimeoutRequest interface {
// SetTimeout sets the request's timeout millis value.
SetTimeout(timeoutMillis int32)
}
// RequestFormatter formats requests.
//
// The default empty struct works correctly, but can be extended with the
// NewRequestFormatter function.
type RequestFormatter struct {
clientID *string
}
// RequestFormatterOpt applys options to a RequestFormatter.
type RequestFormatterOpt interface {
apply(*RequestFormatter)
}
type formatterOpt struct{ fn func(*RequestFormatter) }
func (opt formatterOpt) apply(f *RequestFormatter) { opt.fn(f) }
// FormatterClientID attaches the given client ID to any issued request,
// minus controlled shutdown v0, which uses its own special format.
func FormatterClientID(id string) RequestFormatterOpt {
return formatterOpt{func(f *RequestFormatter) { f.clientID = &id }}
}
// NewRequestFormatter returns a RequestFormatter with the opts applied.
func NewRequestFormatter(opts ...RequestFormatterOpt) *RequestFormatter {
a := new(RequestFormatter)
for _, opt := range opts {
opt.apply(a)
}
return a
}
// AppendRequest appends a full message request to dst, returning the updated
// slice. This message is the full body that needs to be written to issue a
// Kafka request.
func (f *RequestFormatter) AppendRequest(
dst []byte,
r Request,
correlationID int32,
) []byte {
dst = append(dst, 0, 0, 0, 0) // reserve length
k := r.Key()
v := r.GetVersion()
dst = kbin.AppendInt16(dst, k)
dst = kbin.AppendInt16(dst, v)
dst = kbin.AppendInt32(dst, correlationID)
if k == 7 && v == 0 {
return dst
}
// Even with flexible versions, we do not use a compact client id.
// Clients issue ApiVersions immediately before knowing the broker
// version, and old brokers will not be able to understand a compact
// client id.
dst = kbin.AppendNullableString(dst, f.clientID)
// The flexible tags end the request header, and then begins the
// request body.
if r.IsFlexible() {
var numTags uint8
dst = append(dst, numTags)
if numTags != 0 {
// TODO when tags are added
}
}
// Now the request body.
dst = r.AppendTo(dst)
kbin.AppendInt32(dst[:0], int32(len(dst[4:])))
return dst
}
// StringPtr is a helper to return a pointer to a string.
func StringPtr(in string) *string {
return &in
}
// ReadFrom provides decoding various versions of sticky member metadata. A key
// point of this type is that it does not contain a version number inside it,
// but it is versioned: if decoding v1 fails, this falls back to v0.
func (s *StickyMemberMetadata) ReadFrom(src []byte) error {
return s.readFrom(src, false)
}
// UnsafeReadFrom is the same as ReadFrom, but uses unsafe slice to string
// conversions to reduce garbage.
func (s *StickyMemberMetadata) UnsafeReadFrom(src []byte) error {
return s.readFrom(src, true)
}
func (s *StickyMemberMetadata) readFrom(src []byte, unsafe bool) error {
b := kbin.Reader{Src: src}
numAssignments := b.ArrayLen()
if numAssignments < 0 {
numAssignments = 0
}
need := numAssignments - int32(cap(s.CurrentAssignment))
if need > 0 {
s.CurrentAssignment = append(s.CurrentAssignment[:cap(s.CurrentAssignment)], make([]StickyMemberMetadataCurrentAssignment, need)...)
} else {
s.CurrentAssignment = s.CurrentAssignment[:numAssignments]
}
for i := int32(0); i < numAssignments; i++ {
var topic string
if unsafe {
topic = b.UnsafeString()
} else {
topic = b.String()
}
numPartitions := b.ArrayLen()
if numPartitions < 0 {
numPartitions = 0
}
a := &s.CurrentAssignment[i]
a.Topic = topic
need := numPartitions - int32(cap(a.Partitions))
if need > 0 {
a.Partitions = append(a.Partitions[:cap(a.Partitions)], make([]int32, need)...)
} else {
a.Partitions = a.Partitions[:numPartitions]
}
for i := range a.Partitions {
a.Partitions[i] = b.Int32()
}
}
if len(b.Src) > 0 {
s.Generation = b.Int32()
} else {
s.Generation = -1
}
return b.Complete()
}
// AppendTo provides appending various versions of sticky member metadata to dst.
// If generation is not -1 (default for v0), this appends as version 1.
func (s *StickyMemberMetadata) AppendTo(dst []byte) []byte {
dst = kbin.AppendArrayLen(dst, len(s.CurrentAssignment))
for _, assignment := range s.CurrentAssignment {
dst = kbin.AppendString(dst, assignment.Topic)
dst = kbin.AppendArrayLen(dst, len(assignment.Partitions))
for _, partition := range assignment.Partitions {
dst = kbin.AppendInt32(dst, partition)
}
}
if s.Generation != -1 {
dst = kbin.AppendInt32(dst, s.Generation)
}
return dst
}
// TagReader has is a type that has the ability to skip tags.
//
// This is effectively a trimmed version of the kbin.Reader, with the purpose
// being that kmsg cannot depend on an external package.
type TagReader interface {
// Uvarint returns a uint32. If the reader has read too much and has
// exhausted all bytes, this should set the reader's internal state
// to failed and return 0.
Uvarint() uint32
// Span returns n bytes from the reader. If the reader has read too
// much and exhausted all bytes this should set the reader's internal
// to failed and return nil.
Span(n int) []byte
}
// SkipTags skips tags in a TagReader.
func SkipTags(b TagReader) {
for num := b.Uvarint(); num > 0; num-- {
_, size := b.Uvarint(), b.Uvarint()
b.Span(int(size))
}
}
// internalSkipTags skips tags in the duplicated inner kbin.Reader.
func internalSkipTags(b *kbin.Reader) {
for num := b.Uvarint(); num > 0; num-- {
_, size := b.Uvarint(), b.Uvarint()
b.Span(int(size))
}
}
// ReadTags reads tags in a TagReader and returns the tags.
func ReadTags(b TagReader) Tags {
var t Tags
for num := b.Uvarint(); num > 0; num-- {
key, size := b.Uvarint(), b.Uvarint()
t.Set(key, b.Span(int(size)))
}
return t
}
// internalReadTags reads tags in a reader and returns the tags from a
// duplicated inner kbin.Reader.
func internalReadTags(b *kbin.Reader) Tags {
var t Tags
for num := b.Uvarint(); num > 0; num-- {
key, size := b.Uvarint(), b.Uvarint()
t.Set(key, b.Span(int(size)))
}
return t
}
// Tags is an opaque structure capturing unparsed tags.
type Tags struct {
keyvals map[uint32][]byte
}
// Len returns the number of keyvals in Tags.
func (t *Tags) Len() int { return len(t.keyvals) }
// Each calls fn for each key and val in the tags.
func (t *Tags) Each(fn func(uint32, []byte)) {
if len(t.keyvals) == 0 {
return
}
// We must encode keys in order. We expect to have limited (no) unknown
// keys, so for now, we take a lazy approach and allocate an ordered
// slice.
ordered := make([]uint32, 0, len(t.keyvals))
for key := range t.keyvals {
ordered = append(ordered, key)
}
sort.Slice(ordered, func(i, j int) bool { return ordered[i] < ordered[j] })
for _, key := range ordered {
fn(key, t.keyvals[key])
}
}
// Set sets a tag's key and val.
//
// Note that serializing tags does NOT check if the set key overlaps with an
// existing used key. It is invalid to set a key used by Kafka itself.
func (t *Tags) Set(key uint32, val []byte) {
if t.keyvals == nil {
t.keyvals = make(map[uint32][]byte)
}
t.keyvals[key] = val
}
// AppendEach appends each keyval in tags to dst and returns the updated dst.
func (t *Tags) AppendEach(dst []byte) []byte {
t.Each(func(key uint32, val []byte) {
dst = kbin.AppendUvarint(dst, key)
dst = kbin.AppendUvarint(dst, uint32(len(val)))
dst = append(dst, val...)
})
return dst
}
////////////////////////
// DEPRECATED RENAMES //
////////////////////////
// Deprecated: this was renamed to ControlRecordKeyTypeSnapshotHeader.
const ControlRecordKeyTypeLeaderChange = ControlRecordKeyTypeSnapshotHeader
type (
// Deprecated: this was renamed to ListConfigResourcesRequest.
ListClientMetricsResourcesRequest = ListConfigResourcesRequest
// Deprecated: this was renamed to ListConfigResourcesResponse.
ListClientMetricsResourcesResponse = ListConfigResourcesResponse
// Deprecated: this was renamed to ListConfigResourcesResponseConfigResource.
ListClientMetricsResourcesResponseClientMetricsResource = ListConfigResourcesResponseConfigResource
)
// Deprecated: this was renamed to ListConfigResources.
var ListClientMetricsResources Key = 74
// Deprecated: this was renamed to NewPtrListConfigResourcesRequest.
func NewPtrListClientMetricsResourcesRequest() *ListClientMetricsResourcesRequest {
return NewPtrListConfigResourcesRequest()
}
// Deprecated: this was renamed to NewListConfigResourcesRequest.
func NewListClientMetricsResourcesRequest() ListClientMetricsResourcesRequest {
return NewListConfigResourcesRequest()
}
// Deprecated: this was renamed to NewListConfigResourcesResponseConfigResource.
func NewListClientMetricsResourcesResponseClientMetricsResource() ListClientMetricsResourcesResponseClientMetricsResource {
return NewListConfigResourcesResponseConfigResource()
}
// Deprecated: this was renamed to NewPtrListConfigResourcesResponse.
func NewPtrListClientMetricsResourcesResponse() *ListClientMetricsResourcesResponse {
return NewPtrListConfigResourcesResponse()
}
// Deprecated: this was renamed to NewListConfigResourcesResponse.
func NewListClientMetricsResourcesResponse() ListClientMetricsResourcesResponse {
return NewListConfigResourcesResponse()
}