frostfs-api-go/pkg/netmap/filter.go
Evgenii Stratonikov 2026473733 [#137] sdk: Implement netmap filtering and selection
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
2020-09-18 10:45:11 +03:00

129 lines
3.2 KiB
Go

package netmap
import (
"fmt"
"strconv"
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
)
// MainFilterName is a name of the filter
// which points to the whole netmap.
const MainFilterName = "*"
// applyFilter applies named filter to b.
func (c *Context) applyFilter(name string, b *Node) bool {
return name == MainFilterName || c.match(c.Filters[name], b)
}
// processFilters processes filters and returns error is any of them is invalid.
func (c *Context) processFilters(p *netmap.PlacementPolicy) error {
for _, f := range p.GetFilters() {
if err := c.processFilter(f, true); err != nil {
return err
}
}
return nil
}
func (c *Context) processFilter(f *netmap.Filter, top bool) error {
if f == nil {
return fmt.Errorf("%w: FILTER", ErrMissingField)
}
if f.GetName() == MainFilterName {
return fmt.Errorf("%w: '*' is reserved", ErrInvalidFilterName)
}
if top && f.GetName() == "" {
return ErrUnnamedTopFilter
}
if !top && f.GetName() != "" && c.Filters[f.GetName()] == nil {
return fmt.Errorf("%w: '%s'", ErrFilterNotFound, f.GetName())
}
switch f.GetOp() {
case netmap.AND, netmap.OR:
for _, flt := range f.GetFilters() {
if err := c.processFilter(flt, false); err != nil {
return err
}
}
default:
if len(f.GetFilters()) != 0 {
return ErrNonEmptyFilters
} else if !top && f.GetName() != "" { // named reference
return nil
}
switch f.GetOp() {
case netmap.EQ, netmap.NE:
case netmap.GT, netmap.GE, netmap.LT, netmap.LE:
n, err := strconv.ParseUint(f.GetValue(), 10, 64)
if err != nil {
return fmt.Errorf("%w: '%s'", ErrInvalidNumber, f.GetValue())
}
c.numCache[f] = n
default:
return fmt.Errorf("%w: %d", ErrInvalidFilterOp, f.GetOp())
}
}
if top {
c.Filters[f.GetName()] = f
}
return nil
}
// match matches f against b. It returns no errors because
// filter should have been parsed during context creation
// and missing node properties are considered as a regular fail.
func (c *Context) match(f *netmap.Filter, b *Node) bool {
switch f.GetOp() {
case netmap.AND, netmap.OR:
for _, lf := range f.GetFilters() {
if lf.GetName() != "" {
lf = c.Filters[lf.GetName()]
}
ok := c.match(lf, b)
if ok == (f.GetOp() == netmap.OR) {
return ok
}
}
return f.GetOp() == netmap.AND
default:
return c.matchKeyValue(f, b)
}
}
func (c *Context) matchKeyValue(f *netmap.Filter, b *Node) bool {
switch f.GetOp() {
case netmap.EQ:
return b.Attribute(f.GetKey()) == f.GetValue()
case netmap.NE:
return b.Attribute(f.GetKey()) != f.GetValue()
default:
var attr uint64
switch f.GetKey() {
case PriceAttr:
attr = b.Price
case CapacityAttr:
attr = b.Capacity
default:
var err error
attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64)
if err != nil {
// Note: because filters are somewhat independent from nodes attributes,
// We don't report an error here, and fail filter instead.
return false
}
}
switch f.GetOp() {
case netmap.GT:
return attr > c.numCache[f]
case netmap.GE:
return attr >= c.numCache[f]
case netmap.LT:
return attr < c.numCache[f]
case netmap.LE:
return attr <= c.numCache[f]
}
}
// will not happen if context was created from f (maybe panic?)
return false
}