ficha/matcher.go

92 lines
2.2 KiB
Go

package ficha
// Matcher is a composable predicate evaluated against a Token.
// Use the package-level constructors All, Any, Not, Perm, and Check
// (on Token) to combine them.
type Matcher interface {
matches(t *Token) bool
}
// Perm returns a Matcher that checks for a single permission.
func Perm(p string) Matcher { return permMatcher(p) }
// All returns a Matcher that holds when every child matcher holds.
// Permission strings are accepted directly and treated as Perm(s).
// All() with no arguments holds (vacuous truth).
func All(matchers ...any) Matcher { return andMatcher(toMatchers(matchers)) }
// Any returns a Matcher that holds when at least one child matcher holds.
// Any() with no arguments does not hold.
func Any(matchers ...any) Matcher { return orMatcher(toMatchers(matchers)) }
// Not returns a Matcher that holds when the inner matcher does not.
// Strings are accepted and treated as Perm(s).
func Not(m any) Matcher {
return notMatcher{inner: toMatcher(m)}
}
// Check evaluates a Matcher against the token.
func (t *Token) Check(m Matcher) bool {
if m == nil {
return false
}
return m.matches(t)
}
// --- internal matcher implementations ---
type permMatcher string
func (p permMatcher) matches(t *Token) bool { return t.Has(string(p)) }
type andMatcher []Matcher
func (a andMatcher) matches(t *Token) bool {
for _, m := range a {
if !m.matches(t) {
return false
}
}
return true
}
type orMatcher []Matcher
func (o orMatcher) matches(t *Token) bool {
for _, m := range o {
if m.matches(t) {
return true
}
}
return false
}
type notMatcher struct{ inner Matcher }
func (n notMatcher) matches(t *Token) bool { return !n.inner.matches(t) }
// toMatcher accepts either a string (treated as Perm) or a Matcher.
// Anything else produces a matcher that never holds — defensive but
// not panicking, since Matcher trees are often built dynamically.
func toMatcher(x any) Matcher {
switch v := x.(type) {
case string:
return permMatcher(v)
case Matcher:
return v
default:
return alwaysFalse{}
}
}
func toMatchers(xs []any) []Matcher {
out := make([]Matcher, len(xs))
for i, x := range xs {
out[i] = toMatcher(x)
}
return out
}
type alwaysFalse struct{}
func (alwaysFalse) matches(*Token) bool { return false }