mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
- Changes to make adding new mapping functions easier (#3305)
* - Changes to make adding new mapping functions easier
- Adds new subject mapping functions:
{{SplitFromLeft(wildcard index, position)}}
{{SplitFromRight(wildcard index, position)}}
{{SliceFromLeft(wildcard index, slice size)}}
{{SliceFromRight(wildcard index, slice size)}}
{{Split(wildcard index, deliminator)}}
Examples:
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(2,-)}}.{{splitfromleft(1,2)}}", "foo.-abc-def--ghij-", "abc.def.ghij.fo.o")
- Subject mapping functions can now be all lower case or Pascal case (or a combination): e.g. splitfromleft, SplitFromLeft, splitFromleft, etc...
This commit is contained in:
@@ -166,8 +166,26 @@ const (
|
||||
)
|
||||
|
||||
var commaSeparatorRegEx = regexp.MustCompile(`,\s*`)
|
||||
var partitionMappingFunctionRegEx = regexp.MustCompile(`{{\s*partition\s*\((.*)\)\s*}}`)
|
||||
var wildcardMappingFunctionRegEx = regexp.MustCompile(`{{\s*wildcard\s*\((.*)\)\s*}}`)
|
||||
var partitionMappingFunctionRegEx = regexp.MustCompile(`{{\s*[pP]artition\s*\((.*)\)\s*}}`)
|
||||
var wildcardMappingFunctionRegEx = regexp.MustCompile(`{{\s*[wW]ildcard\s*\((.*)\)\s*}}`)
|
||||
var splitFromLeftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit[fF]rom[lL]eft\s*\((.*)\)\s*}}`)
|
||||
var splitFromRightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit[fF]rom[rR]ight\s*\((.*)\)\s*}}`)
|
||||
var sliceFromLeftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]lice[fF]rom[lL]eft\s*\((.*)\)\s*}}`)
|
||||
var sliceFromRightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]lice[fF]rom[rR]ight\s*\((.*)\)\s*}}`)
|
||||
var splitMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit\s*\((.*)\)\s*}}`)
|
||||
|
||||
// Enum for the subject mapping transform function types
|
||||
const (
|
||||
NoTransform int16 = iota
|
||||
BadTransform
|
||||
Partition
|
||||
Wildcard
|
||||
SplitFromLeft
|
||||
SplitFromRight
|
||||
SliceFromLeft
|
||||
SliceFromRight
|
||||
Split
|
||||
)
|
||||
|
||||
// String helper.
|
||||
func (rt ServiceRespType) String() string {
|
||||
@@ -845,7 +863,7 @@ func (a *Account) selectMappedSubject(dest string) (string, bool) {
|
||||
}
|
||||
|
||||
if d != nil {
|
||||
if len(d.tr.dtpi) == 0 {
|
||||
if len(d.tr.dtokmftokindexesargs) == 0 {
|
||||
ndest = d.tr.dest
|
||||
} else if nsubj, err := d.tr.transform(tts); err == nil {
|
||||
ndest = nsubj
|
||||
@@ -948,7 +966,7 @@ func (a *Account) removeClient(c *client) int {
|
||||
|
||||
func setExportAuth(ea *exportAuth, subject string, accounts []*Account, accountPos uint) error {
|
||||
if accountPos > 0 {
|
||||
token := strings.Split(subject, ".")
|
||||
token := strings.Split(subject, tsep)
|
||||
if len(token) < int(accountPos) || token[accountPos-1] != "*" {
|
||||
return ErrInvalidSubject
|
||||
}
|
||||
@@ -2442,7 +2460,7 @@ func (a *Account) AddStreamExport(subject string, accounts []*Account) error {
|
||||
// AddStreamExport will add an export to the account. If accounts is nil
|
||||
// it will signify a public export, meaning anyone can import.
|
||||
// if accountPos is > 0, all imports will be granted where the following holds:
|
||||
// strings.Split(subject, ".")[accountPos] == account id will be granted.
|
||||
// strings.Split(subject, tsep)[accountPos] == account id will be granted.
|
||||
func (a *Account) addStreamExportWithAccountPos(subject string, accounts []*Account, accountPos uint) error {
|
||||
if a == nil {
|
||||
return ErrMissingAccount
|
||||
@@ -4166,11 +4184,13 @@ func (dr *CacheDirAccResolver) Reload() error {
|
||||
// These can also be used for proper mapping on wildcard exports/imports.
|
||||
// These will be grouped and caching and locking are assumed to be in the upper layers.
|
||||
type transform struct {
|
||||
src, dest string
|
||||
dtoks []string
|
||||
stoks []string
|
||||
dtpi [][]int // destination token position indexes
|
||||
dtpinp []int32 // destination token position index number of partitions
|
||||
src, dest string
|
||||
dtoks []string // destination tokens
|
||||
stoks []string // source tokens
|
||||
dtokmftypes []int16 // destination token mapping function types
|
||||
dtokmftokindexesargs [][]int // destination token mapping function array of source token index arguments
|
||||
dtokmfintargs []int32 // destination token mapping function int32 arguments
|
||||
dtokmfstringargs []string // destination token mapping function string arguments
|
||||
}
|
||||
|
||||
func getMappingFunctionArgs(functionRegEx *regexp.Regexp, token string) []string {
|
||||
@@ -4181,17 +4201,40 @@ func getMappingFunctionArgs(functionRegEx *regexp.Regexp, token string) []string
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to pull raw place holder indexes and number of partitions. Returns -1 if not a place holder.
|
||||
func placeHolderIndex(token string) ([]int, int32, error) {
|
||||
// Helper for mapping functions that take a wildcard index and an integer as arguments
|
||||
func transformIndexIntArgsHelper(token string, args []string, transformType int16) (int16, []int, int32, string, error) {
|
||||
if len(args) < 2 {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionNotEnoughArguments}
|
||||
}
|
||||
if len(args) > 2 {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionTooManyArguments}
|
||||
}
|
||||
i, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||
if err != nil {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[1], " "))
|
||||
if err != nil {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
|
||||
return transformType, []int{i}, int32(mappingFunctionIntArg), _EMPTY_, nil
|
||||
}
|
||||
|
||||
// Helper to ingest and index the transform destination token (e.g. $x or {{}}) in the token
|
||||
// returns a transformation type, and three function arguments: an array of source subject token indexes, and a single number (e.g. number of partitions, or a slice size), and a string (e.g.a split delimiter)
|
||||
|
||||
func indexPlaceHolders(token string) (int16, []int, int32, string, error) {
|
||||
length := len(token)
|
||||
if length > 1 {
|
||||
// old $1, $2, etc... mapping format still supported to maintain backwards compatibility
|
||||
if token[0] == '$' { // simple non-partition mapping
|
||||
tp, err := strconv.Atoi(token[1:])
|
||||
if err != nil {
|
||||
return []int{-1}, -1, nil
|
||||
// other things rely on tokens starting with $ so not an error just leave it as is
|
||||
return NoTransform, []int{-1}, -1, _EMPTY_, nil
|
||||
}
|
||||
return []int{tp}, -1, nil
|
||||
return Wildcard, []int{tp}, -1, _EMPTY_, nil
|
||||
}
|
||||
|
||||
// New 'mustache' style mapping
|
||||
@@ -4200,45 +4243,92 @@ func placeHolderIndex(token string) ([]int, int32, error) {
|
||||
args := getMappingFunctionArgs(wildcardMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
if len(args) == 1 && args[0] == _EMPTY_ {
|
||||
return []int{-1}, -1, &mappingDestinationErr{token, ErrorMappingDestinationFunctionNotEnoughArguments}
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionNotEnoughArguments}
|
||||
}
|
||||
if len(args) == 1 {
|
||||
tp, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||
tokenIndex, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||
if err != nil {
|
||||
return []int{}, -1, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
return []int{tp}, -1, nil
|
||||
return Wildcard, []int{tokenIndex}, -1, _EMPTY_, nil
|
||||
} else {
|
||||
return []int{}, -1, &mappingDestinationErr{token, ErrorMappingDestinationFunctionTooManyArguments}
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionTooManyArguments}
|
||||
}
|
||||
}
|
||||
|
||||
// partition(number of partitions, token1, token2, ...)
|
||||
args = getMappingFunctionArgs(partitionMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
if len(args) < 2 {
|
||||
return []int{-1}, -1, &mappingDestinationErr{token, ErrorMappingDestinationFunctionNotEnoughArguments}
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionNotEnoughArguments}
|
||||
}
|
||||
if len(args) >= 2 {
|
||||
tphnp, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||
mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||
if err != nil {
|
||||
return []int{}, -1, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
var numPositions = len(args[1:])
|
||||
tps := make([]int, numPositions)
|
||||
tokenIndexes := make([]int, numPositions)
|
||||
for ti, t := range args[1:] {
|
||||
i, err := strconv.Atoi(strings.Trim(t, " "))
|
||||
if err != nil {
|
||||
return []int{}, -1, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
tps[ti] = i
|
||||
tokenIndexes[ti] = i
|
||||
}
|
||||
return tps, int32(tphnp), nil
|
||||
|
||||
return Partition, tokenIndexes, int32(mappingFunctionIntArg), _EMPTY_, nil
|
||||
}
|
||||
}
|
||||
return []int{}, -1, &mappingDestinationErr{token, ErrUnknownMappingDestinationFunction}
|
||||
|
||||
// SplitFromLeft(token, position)
|
||||
args = getMappingFunctionArgs(splitFromLeftMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
return transformIndexIntArgsHelper(token, args, SplitFromLeft)
|
||||
}
|
||||
|
||||
// SplitFromRight(token, position)
|
||||
args = getMappingFunctionArgs(splitFromRightMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
return transformIndexIntArgsHelper(token, args, SplitFromRight)
|
||||
}
|
||||
|
||||
// SliceFromLeft(token, position)
|
||||
args = getMappingFunctionArgs(sliceFromLeftMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
return transformIndexIntArgsHelper(token, args, SliceFromLeft)
|
||||
}
|
||||
|
||||
// SliceFromRight(token, position)
|
||||
args = getMappingFunctionArgs(sliceFromRightMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
return transformIndexIntArgsHelper(token, args, SliceFromRight)
|
||||
}
|
||||
|
||||
// split(token, deliminator)
|
||||
args = getMappingFunctionArgs(splitMappingFunctionRegEx, token)
|
||||
if args != nil {
|
||||
if len(args) < 2 {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionNotEnoughArguments}
|
||||
}
|
||||
if len(args) > 2 {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionTooManyArguments}
|
||||
}
|
||||
i, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||
if err != nil {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
if strings.Contains(args[1], " ") || strings.Contains(args[1], tsep) {
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token: token, err: ErrorMappingDestinationFunctionInvalidArgument}
|
||||
}
|
||||
|
||||
return Split, []int{i}, -1, args[1], nil
|
||||
}
|
||||
|
||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrUnknownMappingDestinationFunction}
|
||||
}
|
||||
}
|
||||
return []int{-1}, -1, nil
|
||||
return NoTransform, []int{-1}, -1, _EMPTY_, nil
|
||||
}
|
||||
|
||||
// SubjectTransformer transforms subjects using mappings
|
||||
@@ -4266,8 +4356,10 @@ func newTransform(src, dest string) (*transform, error) {
|
||||
return nil, ErrBadSubject
|
||||
}
|
||||
|
||||
var dtpi [][]int
|
||||
var dtpinb []int32
|
||||
var dtokMappingFunctionTypes []int16
|
||||
var dtokMappingFunctionTokenIndexes [][]int
|
||||
var dtokMappingFunctionIntArgs []int32
|
||||
var dtokMappingFunctionStringArgs []string
|
||||
|
||||
// If the src has partial wildcards then the dest needs to have the token place markers.
|
||||
if npwcs > 0 || hasFwc {
|
||||
@@ -4281,25 +4373,31 @@ func newTransform(src, dest string) (*transform, error) {
|
||||
|
||||
nphs := 0
|
||||
for _, token := range dtokens {
|
||||
tp, nb, err := placeHolderIndex(token)
|
||||
tranformType, transformArgWildcardIndexes, transfomArgInt, transformArgString, err := indexPlaceHolders(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp[0] >= 0 {
|
||||
|
||||
if tranformType == NoTransform {
|
||||
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, NoTransform)
|
||||
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{-1})
|
||||
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, -1)
|
||||
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_)
|
||||
} else {
|
||||
nphs++
|
||||
// Now build up our runtime mapping from dest to source tokens.
|
||||
var stis []int
|
||||
for _, position := range tp {
|
||||
if position > npwcs {
|
||||
return nil, &mappingDestinationErr{fmt.Sprintf("%s: [%d]", token, position), ErrorMappingDestinationFunctionWildcardIndexOutOfRange}
|
||||
for _, wildcardIndex := range transformArgWildcardIndexes {
|
||||
if wildcardIndex > npwcs {
|
||||
return nil, &mappingDestinationErr{fmt.Sprintf("%s: [%d]", token, wildcardIndex), ErrorMappingDestinationFunctionWildcardIndexOutOfRange}
|
||||
}
|
||||
stis = append(stis, sti[position])
|
||||
stis = append(stis, sti[wildcardIndex])
|
||||
}
|
||||
dtpi = append(dtpi, stis)
|
||||
dtpinb = append(dtpinb, nb)
|
||||
} else {
|
||||
dtpi = append(dtpi, []int{-1})
|
||||
dtpinb = append(dtpinb, -1)
|
||||
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, tranformType)
|
||||
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, stis)
|
||||
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, transfomArgInt)
|
||||
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, transformArgString)
|
||||
|
||||
}
|
||||
}
|
||||
if nphs < npwcs {
|
||||
@@ -4308,7 +4406,7 @@ func newTransform(src, dest string) (*transform, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return &transform{src: src, dest: dest, dtoks: dtokens, stoks: stokens, dtpi: dtpi, dtpinp: dtpinb}, nil
|
||||
return &transform{src: src, dest: dest, dtoks: dtokens, stoks: stokens, dtokmftypes: dtokMappingFunctionTypes, dtokmftokindexesargs: dtokMappingFunctionTokenIndexes, dtokmfintargs: dtokMappingFunctionIntArgs, dtokmfstringargs: dtokMappingFunctionStringArgs}, nil
|
||||
}
|
||||
|
||||
// Match will take a literal published subject that is associated with a client and will match and transform
|
||||
@@ -4370,41 +4468,110 @@ func (tr *transform) getHashPartition(key []byte, numBuckets int) string {
|
||||
|
||||
// Do a transform on the subject to the dest subject.
|
||||
func (tr *transform) transform(tokens []string) (string, error) {
|
||||
if len(tr.dtpi) == 0 {
|
||||
if len(tr.dtokmftypes) == 0 {
|
||||
return tr.dest, nil
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
var token string
|
||||
|
||||
// We need to walk destination tokens and create the mapped subject pulling tokens from src.
|
||||
// We need to walk destination tokens and create the mapped subject pulling tokens or mapping functions
|
||||
// This is slow and that is ok, transforms should have caching layer in front for mapping transforms
|
||||
// and export/import semantics with streams and services.
|
||||
li := len(tr.dtpi) - 1
|
||||
for i, index := range tr.dtpi {
|
||||
// <0 means use destination token.
|
||||
if index[0] < 0 {
|
||||
token = tr.dtoks[i]
|
||||
li := len(tr.dtokmftypes) - 1
|
||||
for i, mfType := range tr.dtokmftypes {
|
||||
if mfType == NoTransform {
|
||||
// Break if fwc
|
||||
if len(token) == 1 && token[0] == fwc {
|
||||
if len(tr.dtoks[i]) == 1 && tr.dtoks[i][0] == fwc {
|
||||
break
|
||||
}
|
||||
b.WriteString(tr.dtoks[i])
|
||||
} else {
|
||||
// >= 0 means use source map index to figure out which source token to pull.
|
||||
if tr.dtpinp[i] > 0 { // there is a valid (i.e. not -1) value for number of partitions, this is a partition transform token
|
||||
switch mfType {
|
||||
case Partition:
|
||||
var (
|
||||
_buffer [64]byte
|
||||
keyForHashing = _buffer[:0]
|
||||
)
|
||||
for _, sourceToken := range tr.dtpi[i] {
|
||||
for _, sourceToken := range tr.dtokmftokindexesargs[i] {
|
||||
keyForHashing = append(keyForHashing, []byte(tokens[sourceToken])...)
|
||||
}
|
||||
token = tr.getHashPartition(keyForHashing, int(tr.dtpinp[i]))
|
||||
} else { // back to normal substitution
|
||||
token = tokens[tr.dtpi[i][0]]
|
||||
b.WriteString(tr.getHashPartition(keyForHashing, int(tr.dtokmfintargs[i])))
|
||||
case Wildcard: // simple substitution
|
||||
b.WriteString(tokens[tr.dtokmftokindexesargs[i][0]])
|
||||
case SplitFromLeft:
|
||||
sourceToken := tokens[tr.dtokmftokindexesargs[i][0]]
|
||||
sourceTokenLen := len(sourceToken)
|
||||
position := int(tr.dtokmfintargs[i])
|
||||
if position > 0 && position < sourceTokenLen {
|
||||
b.WriteString(sourceToken[:position])
|
||||
b.WriteString(tsep)
|
||||
b.WriteString(sourceToken[position:])
|
||||
} else { // too small to split at the requested position: don't split
|
||||
b.WriteString(sourceToken)
|
||||
}
|
||||
case SplitFromRight:
|
||||
sourceToken := tokens[tr.dtokmftokindexesargs[i][0]]
|
||||
sourceTokenLen := len(sourceToken)
|
||||
position := int(tr.dtokmfintargs[i])
|
||||
if position > 0 && position < sourceTokenLen {
|
||||
b.WriteString(sourceToken[:sourceTokenLen-position])
|
||||
b.WriteString(tsep)
|
||||
b.WriteString(sourceToken[sourceTokenLen-position:])
|
||||
} else { // too small to split at the requested position: don't split
|
||||
b.WriteString(sourceToken)
|
||||
}
|
||||
case SliceFromLeft:
|
||||
sourceToken := tokens[tr.dtokmftokindexesargs[i][0]]
|
||||
sourceTokenLen := len(sourceToken)
|
||||
sliceSize := int(tr.dtokmfintargs[i])
|
||||
if sliceSize > 0 && sliceSize < sourceTokenLen {
|
||||
for i := 0; i+sliceSize <= sourceTokenLen; i += sliceSize {
|
||||
if i != 0 {
|
||||
b.WriteString(tsep)
|
||||
}
|
||||
b.WriteString(sourceToken[i : i+sliceSize])
|
||||
if i+sliceSize != sourceTokenLen && i+sliceSize+sliceSize > sourceTokenLen {
|
||||
b.WriteString(tsep)
|
||||
b.WriteString(sourceToken[i+sliceSize:])
|
||||
break
|
||||
}
|
||||
}
|
||||
} else { // too small to slice at the requested size: don't slice
|
||||
b.WriteString(sourceToken)
|
||||
}
|
||||
case SliceFromRight:
|
||||
sourceToken := tokens[tr.dtokmftokindexesargs[i][0]]
|
||||
sourceTokenLen := len(sourceToken)
|
||||
sliceSize := int(tr.dtokmfintargs[i])
|
||||
if sliceSize > 0 && sliceSize < sourceTokenLen {
|
||||
remainder := sourceTokenLen % sliceSize
|
||||
if remainder > 0 {
|
||||
b.WriteString(sourceToken[:remainder])
|
||||
b.WriteString(tsep)
|
||||
}
|
||||
for i := remainder; i+sliceSize <= sourceTokenLen; i += sliceSize {
|
||||
b.WriteString(sourceToken[i : i+sliceSize])
|
||||
if i+sliceSize < sourceTokenLen {
|
||||
b.WriteString(tsep)
|
||||
}
|
||||
}
|
||||
} else { // too small to slice at the requested size: don't slice
|
||||
b.WriteString(sourceToken)
|
||||
}
|
||||
case Split:
|
||||
sourceToken := tokens[tr.dtokmftokindexesargs[i][0]]
|
||||
splits := strings.Split(sourceToken, tr.dtokmfstringargs[i])
|
||||
for j, split := range splits {
|
||||
if split != _EMPTY_ {
|
||||
b.WriteString(split)
|
||||
}
|
||||
if j < len(splits)-1 && splits[j+1] != _EMPTY_ && j != 0 {
|
||||
b.WriteString(tsep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteString(token)
|
||||
|
||||
if i < li {
|
||||
b.WriteByte(btsep)
|
||||
}
|
||||
@@ -4424,7 +4591,7 @@ func (tr *transform) transform(tokens []string) (string, error) {
|
||||
|
||||
// Reverse a transform.
|
||||
func (tr *transform) reverse() *transform {
|
||||
if len(tr.dtpi) == 0 {
|
||||
if len(tr.dtokmftokindexesargs) == 0 {
|
||||
rtr, _ := newTransform(tr.dest, tr.src)
|
||||
return rtr
|
||||
}
|
||||
|
||||
@@ -50,47 +50,54 @@ func simpleAccountServer(t *testing.T) (*Server, *Account, *Account) {
|
||||
|
||||
func TestPlaceHolderIndex(t *testing.T) {
|
||||
testString := "$1"
|
||||
indexes, nbPartitions, err := placeHolderIndex(testString)
|
||||
transformType, indexes, nbPartitions, _, err := indexPlaceHolders(testString)
|
||||
var position int32
|
||||
|
||||
if err != nil || len(indexes) != 1 || indexes[0] != 1 || nbPartitions != -1 {
|
||||
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)}}"
|
||||
|
||||
indexes, nbPartitions, err = placeHolderIndex(testString)
|
||||
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
|
||||
|
||||
if err != nil || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
|
||||
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) }}"
|
||||
testString = "{{ Partition (10,1,2,3) }}"
|
||||
|
||||
indexes, nbPartitions, err = placeHolderIndex(testString)
|
||||
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
|
||||
|
||||
if err != nil || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
|
||||
t.Fatalf("Error parsing %s", testString)
|
||||
}
|
||||
|
||||
testString = "{{partition (10,1,2,3)}}"
|
||||
|
||||
indexes, nbPartitions, err = placeHolderIndex(testString)
|
||||
|
||||
if err != nil || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
|
||||
if err != nil || transformType != Partition || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
|
||||
t.Fatalf("Error parsing %s", testString)
|
||||
}
|
||||
|
||||
testString = "{{wildcard(2)}}"
|
||||
indexes, nbPartitions, err = placeHolderIndex(testString)
|
||||
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
|
||||
|
||||
if err != nil || len(indexes) != 1 || indexes[0] != 2 || nbPartitions != -1 {
|
||||
if err != nil || transformType != Wildcard || len(indexes) != 1 || indexes[0] != 2 || nbPartitions != -1 {
|
||||
t.Fatalf("Error parsing %s", testString)
|
||||
}
|
||||
|
||||
testString = "{{ wildcard (2) }}"
|
||||
indexes, nbPartitions, err = placeHolderIndex(testString)
|
||||
testString = "{{SplitFromLeft(2,1)}}"
|
||||
transformType, indexes, position, _, err = indexPlaceHolders(testString)
|
||||
|
||||
if err != nil || len(indexes) != 1 || indexes[0] != 2 || nbPartitions != -1 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3290,6 +3297,7 @@ func TestSubjectTransforms(t *testing.T) {
|
||||
shouldErr("foo.*", "foo.{{wildcard()}}") // Not enough arguments passed to the mapping function
|
||||
shouldErr("foo.*", "foo.{{wildcard(1,2)}}") // Too many arguments passed to the mapping function
|
||||
shouldErr("foo.*", "foo.{{ wildcard5) }}") // Bad mapping function
|
||||
shouldErr("foo.*", "foo.{{splitLeft(2,2}}") // arg out of range
|
||||
|
||||
shouldBeOK := func(src, dest string) *transform {
|
||||
t.Helper()
|
||||
@@ -3303,6 +3311,7 @@ func TestSubjectTransforms(t *testing.T) {
|
||||
shouldBeOK("foo", "bar")
|
||||
shouldBeOK("foo.*.bar.*.baz", "req.$2.$1")
|
||||
shouldBeOK("baz.>", "mybaz.>")
|
||||
shouldBeOK("*", "{{splitfromleft(1,1)}}")
|
||||
|
||||
shouldMatch := func(src, dest, sample, expected string) {
|
||||
t.Helper()
|
||||
@@ -3321,6 +3330,12 @@ func TestSubjectTransforms(t *testing.T) {
|
||||
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(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
|
||||
}
|
||||
|
||||
func TestAccountSystemPermsWithGlobalAccess(t *testing.T) {
|
||||
|
||||
@@ -1140,7 +1140,13 @@ func ValidateMappingDestination(subject string) error {
|
||||
}
|
||||
|
||||
if length > 4 && t[0] == '{' && t[1] == '{' && t[length-2] == '}' && t[length-1] == '}' {
|
||||
if !partitionMappingFunctionRegEx.MatchString(t) && !wildcardMappingFunctionRegEx.MatchString(t) {
|
||||
if !partitionMappingFunctionRegEx.MatchString(t) &&
|
||||
!wildcardMappingFunctionRegEx.MatchString(t) &&
|
||||
!splitFromLeftMappingFunctionRegEx.MatchString(t) &&
|
||||
!splitFromRightMappingFunctionRegEx.MatchString(t) &&
|
||||
!sliceFromLeftMappingFunctionRegEx.MatchString(t) &&
|
||||
!sliceFromRightMappingFunctionRegEx.MatchString(t) &&
|
||||
!splitMappingFunctionRegEx.MatchString(t) {
|
||||
return &mappingDestinationErr{t, ErrUnknownMappingDestinationFunction}
|
||||
} else {
|
||||
continue
|
||||
|
||||
@@ -669,6 +669,8 @@ func TestValidateDestinationSubject(t *testing.T) {
|
||||
checkError(ValidateMappingDestination("foo.{{ wildcard(1) }}"), nil, t)
|
||||
checkError(ValidateMappingDestination("foo.{{wildcard( 1 )}}"), nil, t)
|
||||
checkError(ValidateMappingDestination("foo.{{partition(2,1)}}"), nil, t)
|
||||
checkError(ValidateMappingDestination("foo.{{SplitFromLeft(2,1)}}"), nil, t)
|
||||
checkError(ValidateMappingDestination("foo.{{SplitFromRight(2,1)}}"), nil, t)
|
||||
checkError(ValidateMappingDestination("foo.{{unknown(1)}}"), ErrInvalidMappingDestination, t)
|
||||
checkError(ValidateMappingDestination("foo..}"), ErrInvalidMappingDestination, t)
|
||||
checkError(ValidateMappingDestination("foo. bar}"), ErrInvalidMappingDestinationSubject, t)
|
||||
|
||||
Reference in New Issue
Block a user