fix(api): generate CallRaw + per-element decode for []<union> returns

GetChatAdministrators returns []ChatMember, where ChatMember is a
sealed-interface union. The codegen template emitted the generic
client.Call[..., []ChatMember] for it — encoding/json cannot unmarshal
a slice of an interface (no discriminator-aware path), so every real
response from Telegram failed at the parse step:

  telegram: parse: json: cannot unmarshal api.ChatMember into
  Go struct field Result[[]ChatMember].Result of type api.ChatMember

Fix is in cmd/genapi/methods.tmpl: add a third branch alongside the
existing single-union branch. When a method returns []<union>,
emit CallRaw + json.Unmarshal into []json.RawMessage + per-element
Unmarshal<Union>(e). Mirrors what GetChatMember (single-element)
already does, applied uniformly so any future slice-of-union method
Telegram introduces inherits the right shape.

Survey of v1.1.1 across all 23 sealed-interface unions confirms
GetChatAdministrators was the only broken site; the fix regenerates
just that one method body. New regression tests in
api/getchatadministrators_test.go cover the typical
admin+owner response and the empty-array case.
This commit is contained in:
2026-05-09 18:57:54 +01:00
parent 5a27b53f30
commit f899cc2663
15 changed files with 1097 additions and 995 deletions
+14 -14
View File
@@ -36,7 +36,7 @@ var ErrKeyNotFound = errors.New("conversation: key not found")
```
<a name="End"></a>
## func End
## func [End](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/handler.go#L34>)
```go
func End() error
@@ -45,7 +45,7 @@ func End() error
End signals the conversation has finished and state should be cleared. Conversation handlers return End\(\) to terminate.
<a name="Next"></a>
## func Next
## func [Next](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/handler.go#L28>)
```go
func Next(s State) error
@@ -54,7 +54,7 @@ func Next(s State) error
Next signals the conversation should advance to the given state. Conversation handlers return Next\("state\_name"\) to transition.
<a name="Conversation"></a>
## type Conversation
## type [Conversation](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/handler.go#L55-L79>)
Conversation is a stateful handler with entry, per\-state, exit and fallback steps. A conversation is keyed by KeyStrategy \(default KeyByUserAndChat\) and persisted by Storage \(default in\-memory\).
@@ -87,7 +87,7 @@ type Conversation struct {
```
<a name="Conversation.Dispatch"></a>
### func \(\*Conversation\) Dispatch
### func \(\*Conversation\) [Dispatch](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/handler.go#L87>)
```go
func (c *Conversation) Dispatch(next dispatch.Handler[*api.Update]) dispatch.Handler[*api.Update]
@@ -98,7 +98,7 @@ Dispatch is a global middleware\-shaped Handler that consumes updates and routes
If the conversation claims an update, downstream handlers are skipped. If the conversation does not claim it, downstream handlers run as normal.
<a name="Handler"></a>
## type Handler
## type [Handler](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/handler.go#L44>)
Handler defines a step in the conversation. Receives the dispatch context and the raw update. Returns:
@@ -112,7 +112,7 @@ type Handler func(ctx *dispatch.Context, u *api.Update) error
```
<a name="KeyStrategy"></a>
## type KeyStrategy
## type [KeyStrategy](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/key.go#L16>)
KeyStrategy derives a persistence key from an update. Strategies determine how conversation scope works — per\-user, per\-chat, or per\-user\-and\-chat. Implementations must return a stable string for the same logical scope across updates.
@@ -158,7 +158,7 @@ var KeyByUserAndChat KeyStrategy = func(u *api.Update) string {
```
<a name="MemoryStorage"></a>
## type MemoryStorage
## type [MemoryStorage](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/in_memory.go#L11-L14>)
MemoryStorage is the default in\-process Storage. It is safe for concurrent use. Conversation state is lost on process restart; use a custom Storage backed by a database for persistent flows.
@@ -169,7 +169,7 @@ type MemoryStorage struct {
```
<a name="NewMemoryStorage"></a>
### func NewMemoryStorage
### func [NewMemoryStorage](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/in_memory.go#L17>)
```go
func NewMemoryStorage() *MemoryStorage
@@ -178,7 +178,7 @@ func NewMemoryStorage() *MemoryStorage
NewMemoryStorage constructs an empty in\-memory storage.
<a name="MemoryStorage.Delete"></a>
### func \(\*MemoryStorage\) Delete
### func \(\*MemoryStorage\) [Delete](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/in_memory.go#L38>)
```go
func (s *MemoryStorage) Delete(_ context.Context, key string) error
@@ -187,7 +187,7 @@ func (s *MemoryStorage) Delete(_ context.Context, key string) error
<a name="MemoryStorage.Get"></a>
### func \(\*MemoryStorage\) Get
### func \(\*MemoryStorage\) [Get](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/in_memory.go#L21>)
```go
func (s *MemoryStorage) Get(_ context.Context, key string) (State, error)
@@ -196,7 +196,7 @@ func (s *MemoryStorage) Get(_ context.Context, key string) (State, error)
<a name="MemoryStorage.Set"></a>
### func \(\*MemoryStorage\) Set
### func \(\*MemoryStorage\) [Set](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/in_memory.go#L31>)
```go
func (s *MemoryStorage) Set(_ context.Context, key string, state State) error
@@ -205,7 +205,7 @@ func (s *MemoryStorage) Set(_ context.Context, key string, state State) error
<a name="State"></a>
## type State
## type [State](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/state.go#L9>)
State is a label identifying a node in the conversation graph. The empty string is the implicit "no active conversation" state.
@@ -214,7 +214,7 @@ type State string
```
<a name="Step"></a>
## type Step
## type [Step](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/handler.go#L47-L50>)
Step pairs a filter with a handler for one conversation step.
@@ -226,7 +226,7 @@ type Step struct {
```
<a name="Storage"></a>
## type Storage
## type [Storage](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/conversation/storage.go#L16-L20>)
Storage persists per\-user \(or per\-chat, per\-message — depending on the KeyStrategy in use\) conversation state across update deliveries.
+4 -4
View File
@@ -17,7 +17,7 @@ Package callback provides Filter helpers for \*api.CallbackQuery payloads.
<a name="Data"></a>
## func Data
## func [Data](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/callback/callback.go#L14>)
```go
func Data(pattern string) dispatch.Filter[*api.CallbackQuery]
@@ -26,7 +26,7 @@ func Data(pattern string) dispatch.Filter[*api.CallbackQuery]
Data returns a Filter that matches callback queries whose Data matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="DataEquals"></a>
## func DataEquals
## func [DataEquals](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/callback/callback.go#L23>)
```go
func DataEquals(s string) dispatch.Filter[*api.CallbackQuery]
@@ -35,7 +35,7 @@ func DataEquals(s string) dispatch.Filter[*api.CallbackQuery]
DataEquals returns a Filter that matches callback queries whose Data equals s exactly.
<a name="DataPrefix"></a>
## func DataPrefix
## func [DataPrefix](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/callback/callback.go#L31>)
```go
func DataPrefix(prefix string) dispatch.Filter[*api.CallbackQuery]
@@ -44,7 +44,7 @@ func DataPrefix(prefix string) dispatch.Filter[*api.CallbackQuery]
DataPrefix returns a Filter that matches callback queries whose Data starts with prefix.
<a name="FromUser"></a>
## func FromUser
## func [FromUser](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/callback/callback.go#L39>)
```go
func FromUser(userID int64) dispatch.Filter[*api.CallbackQuery]
@@ -15,7 +15,7 @@ Package chatjoinrequest provides Filter helpers for \*api.ChatJoinRequest payloa
<a name="FromUser"></a>
## func FromUser
## func [FromUser](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/chatjoinrequest/chatjoinrequest.go#L11>)
```go
func FromUser(uid int64) dispatch.Filter[*api.ChatJoinRequest]
@@ -24,7 +24,7 @@ func FromUser(uid int64) dispatch.Filter[*api.ChatJoinRequest]
FromUser returns a Filter that matches join requests where the requesting user's ID equals uid.
<a name="InChat"></a>
## func InChat
## func [InChat](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/chatjoinrequest/chatjoinrequest.go#L19>)
```go
func InChat(cid int64) dispatch.Filter[*api.ChatJoinRequest]
@@ -15,7 +15,7 @@ Package chatmember provides Filter helpers for \*api.ChatMemberUpdated payloads.
<a name="FromUser"></a>
## func FromUser
## func [FromUser](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/chatmember/chatmember.go#L37>)
```go
func FromUser(uid int64) dispatch.Filter[*api.ChatMemberUpdated]
@@ -24,7 +24,7 @@ func FromUser(uid int64) dispatch.Filter[*api.ChatMemberUpdated]
FromUser returns a Filter that matches updates where the acting user \(From.ID\) equals uid.
<a name="NewStatus"></a>
## func NewStatus
## func [NewStatus](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/chatmember/chatmember.go#L11>)
```go
func NewStatus(s string) dispatch.Filter[*api.ChatMemberUpdated]
+3 -3
View File
@@ -16,7 +16,7 @@ Package inline provides Filter helpers for \*api.InlineQuery payloads.
<a name="Query"></a>
## func Query
## func [Query](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/inline/inline.go#L14>)
```go
func Query(pattern string) dispatch.Filter[*api.InlineQuery]
@@ -25,7 +25,7 @@ func Query(pattern string) dispatch.Filter[*api.InlineQuery]
Query returns a Filter that matches inline queries whose Query field matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="QueryEquals"></a>
## func QueryEquals
## func [QueryEquals](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/inline/inline.go#L23>)
```go
func QueryEquals(s string) dispatch.Filter[*api.InlineQuery]
@@ -34,7 +34,7 @@ func QueryEquals(s string) dispatch.Filter[*api.InlineQuery]
QueryEquals returns a Filter that matches inline queries whose Query equals s exactly.
<a name="QueryPrefix"></a>
## func QueryPrefix
## func [QueryPrefix](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/inline/inline.go#L31>)
```go
func QueryPrefix(prefix string) dispatch.Filter[*api.InlineQuery]
+14 -14
View File
@@ -27,7 +27,7 @@ Package message provides Filter helpers for \*api.Message payloads.
<a name="AnyCommand"></a>
## func AnyCommand
## func [AnyCommand](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L69>)
```go
func AnyCommand() dispatch.Filter[*api.Message]
@@ -36,7 +36,7 @@ func AnyCommand() dispatch.Filter[*api.Message]
AnyCommand returns a Filter that matches any message starting with a bot\_command entity at offset 0.
<a name="ChatType"></a>
## func ChatType
## func [ChatType](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L124>)
```go
func ChatType(t api.ChatType) dispatch.Filter[*api.Message]
@@ -45,7 +45,7 @@ func ChatType(t api.ChatType) dispatch.Filter[*api.Message]
ChatType returns a Filter that matches messages whose Chat.Type equals t.
<a name="Command"></a>
## func Command
## func [Command](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L44>)
```go
func Command(name string) dispatch.Filter[*api.Message]
@@ -54,7 +54,7 @@ func Command(name string) dispatch.Filter[*api.Message]
Command returns a Filter that matches messages whose first entity is a bot\_command equal to "/\<name\>" \(with or without "@BotName" suffix\).
<a name="FromUser"></a>
## func FromUser
## func [FromUser](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L131>)
```go
func FromUser(userID int64) dispatch.Filter[*api.Message]
@@ -63,7 +63,7 @@ func FromUser(userID int64) dispatch.Filter[*api.Message]
FromUser returns a Filter that matches messages whose From.ID equals userID.
<a name="HasDocument"></a>
## func HasDocument
## func [HasDocument](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L101>)
```go
func HasDocument() dispatch.Filter[*api.Message]
@@ -72,7 +72,7 @@ func HasDocument() dispatch.Filter[*api.Message]
HasDocument returns a Filter that matches messages with a Document attachment.
<a name="HasEntity"></a>
## func HasEntity
## func [HasEntity](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L109>)
```go
func HasEntity(t api.MessageEntityType) dispatch.Filter[*api.Message]
@@ -81,7 +81,7 @@ func HasEntity(t api.MessageEntityType) dispatch.Filter[*api.Message]
HasEntity returns a Filter that matches messages whose Entities contain at least one entity of type t \(e.g. api.MessageEntityTypeBotCommand\).
<a name="HasPhoto"></a>
## func HasPhoto
## func [HasPhoto](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L94>)
```go
func HasPhoto() dispatch.Filter[*api.Message]
@@ -90,7 +90,7 @@ func HasPhoto() dispatch.Filter[*api.Message]
HasPhoto returns a Filter that matches messages with a Photo attachment.
<a name="InChat"></a>
## func InChat
## func [InChat](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L138>)
```go
func InChat(chatID int64) dispatch.Filter[*api.Message]
@@ -99,7 +99,7 @@ func InChat(chatID int64) dispatch.Filter[*api.Message]
InChat returns a Filter that matches messages whose Chat.ID equals chatID.
<a name="IsForward"></a>
## func IsForward
## func [IsForward](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L87>)
```go
func IsForward() dispatch.Filter[*api.Message]
@@ -108,7 +108,7 @@ func IsForward() dispatch.Filter[*api.Message]
IsForward returns a Filter that matches messages that have ForwardOrigin set.
<a name="IsReply"></a>
## func IsReply
## func [IsReply](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L80>)
```go
func IsReply() dispatch.Filter[*api.Message]
@@ -117,7 +117,7 @@ func IsReply() dispatch.Filter[*api.Message]
IsReply returns a Filter that matches messages that have ReplyToMessage set.
<a name="Text"></a>
## func Text
## func [Text](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L14>)
```go
func Text(pattern string) dispatch.Filter[*api.Message]
@@ -126,7 +126,7 @@ func Text(pattern string) dispatch.Filter[*api.Message]
Text returns a Filter that matches messages whose Text matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="TextContains"></a>
## func TextContains
## func [TextContains](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L36>)
```go
func TextContains(sub string) dispatch.Filter[*api.Message]
@@ -135,7 +135,7 @@ func TextContains(sub string) dispatch.Filter[*api.Message]
TextContains returns a Filter that matches messages whose Text contains sub.
<a name="TextEquals"></a>
## func TextEquals
## func [TextEquals](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L22>)
```go
func TextEquals(s string) dispatch.Filter[*api.Message]
@@ -144,7 +144,7 @@ func TextEquals(s string) dispatch.Filter[*api.Message]
TextEquals returns a Filter that matches messages whose Text equals s exactly.
<a name="TextPrefix"></a>
## func TextPrefix
## func [TextPrefix](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/message/message.go#L29>)
```go
func TextPrefix(prefix string) dispatch.Filter[*api.Message]
@@ -15,7 +15,7 @@ Package precheckoutquery provides Filter helpers for \*api.PreCheckoutQuery payl
<a name="Currency"></a>
## func Currency
## func [Currency](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/precheckoutquery/precheckoutquery.go#L11>)
```go
func Currency(c string) dispatch.Filter[*api.PreCheckoutQuery]
@@ -24,7 +24,7 @@ func Currency(c string) dispatch.Filter[*api.PreCheckoutQuery]
Currency returns a Filter that matches pre\-checkout queries with the given ISO 4217 currency code \(e.g. "USD", "EUR", "XTR"\).
<a name="FromUser"></a>
## func FromUser
## func [FromUser](<https://github.com/lukaszraczylo/go-telegram/blob/main/dispatch/filters/precheckoutquery/precheckoutquery.go#L19>)
```go
func FromUser(uid int64) dispatch.Filter[*api.PreCheckoutQuery]