mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
- [X] Tests added - [X] Branch rebased on top of current main (`git pull --rebase origin main`) - [X] Changes squashed to a single commit (described [here](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)) - [X] Build is green in Travis CI - [X] You have certified that the contribution is your original work and that you license the work to the project under the [Apache 2 license](https://github.com/nats-io/nats-server/blob/main/LICENSE) ### Changes proposed in this pull request: Adds support for multi-filter (and associated transform destinations) to stream sources --------- Signed-off-by: Jean-Noël Moyne <jnmoyne@gmail.com>
204 lines
8.1 KiB
Go
204 lines
8.1 KiB
Go
// Copyright 2023 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestPlaceHolderIndex(t *testing.T) {
|
|
testString := "$1"
|
|
transformType, indexes, nbPartitions, _, err := indexPlaceHolders(testString)
|
|
var position int32
|
|
|
|
if err != nil || transformType != Wildcard || len(indexes) != 1 || indexes[0] != 1 || nbPartitions != -1 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
|
|
testString = "{{partition(10,1,2,3)}}"
|
|
|
|
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
|
|
|
|
if err != nil || transformType != Partition || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
|
|
testString = "{{ Partition (10,1,2,3) }}"
|
|
|
|
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
|
|
|
|
if err != nil || transformType != Partition || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
|
|
testString = "{{wildcard(2)}}"
|
|
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
|
|
|
|
if err != nil || transformType != Wildcard || len(indexes) != 1 || indexes[0] != 2 || nbPartitions != -1 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
|
|
testString = "{{SplitFromLeft(2,1)}}"
|
|
transformType, indexes, position, _, err = indexPlaceHolders(testString)
|
|
|
|
if err != nil || transformType != SplitFromLeft || len(indexes) != 1 || indexes[0] != 2 || position != 1 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
|
|
testString = "{{SplitFromRight(3,2)}}"
|
|
transformType, indexes, position, _, err = indexPlaceHolders(testString)
|
|
|
|
if err != nil || transformType != SplitFromRight || len(indexes) != 1 || indexes[0] != 3 || position != 2 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
|
|
testString = "{{SliceFromLeft(2,2)}}"
|
|
transformType, indexes, sliceSize, _, err := indexPlaceHolders(testString)
|
|
|
|
if err != nil || transformType != SliceFromLeft || len(indexes) != 1 || indexes[0] != 2 || sliceSize != 2 {
|
|
t.Fatalf("Error parsing %s", testString)
|
|
}
|
|
}
|
|
|
|
func TestSubjectTransformHelpers(t *testing.T) {
|
|
equals := func(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i, v := range a {
|
|
if v != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
filter, placeHolders := transformUntokenize("bar")
|
|
if filter != "bar" || len(placeHolders) != 0 {
|
|
t.Fatalf("transformUntokenize for not returning expected result")
|
|
}
|
|
|
|
filter, placeHolders = transformUntokenize("foo.$2.$1")
|
|
if filter != "foo.*.*" || !equals(placeHolders, []string{"$2", "$1"}) {
|
|
t.Fatalf("transformUntokenize for not returning expected result")
|
|
}
|
|
|
|
filter, placeHolders = transformUntokenize("foo.{{wildcard(2)}}.{{wildcard(1)}}")
|
|
if filter != "foo.*.*" || !equals(placeHolders, []string{"{{wildcard(2)}}", "{{wildcard(1)}}"}) {
|
|
t.Fatalf("transformUntokenize for not returning expected result")
|
|
}
|
|
|
|
newReversibleTransform := func(src, dest string) *subjectTransform {
|
|
tr, err := NewSubjectTransformStrict(src, dest)
|
|
if err != nil {
|
|
t.Fatalf("Error getting reversible transform: %s to %s", src, dest)
|
|
}
|
|
return tr
|
|
}
|
|
|
|
tr := newReversibleTransform("foo.*.*", "bar.$2.{{Wildcard(1)}}")
|
|
subject := "foo.b.a"
|
|
transformed := tr.TransformSubject(subject)
|
|
reverse := tr.reverse()
|
|
if reverse.TransformSubject(transformed) != subject {
|
|
t.Fatal("Reversed transform subject not matching")
|
|
}
|
|
}
|
|
|
|
func TestSubjectTransforms(t *testing.T) {
|
|
shouldErr := func(src, dest string, strict bool) {
|
|
t.Helper()
|
|
if _, err := NewSubjectTransformWithStrict(src, dest, strict); err != ErrBadSubject && !errors.Is(err, ErrInvalidMappingDestination) {
|
|
t.Fatalf("Did not get an error for src=%q and dest=%q", src, dest)
|
|
}
|
|
}
|
|
|
|
// Must be valid subjects.
|
|
shouldErr("foo..", "bar", false)
|
|
|
|
// Wildcards are allowed in src, but must be matched by token placements on the other side.
|
|
// e.g. foo.* -> bar.$1.
|
|
// Need to have as many pwcs as placements on other side
|
|
|
|
shouldErr("foo.*", "bar.*", false)
|
|
shouldErr("foo.*", "bar.$2", false) // Bad pwc token identifier
|
|
shouldErr("foo.*", "bar.$1.>", false) // fwcs have to match.
|
|
shouldErr("foo.>", "bar.baz", false) // fwcs have to match.
|
|
shouldErr("foo.*.*", "bar.$2", true) // Must place all pwcs.
|
|
shouldErr("foo.*", "foo.$foo", true) // invalid $ value
|
|
shouldErr("foo.*", "bar.{{Partition(2,1)}}", true) // can only use Wildcard function (and old-style $x) in import transform
|
|
shouldErr("foo.*", "foo.{{wildcard(2)}}", false) // Mapping function being passed an out of range wildcard index
|
|
shouldErr("foo.*", "foo.{{unimplemented(1)}}", false) // Mapping trying to use an unknown mapping function
|
|
shouldErr("foo.*", "foo.{{partition(10)}}", false) // Not enough arguments passed to the mapping function
|
|
shouldErr("foo.*", "foo.{{wildcard(foo)}}", false) // Invalid argument passed to the mapping function
|
|
shouldErr("foo.*", "foo.{{wildcard()}}", false) // Not enough arguments passed to the mapping function
|
|
shouldErr("foo.*", "foo.{{wildcard(1,2)}}", false) // Too many arguments passed to the mapping function
|
|
shouldErr("foo.*", "foo.{{ wildcard5) }}", false) // Bad mapping function
|
|
shouldErr("foo.*", "foo.{{splitLeft(2,2}}", false) // arg out of range
|
|
shouldErr("foo", "bla.{{wildcard(1)}}", false) // arg out of range with no wildcard in the source
|
|
|
|
shouldBeOK := func(src, dest string, strict bool) *subjectTransform {
|
|
t.Helper()
|
|
tr, err := NewSubjectTransformWithStrict(src, dest, strict)
|
|
if err != nil {
|
|
t.Fatalf("Got an error %v for src=%q and dest=%q", err, src, dest)
|
|
}
|
|
return tr
|
|
}
|
|
|
|
shouldBeOK("foo.*", "bar.{{Wildcard(1)}}", true)
|
|
|
|
shouldBeOK("foo.*.*", "bar.$2", false) // don't have to use all pwcs.
|
|
shouldBeOK("foo.*.*", "bar.{{wildcard(1)}}", false) // don't have to use all pwcs.
|
|
shouldBeOK("foo", "bar", false)
|
|
shouldBeOK("foo.*.bar.*.baz", "req.$2.$1", false)
|
|
shouldBeOK("baz.>", "mybaz.>", false)
|
|
shouldBeOK("*", "{{splitfromleft(1,1)}}", false)
|
|
shouldBeOK("", "prefix.>", false)
|
|
shouldBeOK("*.*", "{{partition(10,1,2)}}", false)
|
|
shouldBeOK("foo.*.*", "foo.{{wildcard(1)}}.{{wildcard(2)}}.{{partition(5,1,2)}}", false)
|
|
|
|
shouldMatch := func(src, dest, sample, expected string) {
|
|
t.Helper()
|
|
tr := shouldBeOK(src, dest, false)
|
|
if tr != nil {
|
|
s, err := tr.Match(sample)
|
|
if err != nil {
|
|
t.Fatalf("Got an error %v when expecting a match for %q to %q", err, sample, expected)
|
|
}
|
|
if s != expected {
|
|
t.Fatalf("Dest does not match what was expected. Got %q, expected %q", s, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
shouldMatch("", "prefix.>", "foo", "prefix.foo")
|
|
shouldMatch("foo", "", "foo", "foo")
|
|
shouldMatch("foo", "bar", "foo", "bar")
|
|
shouldMatch("foo.*.bar.*.baz", "req.$2.$1", "foo.A.bar.B.baz", "req.B.A")
|
|
shouldMatch("foo.*.bar.*.baz", "req.{{wildcard(2)}}.{{wildcard(1)}}", "foo.A.bar.B.baz", "req.B.A")
|
|
shouldMatch("baz.>", "my.pre.>", "baz.1.2.3", "my.pre.1.2.3")
|
|
shouldMatch("baz.>", "foo.bar.>", "baz.1.2.3", "foo.bar.1.2.3")
|
|
shouldMatch("*", "foo.bar.$1", "foo", "foo.bar.foo")
|
|
shouldMatch("*", "{{splitfromleft(1,3)}}", "12345", "123.45")
|
|
shouldMatch("*", "{{SplitFromRight(1,3)}}", "12345", "12.345")
|
|
shouldMatch("*", "{{SliceFromLeft(1,3)}}", "1234567890", "123.456.789.0")
|
|
shouldMatch("*", "{{SliceFromRight(1,3)}}", "1234567890", "1.234.567.890")
|
|
shouldMatch("*", "{{split(1,-)}}", "-abc-def--ghi-", "abc.def.ghi")
|
|
shouldMatch("*", "{{split(1,-)}}", "abc-def--ghi-", "abc.def.ghi")
|
|
shouldMatch("*.*", "{{split(2,-)}}.{{splitfromleft(1,2)}}", "foo.-abc-def--ghij-", "abc.def.ghij.fo.o") // combo + checks split for multiple instance of deliminator and deliminator being at the start or end
|
|
}
|