diff --git a/pkg/policy/encode.go b/pkg/policy/encode.go new file mode 100644 index 000000000..0f08afe95 --- /dev/null +++ b/pkg/policy/encode.go @@ -0,0 +1,163 @@ +package policy + +import ( + "fmt" + "strconv" + "strings" + + "github.com/nspcc-dev/neofs-api-go/v2/netmap" +) + +func Encode(p *netmap.PlacementPolicy) []string { + if p == nil { + return nil + } + + var ( + replicas = p.GetReplicas() + selectors = p.GetSelectors() + filters = p.GetFilters() + ) + + // 1 for container backup factor + result := make([]string, 0, len(replicas)+len(selectors)+len(filters)+1) + + // first print replicas + encodeReplicas(replicas, &result) + + // then backup factor + if backupFactor := p.GetContainerBackupFactor(); backupFactor != 0 { + result = append(result, fmt.Sprintf("CBF %d", backupFactor)) + } + + // then selectors + encodeSelectors(selectors, &result) + + // then filters + encodeFilters(filters, &result) + + return result +} + +func encodeReplicas(replicas []*netmap.Replica, dst *[]string) { + builder := new(strings.Builder) + + for _, replica := range replicas { + builder.WriteString("REP ") + builder.WriteString(strconv.FormatUint(uint64(replica.GetCount()), 10)) + + if s := replica.GetSelector(); s != "" { + builder.WriteString(" IN ") + builder.WriteString(s) + } + + *dst = append(*dst, builder.String()) + builder.Reset() + } +} + +func encodeSelectors(selectors []*netmap.Selector, dst *[]string) { + builder := new(strings.Builder) + + for _, selector := range selectors { + builder.WriteString("SELECT ") + builder.WriteString(strconv.FormatUint(uint64(selector.GetCount()), 10)) + + if a := selector.GetAttribute(); a != "" { + builder.WriteString(" IN") + + switch selector.GetClause() { + case netmap.Same: + builder.WriteString(" SAME ") + case netmap.Distinct: + builder.WriteString(" DISTINCT ") + default: + builder.WriteString(" ") + } + + builder.WriteString(a) + } + + if f := selector.GetFilter(); f != "" { + builder.WriteString(" FROM ") + builder.WriteString(f) + } + + if n := selector.GetName(); n != "" { + builder.WriteString(" AS ") + builder.WriteString(n) + } + + *dst = append(*dst, builder.String()) + builder.Reset() + } +} + +func encodeFilters(filters []*netmap.Filter, dst *[]string) { + builder := new(strings.Builder) + + for _, filter := range filters { + builder.WriteString("FILTER ") + + builder.WriteString(encodeFilter(filter)) + + *dst = append(*dst, builder.String()) + builder.Reset() + } +} + +func operationString(operation netmap.Operation) string { + switch operation { + case netmap.EQ: + return "EQ" + case netmap.NE: + return "NE" + case netmap.GT: + return "GT" + case netmap.GE: + return "GE" + case netmap.LT: + return "LT" + case netmap.LE: + return "LE" + case netmap.OR: + return "OR" + case netmap.AND: + return "AND" + default: + return "" + } +} + +func encodeFilter(filter *netmap.Filter) string { + builder := new(strings.Builder) + unspecified := filter.GetOp() == netmap.UnspecifiedOperation + + if k := filter.GetKey(); k != "" { + builder.WriteString(k) + builder.WriteString(" ") + builder.WriteString(operationString(filter.GetOp())) + builder.WriteString(" ") + builder.WriteString(filter.GetValue()) + } else if n := filter.GetName(); unspecified && n != "" { + builder.WriteString("@") + builder.WriteString(n) + } + + for i, subfilter := range filter.GetFilters() { + if i != 0 { + builder.WriteString(" ") + builder.WriteString(operationString(filter.GetOp())) + builder.WriteString(" ") + } + + builder.WriteString(encodeFilter(subfilter)) + } + + if n := filter.GetName(); n != "" && !unspecified { + builder.WriteString(" AS ") + builder.WriteString(n) + } + + return builder.String() +} diff --git a/pkg/policy/encode_test.go b/pkg/policy/encode_test.go new file mode 100644 index 000000000..968c69039 --- /dev/null +++ b/pkg/policy/encode_test.go @@ -0,0 +1,36 @@ +package policy_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/policy" + "github.com/stretchr/testify/require" +) + +func TestEncode(t *testing.T) { + testCases := []string{ + `REP 1 IN X +CBF 1 +SELECT 2 IN SAME Location FROM * AS X`, + + `REP 1 +SELECT 2 IN City FROM Good +FILTER Country EQ RU AS FromRU +FILTER @FromRU AND Rating GT 7 AS Good`, + + `REP 7 IN SPB +SELECT 1 IN City FROM SPBSSD AS SPB +FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`, + } + + for _, testCase := range testCases { + q, err := policy.Parse(testCase) + require.NoError(t, err) + + got := policy.Encode(q) + fmt.Println(strings.Join(got, "\n")) + require.Equal(t, testCase, strings.Join(got, "\n")) + } +}