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
+13
View File
@@ -245,6 +245,19 @@ func funcs(plan *enumPlan) template.FuncMap {
s, ok := knownDiscriminators[tr.Name]
return ok && len(s.Variants) > 0
},
"isSealedUnionArrayReturn": func(tr spec.TypeRef) bool {
if tr.Kind != spec.KindArray || tr.ElemType == nil || tr.ElemType.Kind != spec.KindNamed {
return false
}
s, ok := knownDiscriminators[tr.ElemType.Name]
return ok && len(s.Variants) > 0
},
"sealedUnionElemName": func(tr spec.TypeRef) string {
if tr.Kind == spec.KindArray && tr.ElemType != nil {
return tr.ElemType.Name
}
return ""
},
"isMaybeInaccessibleMessage": func(name string) bool { return name == "MaybeInaccessibleMessage" },
"discriminatorField": func(name string) string { return knownDiscriminators[name].Field },
"discriminatorMap": func(name string) map[string]string { return knownDiscriminators[name].Variants },
+18
View File
@@ -51,6 +51,24 @@ func {{title .Name}}(ctx context.Context, b *client.Bot, p *{{title .Name}}Param
return nil, err
}
return Unmarshal{{.Returns.Name}}(raw)
{{else if isSealedUnionArrayReturn .Returns -}}
raw, err := client.CallRaw[*{{title .Name}}Params](ctx, b, "{{.Name}}", p)
if err != nil {
return nil, err
}
var elems []json.RawMessage
if err := json.Unmarshal(raw, &elems); err != nil {
return nil, err
}
out := make([]{{sealedUnionElemName .Returns}}, 0, len(elems))
for _, e := range elems {
v, err := Unmarshal{{sealedUnionElemName .Returns}}(e)
if err != nil {
return nil, err
}
out = append(out, v)
}
return out, nil
{{else -}}
return client.Call[*{{title .Name}}Params, {{returnGoType .Returns}}](ctx, b, "{{.Name}}", p)
{{end -}}