mirror of
https://github.com/livekit/livekit.git
synced 2026-09-18 03:35:21 +00:00
agent endpoints: key templates by shape and export their ambiguity
Canonical renders a template as its literals and convertor kinds, so two
templates the trie cannot tell apart share a key. Param names reach the trie
only as a kind, so /x/{a} and /x/{b} accept the same paths; anything keying
routes by the declared spelling splits them apart. Literals are length-prefixed
because a brace that opens no well-formed param is literal text: /x/{:str}
matches itself and must not collide with /x/{a}.
ambiguousTemplate becomes Template.Ambiguous. It reads only the template's own
elements, so it answers for a manifest that was validated but never compiled
into a table of its own.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b567fcfa78
commit
cb8f640865
@@ -54,7 +54,7 @@ func (b *Builder[T]) Add(t *Template, m Mask, v T) error {
|
||||
idx := b.n
|
||||
b.n++
|
||||
|
||||
if ambiguousTemplate(t) {
|
||||
if t.Ambiguous() {
|
||||
b.ambiguous = append(b.ambiguous, t.raw)
|
||||
}
|
||||
|
||||
@@ -125,24 +125,6 @@ func singleRun[T any](e *buildEdge[T]) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ambiguousTemplate reports whether a template's own shape can force a search,
|
||||
// independent of what other templates put in the tree.
|
||||
func ambiguousTemplate(t *Template) bool {
|
||||
for i, e := range t.elements {
|
||||
if e.kind == kindLiteral || e.kind == kindUUID {
|
||||
continue
|
||||
}
|
||||
if i == len(t.elements)-1 {
|
||||
continue // terminal: only the greedy run can reach the end
|
||||
}
|
||||
next := t.elements[i+1]
|
||||
if next.kind != kindLiteral || e.kind.charset(next.lit[0]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func insertLiteral[T any](n *buildNode[T], lit string, idx uint32) *buildNode[T] {
|
||||
for len(lit) > 0 {
|
||||
e := literalEdge(n, lit[0])
|
||||
|
||||
@@ -16,6 +16,7 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@@ -67,6 +68,46 @@ type Template struct {
|
||||
// String returns the template as declared.
|
||||
func (t *Template) String() string { return t.raw }
|
||||
|
||||
// Canonical returns a key identifying what the template matches: two templates
|
||||
// with the same canonical form accept exactly the same paths and differ only in
|
||||
// param names. Literals are length-prefixed so that a literal brace cannot forge
|
||||
// a convertor - "/x/{:str}" is literal text, and must not collide with "/x/{a}".
|
||||
func (t *Template) Canonical() string {
|
||||
var b strings.Builder
|
||||
for _, e := range t.elements {
|
||||
if e.kind == kindLiteral {
|
||||
b.WriteByte('L')
|
||||
b.WriteString(strconv.Itoa(len(e.lit)))
|
||||
b.WriteByte(':')
|
||||
b.WriteString(e.lit)
|
||||
continue
|
||||
}
|
||||
b.WriteByte('K')
|
||||
b.WriteString(strconv.Itoa(int(e.kind)))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Ambiguous reports whether the template's own shape can force the matcher to
|
||||
// backtrack - a param a following literal can extend, adjacent params, or a
|
||||
// non-final path convertor - independent of what other templates put in the
|
||||
// tree.
|
||||
func (t *Template) Ambiguous() bool {
|
||||
for i, e := range t.elements {
|
||||
if e.kind == kindLiteral || e.kind == kindUUID {
|
||||
continue
|
||||
}
|
||||
if i == len(t.elements)-1 {
|
||||
continue // terminal: only the greedy run can reach the end
|
||||
}
|
||||
next := t.elements[i+1]
|
||||
if next.kind != kindLiteral || e.kind.charset(next.lit[0]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseTemplate parses a starlette path template. Custom convertors are
|
||||
// rejected: only the five built-ins may travel over the wire.
|
||||
func ParseTemplate(path string) (*Template, error) {
|
||||
|
||||
Reference in New Issue
Block a user