mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-10-12 00:45:02 +00:00
The generator gated all union decoder emission behind knownDiscriminators, a hand-curated map of 13 entries. Any sealed union absent from that map received no decoder, so decoding must fail. Bot API v10 added RichBlock and RichText, which were absent. Roughly 8.6 percent of Telegram updates failed with HTTP 400. The generator now derives discriminators from the specification instead of a fixed list. A gate check fails the build when a union resolves no discriminator. Eleven unions gain decoders that never had one: BotCommandScope, InputMedia, InputPaidMedia, InputPollMedia, InputPollOptionMedia, InputProfilePhoto, InputRichBlock, InputStoryContent, PassportElementError, RichBlock and RichText. RichText can also appear as a bare string or an array of RichText. The scraper now extracts these alternate shapes into the intermediate representation. The emitter adds RichTextPlain and RichTextSequence. The decoder dispatches on the first JSON byte before it reads any discriminator. Unions without alternates must regenerate byte for byte. The golden test proves this. Claude-Session: https://claude.ai/code/session_01Fu2wXqGUrfVBi5aLafKk8U
89 lines
2.5 KiB
Go
89 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/goccy/go-json"
|
|
"os"
|
|
|
|
"golang.org/x/net/html"
|
|
|
|
"github.com/lukaszraczylo/go-telegram/internal/spec"
|
|
)
|
|
|
|
// scrape (the package-level implementation overriding the stub in main.go;
|
|
// remove the stub from main.go in this task) parses the docs HTML into IR.
|
|
func scrape(htmlBytes []byte) (*spec.API, error) {
|
|
doc, err := html.Parse(bytes.NewReader(htmlBytes))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("html parse: %w", err)
|
|
}
|
|
sections := walk(doc)
|
|
api := &spec.API{Version: extractVersion(sections)}
|
|
for _, s := range sections {
|
|
switch {
|
|
case isMethodTitle(s.Title):
|
|
api.Methods = append(api.Methods, methodFromSection(s))
|
|
case isTypeTitle(s.Title):
|
|
api.Types = append(api.Types, typeFromSection(s))
|
|
}
|
|
}
|
|
return api, nil
|
|
}
|
|
|
|
func typeFromSection(s section) spec.TypeDecl {
|
|
td := spec.TypeDecl{Name: s.Title, Doc: s.Description}
|
|
if len(s.Tables) > 0 {
|
|
td.Fields = parseFieldsTable(s.Tables[0])
|
|
} else if len(s.Lists) > 0 {
|
|
// Union: extract variant names from <li><a>...</a></li>.
|
|
td.OneOf = extractListLinks(s.Lists[0])
|
|
// Some unions (e.g. RichText) also declare non-object wire shapes
|
|
// ("a String for plain text", "an Array of RichText") in the same
|
|
// sentence as the object-variant list; extract those too.
|
|
td.Alternates = extractAlternates(td.Name, td.Doc)
|
|
}
|
|
return td
|
|
}
|
|
|
|
func methodFromSection(s section) spec.MethodDecl {
|
|
md := spec.MethodDecl{Name: s.Title, Doc: s.Description, Returns: extractReturn(s.Description)}
|
|
if len(s.Tables) > 0 {
|
|
md.Params = parseParamsTable(s.Tables[0])
|
|
}
|
|
md.HasFiles = hasFilesParams(md.Params)
|
|
return md
|
|
}
|
|
|
|
// extractListLinks pulls anchor texts out of a <ul>: each <li><a>X</a></li>
|
|
// contributes "X" to the result. Used for union variant lists.
|
|
func extractListLinks(ul *html.Node) []string {
|
|
var names []string
|
|
var visit func(*html.Node)
|
|
visit = func(n *html.Node) {
|
|
if n.Type == html.ElementNode && n.Data == "a" {
|
|
names = append(names, textOf(n))
|
|
return
|
|
}
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
visit(c)
|
|
}
|
|
}
|
|
visit(ul)
|
|
return names
|
|
}
|
|
|
|
// writeJSON marshals the IR with stable, human-readable formatting and
|
|
// writes it to path. Marshalling is deterministic: types and methods
|
|
// preserve scrape order; struct fields use IR-defined order.
|
|
func writeJSON(path string, api *spec.API) error {
|
|
var buf bytes.Buffer
|
|
enc := json.NewEncoder(&buf)
|
|
enc.SetIndent("", " ")
|
|
enc.SetEscapeHTML(false)
|
|
if err := enc.Encode(api); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, buf.Bytes(), 0o644)
|
|
}
|