package transcode import ( "fmt" "sort" "sync" ) // Registry 承载「哪些 源→目标 可用」(design/25 §1.3.2 第一层)。它是**包内**的 // 编解码器注册表,与全局能力注册表(internal/capability/registry)是两件事: // 后者表达「agent 可选用的能力 + 门控策略」,前者表达「本能力认识哪几条路」。 // // 新增一种格式 = 往这里多一个注册项(见 codecs.go 的 DefaultCodecs); // 并发安全。 type Registry struct { mu sync.RWMutex codecs map[route]Codec } // route 是「源→目标」这一对,作注册表的键。用结构体而非字符串拼接:拼接会让 // 格式名里出现分隔符时产生歧义(`a->b` 与 `a-` + `>b`)。 type route struct { from Format to Format } // NewRegistry 返回一个空且并发安全的编解码器注册表。 func NewRegistry() *Registry { return &Registry{codecs: make(map[route]Codec)} } // Register 注册一条转换路。nil、源/目标为空、源等于目标都返回 ErrInvalidCodec; // 同一对重复注册返回 ErrDuplicate。 func (r *Registry) Register(c Codec) error { if c == nil { return fmt.Errorf("transcode: register codec: %w: nil codec", ErrInvalidCodec) } from, to := c.From(), c.To() if from == "" || to == "" { return fmt.Errorf("transcode: register codec %q -> %q: %w: empty format", from, to, ErrInvalidCodec) } if from == to { return fmt.Errorf("transcode: register codec %q -> %q: %w: identity route", from, to, ErrInvalidCodec) } r.mu.Lock() defer r.mu.Unlock() key := route{from: from, to: to} if _, exists := r.codecs[key]; exists { return fmt.Errorf("transcode: register codec %q -> %q: %w", from, to, ErrDuplicate) } r.codecs[key] = c return nil } // Lookup 返回 from→to 这条路上的编解码器。没有这条路时 ok 为 false。 func (r *Registry) Lookup(from, to Format) (Codec, bool) { r.mu.RLock() defer r.mu.RUnlock() c, ok := r.codecs[route{from: from, to: to}] return c, ok } // Formats 返回当前注册涉及的全部格式(源与目标去重后的并集,已排序)。 // 它是「发现」面:调用方据此知道本能力认识哪些格式名。 func (r *Registry) Formats() []Format { r.mu.RLock() defer r.mu.RUnlock() seen := make(map[Format]struct{}, len(r.codecs)*2) for key := range r.codecs { seen[key.from] = struct{}{} seen[key.to] = struct{}{} } out := make([]Format, 0, len(seen)) for f := range seen { out = append(out, f) } sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) return out } // Len 返回已注册的「源→目标」路数。供启动期自检与测试断言使用。 func (r *Registry) Len() int { r.mu.RLock() defer r.mu.RUnlock() return len(r.codecs) }