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: There is currently a blanket requirement that subject transforms destinations MUST use ALL of the partial wildcards defined in the source of the transform. This is because the subject transformed defined for imports must be 'reversible' and therefore the destination transform must use all of the partial wildcard tokens defined in the source of the transform. This reversing of a transform is only used for transforms used by imports, where in any case it doesn't make sense to use any transform other than Wildcard. This PR: - relaxes this requirement to only apply when the transform is used by an import, adding the ability to drop a wildcard token in transforms other than as part of an import. - Improves transform reverse to support both legacy style wildcards $X and the new transform function {{Wildcard(X)}}- Improves reversible transform checking to only allow the use of wildcards in the destination. --------- Signed-off-by: Jean-Noël Moyne <jnmoyne@gmail.com>
201 lines
8.0 KiB
Go
201 lines
8.0 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", "", false)
|
|
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
|
|
|
|
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)
|
|
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", "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
|
|
}
|