mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-21 21:54:52 -04:00
Three copies of "does this filter match this subject" lived in the tree: one in testutil.FakeBus, a byte-identical second inside galleryop's own private fakeBus, and a third in pkg/natsauth with different semantics. The first two are doubles the specs publish through, and the carrier the pgbus work is about to add needs the same rule in production. Two spellings drift, and the drift reads as a peer that receives an event on one replica and not on another. messaging.SubjectMatches is now the only definition either double uses. The natsauth copy stays: it matches a NATS server allow list, so it has to implement the '>' tail wildcard this one deliberately refuses, and Task 16 deletes that package anyway. '>' is refused rather than implemented because no surviving subscription uses it, and a caller who writes one must get no messages rather than silently getting every message on the prefix. The refusal is checked BEFORE the filter == subject fast path: a verbatim port checks equality first, and then the filter "a.>" matches the literal subject "a.>", which is the contract leaking. One table row pins that ordering and it is the only row that does. messaging.ValidFilter refuses an empty filter, a '>' filter and an empty token so a subscriber is told at subscribe time instead of staying silently empty for the life of the process. FakeBus.Subscribe calls it, which is what keeps the double honest about what the carrier will do, and three new testutil specs pin that wiring: the previous state of the tree had no spec at all that failed when the double's wildcard routing was replaced by exact matching in any package the plan named. Assisted-by: Claude Opus 5 [claude-code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
72 lines
2.5 KiB
Go
72 lines
2.5 KiB
Go
package messaging
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ErrUnsupportedFilter is the class every ValidFilter rejection belongs to, so a
|
|
// carrier can tell "this caller asked for something we do not implement" apart
|
|
// from "the store is unhappy" without string matching.
|
|
var ErrUnsupportedFilter = errors.New("unsupported subject filter")
|
|
|
|
// SubjectMatches reports whether a subscription filter matches a concrete
|
|
// subject, honoring the single-token `*` wildcard used by NATS.
|
|
//
|
|
// This is the single definition of subject matching for the whole tree: the
|
|
// carrier that delivers messages in production and the in-memory doubles the
|
|
// specs publish through both call it. When those were separate copies, a filter
|
|
// could match on one and not the other, and the difference read as a peer that
|
|
// received an event on one replica and missed it on another.
|
|
//
|
|
// A `>` tail wildcard is deliberately NOT implemented and makes the filter
|
|
// match nothing, so a caller who writes one receives no messages rather than
|
|
// silently receiving every message on the prefix. The refusal is checked before
|
|
// the exact-equality fast path, otherwise `a.>` would still match the literal
|
|
// subject `a.>`.
|
|
func SubjectMatches(filter, subject string) bool {
|
|
// sanitizeSubjectToken folds '>' out of every generated token, so a '>'
|
|
// anywhere in a filter can only be a hand-written tail wildcard.
|
|
if strings.Contains(filter, ">") {
|
|
return false
|
|
}
|
|
if filter == subject {
|
|
return true
|
|
}
|
|
fp := strings.Split(filter, ".")
|
|
sp := strings.Split(subject, ".")
|
|
if len(fp) != len(sp) {
|
|
return false
|
|
}
|
|
for i := range fp {
|
|
if fp[i] == "*" {
|
|
continue
|
|
}
|
|
if fp[i] != sp[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ValidFilter reports whether a filter is one SubjectMatches can act on, so a
|
|
// subscriber is refused at subscribe time instead of staying silently empty for
|
|
// the life of the process. It rejects an empty filter, a filter containing the
|
|
// unimplemented `>` tail wildcard, and a filter with an empty token (`a..b`),
|
|
// which no subject generator can ever produce.
|
|
func ValidFilter(filter string) error {
|
|
if filter == "" {
|
|
return fmt.Errorf("%w: filter is empty", ErrUnsupportedFilter)
|
|
}
|
|
if strings.Contains(filter, ">") {
|
|
return fmt.Errorf("%w: %q uses the tail wildcard '>', which is not implemented", ErrUnsupportedFilter, filter)
|
|
}
|
|
for _, token := range strings.Split(filter, ".") {
|
|
if token == "" {
|
|
return fmt.Errorf("%w: %q has an empty token", ErrUnsupportedFilter, filter)
|
|
}
|
|
}
|
|
return nil
|
|
}
|