From 043c614bf88b51629928b6b223d72babc0160d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Moyne?= Date: Thu, 4 Jul 2024 12:14:34 -0700 Subject: [PATCH] Improve error reporting for bad subject mapping transform sources and destinations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add checking of subject transforms in stream configuration for mirroring subject transform and stream subject transform. Add missing JS API Errors. Signed-off-by: Jean-Noël Moyne --- server/accounts.go | 2 +- server/errors.go | 2 +- server/errors.json | 36 ++++++++- server/jetstream_errors_generated.go | 108 +++++++++++++++++++++++---- server/jetstream_test.go | 65 +++++++++++++++- server/stream.go | 35 +++++++-- server/sublist.go | 14 ++-- server/sublist_test.go | 36 ++++----- 8 files changed, 245 insertions(+), 53 deletions(-) diff --git a/server/accounts.go b/server/accounts.go index aca96b5d24b..05a7815497e 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -660,7 +660,7 @@ func (a *Account) AddWeightedMappings(src string, dests ...*MapDest) error { if tw[d.Cluster] > 100 { return fmt.Errorf("total weight needs to be <= 100") } - err := ValidateMappingDestination(d.Subject) + err := ValidateMapping(src, d.Subject) if err != nil { return err } diff --git a/server/errors.go b/server/errors.go index 8efa7ac02ea..5a91140d406 100644 --- a/server/errors.go +++ b/server/errors.go @@ -203,7 +203,7 @@ var ( ErrInvalidMappingDestination = errors.New("invalid mapping destination") // ErrInvalidMappingDestinationSubject is used to error on a bad transform destination mapping - ErrInvalidMappingDestinationSubject = fmt.Errorf("%w: invalid subject", ErrInvalidMappingDestination) + ErrInvalidMappingDestinationSubject = fmt.Errorf("%w: invalid transform", ErrInvalidMappingDestination) // ErrMappingDestinationNotUsingAllWildcards is used to error on a transform destination not using all of the token wildcards ErrMappingDestinationNotUsingAllWildcards = fmt.Errorf("%w: not using all of the token wildcard(s)", ErrInvalidMappingDestination) diff --git a/server/errors.json b/server/errors.json index 79602466a19..039eda78c95 100644 --- a/server/errors.json +++ b/server/errors.json @@ -1433,7 +1433,7 @@ "constant": "JSSourceInvalidSubjectFilter", "code": 400, "error_code": 10145, - "description": "source subject filter is invalid", + "description": "source transform source: {err}", "comment": "", "help": "", "url": "", @@ -1443,7 +1443,7 @@ "constant": "JSSourceInvalidTransformDestination", "code": 400, "error_code": 10146, - "description": "source transform destination is invalid", + "description": "source transform: {err}", "comment": "", "help": "", "url": "", @@ -1493,7 +1493,7 @@ "constant": "JSMirrorInvalidSubjectFilter", "code": 400, "error_code": 10151, - "description": "mirror subject filter is invalid", + "description": "mirror transform source: {err}", "comment": "", "help": "", "url": "", @@ -1518,5 +1518,35 @@ "help": "", "url": "", "deprecates": "" + }, + { + "constant": "JSMirrorInvalidTransformDestination", + "code": 400, + "error_code": 10154, + "description": "mirror transform: {err}", + "comment": "", + "help": "", + "url": "", + "deprecates": "" + }, + { + "constant": "JSStreamTransformInvalidSource", + "code": 400, + "error_code": 10155, + "description": "stream transform source: {err}", + "comment": "", + "help": "", + "url": "", + "deprecates": "" + }, + { + "constant": "JSStreamTransformInvalidDestination", + "code": 400, + "error_code": 10156, + "description": "stream transform: {err}", + "comment": "", + "help": "", + "url": "", + "deprecates": "" } ] diff --git a/server/jetstream_errors_generated.go b/server/jetstream_errors_generated.go index 1543eeeb4b7..4c6b594c623 100644 --- a/server/jetstream_errors_generated.go +++ b/server/jetstream_errors_generated.go @@ -236,9 +236,12 @@ const ( // JSMirrorInvalidStreamName mirrored stream name is invalid JSMirrorInvalidStreamName ErrorIdentifier = 10142 - // JSMirrorInvalidSubjectFilter mirror subject filter is invalid + // JSMirrorInvalidSubjectFilter mirror transform source: {err} JSMirrorInvalidSubjectFilter ErrorIdentifier = 10151 + // JSMirrorInvalidTransformDestination mirror transform: {err} + JSMirrorInvalidTransformDestination ErrorIdentifier = 10154 + // JSMirrorMaxMessageSizeTooBigErr stream mirror must have max message size >= source JSMirrorMaxMessageSizeTooBigErr ErrorIdentifier = 10030 @@ -308,10 +311,10 @@ const ( // JSSourceInvalidStreamName sourced stream name is invalid JSSourceInvalidStreamName ErrorIdentifier = 10141 - // JSSourceInvalidSubjectFilter source subject filter is invalid + // JSSourceInvalidSubjectFilter source transform source: {err} JSSourceInvalidSubjectFilter ErrorIdentifier = 10145 - // JSSourceInvalidTransformDestination source transform destination is invalid + // JSSourceInvalidTransformDestination source transform: {err} JSSourceInvalidTransformDestination ErrorIdentifier = 10146 // JSSourceMaxMessageSizeTooBigErr stream source must have max message size >= target @@ -446,6 +449,12 @@ const ( // JSStreamTemplateNotFoundErr template not found JSStreamTemplateNotFoundErr ErrorIdentifier = 10068 + // JSStreamTransformInvalidDestination stream transform: {err} + JSStreamTransformInvalidDestination ErrorIdentifier = 10156 + + // JSStreamTransformInvalidSource stream transform source: {err} + JSStreamTransformInvalidSource ErrorIdentifier = 10155 + // JSStreamUpdateErrF Generic stream update error string ({err}) JSStreamUpdateErrF ErrorIdentifier = 10069 @@ -541,7 +550,8 @@ var ( JSMemoryResourcesExceededErr: {Code: 500, ErrCode: 10028, Description: "insufficient memory resources available"}, JSMirrorConsumerSetupFailedErrF: {Code: 500, ErrCode: 10029, Description: "{err}"}, JSMirrorInvalidStreamName: {Code: 400, ErrCode: 10142, Description: "mirrored stream name is invalid"}, - JSMirrorInvalidSubjectFilter: {Code: 400, ErrCode: 10151, Description: "mirror subject filter is invalid"}, + JSMirrorInvalidSubjectFilter: {Code: 400, ErrCode: 10151, Description: "mirror transform source: {err}"}, + JSMirrorInvalidTransformDestination: {Code: 400, ErrCode: 10154, Description: "mirror transform: {err}"}, JSMirrorMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10030, Description: "stream mirror must have max message size >= source"}, JSMirrorMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10150, Description: "mirror with multiple subject transforms cannot also have a single subject filter"}, JSMirrorOverlappingSubjectFilters: {Code: 400, ErrCode: 10152, Description: "mirror subject filters can not overlap"}, @@ -565,8 +575,8 @@ var ( JSSourceConsumerSetupFailedErrF: {Code: 500, ErrCode: 10045, Description: "{err}"}, JSSourceDuplicateDetected: {Code: 400, ErrCode: 10140, Description: "duplicate source configuration detected"}, JSSourceInvalidStreamName: {Code: 400, ErrCode: 10141, Description: "sourced stream name is invalid"}, - JSSourceInvalidSubjectFilter: {Code: 400, ErrCode: 10145, Description: "source subject filter is invalid"}, - JSSourceInvalidTransformDestination: {Code: 400, ErrCode: 10146, Description: "source transform destination is invalid"}, + JSSourceInvalidSubjectFilter: {Code: 400, ErrCode: 10145, Description: "source transform source: {err}"}, + JSSourceInvalidTransformDestination: {Code: 400, ErrCode: 10146, Description: "source transform: {err}"}, JSSourceMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10046, Description: "stream source must have max message size >= target"}, JSSourceMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10144, Description: "source with multiple subject transforms cannot also have a single subject filter"}, JSSourceOverlappingSubjectFilters: {Code: 400, ErrCode: 10147, Description: "source filters can not overlap"}, @@ -611,6 +621,8 @@ var ( JSStreamTemplateCreateErrF: {Code: 500, ErrCode: 10066, Description: "{err}"}, JSStreamTemplateDeleteErrF: {Code: 500, ErrCode: 10067, Description: "{err}"}, JSStreamTemplateNotFoundErr: {Code: 404, ErrCode: 10068, Description: "template not found"}, + JSStreamTransformInvalidDestination: {Code: 400, ErrCode: 10156, Description: "stream transform: {err}"}, + JSStreamTransformInvalidSource: {Code: 400, ErrCode: 10155, Description: "stream transform source: {err}"}, JSStreamUpdateErrF: {Code: 500, ErrCode: 10069, Description: "{err}"}, JSStreamWrongLastMsgIDErrF: {Code: 400, ErrCode: 10070, Description: "wrong last msg ID: {id}"}, JSStreamWrongLastSequenceErrF: {Code: 400, ErrCode: 10071, Description: "wrong last sequence: {seq}"}, @@ -1483,14 +1495,36 @@ func NewJSMirrorInvalidStreamNameError(opts ...ErrorOption) *ApiError { return ApiErrors[JSMirrorInvalidStreamName] } -// NewJSMirrorInvalidSubjectFilterError creates a new JSMirrorInvalidSubjectFilter error: "mirror subject filter is invalid" -func NewJSMirrorInvalidSubjectFilterError(opts ...ErrorOption) *ApiError { +// NewJSMirrorInvalidSubjectFilterError creates a new JSMirrorInvalidSubjectFilter error: "mirror transform source: {err}" +func NewJSMirrorInvalidSubjectFilterError(err error, opts ...ErrorOption) *ApiError { + eopts := parseOpts(opts) + if ae, ok := eopts.err.(*ApiError); ok { + return ae + } + + e := ApiErrors[JSMirrorInvalidSubjectFilter] + args := e.toReplacerArgs([]interface{}{"{err}", err}) + return &ApiError{ + Code: e.Code, + ErrCode: e.ErrCode, + Description: strings.NewReplacer(args...).Replace(e.Description), + } +} + +// NewJSMirrorInvalidTransformDestinationError creates a new JSMirrorInvalidTransformDestination error: "mirror transform: {err}" +func NewJSMirrorInvalidTransformDestinationError(err error, opts ...ErrorOption) *ApiError { eopts := parseOpts(opts) if ae, ok := eopts.err.(*ApiError); ok { return ae } - return ApiErrors[JSMirrorInvalidSubjectFilter] + e := ApiErrors[JSMirrorInvalidTransformDestination] + args := e.toReplacerArgs([]interface{}{"{err}", err}) + return &ApiError{ + Code: e.Code, + ErrCode: e.ErrCode, + Description: strings.NewReplacer(args...).Replace(e.Description), + } } // NewJSMirrorMaxMessageSizeTooBigError creates a new JSMirrorMaxMessageSizeTooBigErr error: "stream mirror must have max message size >= source" @@ -1747,24 +1781,36 @@ func NewJSSourceInvalidStreamNameError(opts ...ErrorOption) *ApiError { return ApiErrors[JSSourceInvalidStreamName] } -// NewJSSourceInvalidSubjectFilterError creates a new JSSourceInvalidSubjectFilter error: "source subject filter is invalid" -func NewJSSourceInvalidSubjectFilterError(opts ...ErrorOption) *ApiError { +// NewJSSourceInvalidSubjectFilterError creates a new JSSourceInvalidSubjectFilter error: "source transform source: {err}" +func NewJSSourceInvalidSubjectFilterError(err error, opts ...ErrorOption) *ApiError { eopts := parseOpts(opts) if ae, ok := eopts.err.(*ApiError); ok { return ae } - return ApiErrors[JSSourceInvalidSubjectFilter] + e := ApiErrors[JSSourceInvalidSubjectFilter] + args := e.toReplacerArgs([]interface{}{"{err}", err}) + return &ApiError{ + Code: e.Code, + ErrCode: e.ErrCode, + Description: strings.NewReplacer(args...).Replace(e.Description), + } } -// NewJSSourceInvalidTransformDestinationError creates a new JSSourceInvalidTransformDestination error: "source transform destination is invalid" -func NewJSSourceInvalidTransformDestinationError(opts ...ErrorOption) *ApiError { +// NewJSSourceInvalidTransformDestinationError creates a new JSSourceInvalidTransformDestination error: "source transform: {err}" +func NewJSSourceInvalidTransformDestinationError(err error, opts ...ErrorOption) *ApiError { eopts := parseOpts(opts) if ae, ok := eopts.err.(*ApiError); ok { return ae } - return ApiErrors[JSSourceInvalidTransformDestination] + e := ApiErrors[JSSourceInvalidTransformDestination] + args := e.toReplacerArgs([]interface{}{"{err}", err}) + return &ApiError{ + Code: e.Code, + ErrCode: e.ErrCode, + Description: strings.NewReplacer(args...).Replace(e.Description), + } } // NewJSSourceMaxMessageSizeTooBigError creates a new JSSourceMaxMessageSizeTooBigErr error: "stream source must have max message size >= target" @@ -2315,6 +2361,38 @@ func NewJSStreamTemplateNotFoundError(opts ...ErrorOption) *ApiError { return ApiErrors[JSStreamTemplateNotFoundErr] } +// NewJSStreamTransformInvalidDestinationError creates a new JSStreamTransformInvalidDestination error: "stream transform: {err}" +func NewJSStreamTransformInvalidDestinationError(err error, opts ...ErrorOption) *ApiError { + eopts := parseOpts(opts) + if ae, ok := eopts.err.(*ApiError); ok { + return ae + } + + e := ApiErrors[JSStreamTransformInvalidDestination] + args := e.toReplacerArgs([]interface{}{"{err}", err}) + return &ApiError{ + Code: e.Code, + ErrCode: e.ErrCode, + Description: strings.NewReplacer(args...).Replace(e.Description), + } +} + +// NewJSStreamTransformInvalidSourceError creates a new JSStreamTransformInvalidSource error: "stream transform source: {err}" +func NewJSStreamTransformInvalidSourceError(err error, opts ...ErrorOption) *ApiError { + eopts := parseOpts(opts) + if ae, ok := eopts.err.(*ApiError); ok { + return ae + } + + e := ApiErrors[JSStreamTransformInvalidSource] + args := e.toReplacerArgs([]interface{}{"{err}", err}) + return &ApiError{ + Code: e.Code, + ErrCode: e.ErrCode, + Description: strings.NewReplacer(args...).Replace(e.Description), + } +} + // NewJSStreamUpdateError creates a new JSStreamUpdateErrF error: "{err}" func NewJSStreamUpdateError(err error, opts ...ErrorOption) *ApiError { eopts := parseOpts(opts) diff --git a/server/jetstream_test.go b/server/jetstream_test.go index d153255a525..4cc3730d547 100644 --- a/server/jetstream_test.go +++ b/server/jetstream_test.go @@ -11577,7 +11577,7 @@ func TestJetStreamMirrorBasics(t *testing.T) { Name: "MBAD", Storage: FileStorage, Mirror: &StreamSource{Name: "S1", SubjectTransforms: []SubjectTransformConfig{{Source: "*", Destination: "{{wildcard(2)}}"}}}, - }, ApiErrors[JSStreamCreateErrF].ErrCode) + }, ApiErrors[JSMirrorInvalidTransformDestination].ErrCode) createStreamServerStreamConfig(&StreamConfig{ Name: "MBAD", @@ -23760,6 +23760,67 @@ func TestJetStreamBadSubjectMappingStream(t *testing.T) { }, }, }) + require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: source transform: invalid mapping destination: too many arguments passed to the function in {{wildcard(1)}}{{split(3,1)}}"))) - require_Error(t, err, NewJSStreamUpdateError(errors.New("unable to get subject transform for source: invalid mapping destination: too many arguments passed to the function in {{wildcard(1)}}{{split(3,1)}}"))) + _, err = js.UpdateStream(&nats.StreamConfig{ + Name: "test", + Sources: []*nats.StreamSource{ + { + Name: "mapping", + SubjectTransforms: []nats.SubjectTransformConfig{ + { + Source: "events.>.*", + Destination: "events.{{split(1,1)}}", + }, + }, + }, + }, + }) + require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: source transform source: invalid subject events.>.*"))) + + _, err = js.UpdateStream(&nats.StreamConfig{ + Name: "test", + Mirror: &nats.StreamSource{ + Name: "mapping", + SubjectTransforms: []nats.SubjectTransformConfig{ + { + Source: "events.*", + Destination: "events.{{split(3,1)}}", + }, + }, + }, + }) + require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: mirror transform: invalid mapping destination: wildcard index out of range in {{split(3,1)}}: [3]"))) + + _, err = js.UpdateStream(&nats.StreamConfig{ + Name: "test", + Mirror: &nats.StreamSource{ + Name: "mapping", + SubjectTransforms: []nats.SubjectTransformConfig{ + { + Source: "events.>.*", + Destination: "events.{{split(1,1)}}", + }, + }, + }, + }) + require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: mirror transform source: invalid subject events.>.*"))) + + _, err = js.UpdateStream(&nats.StreamConfig{ + Name: "test", + SubjectTransform: &nats.SubjectTransformConfig{ + Source: "events.*", + Destination: "events.{{split(3,1)}}", + }, + }) + require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: stream transform: invalid mapping destination: wildcard index out of range in {{split(3,1)}}: [3]"))) + + _, err = js.UpdateStream(&nats.StreamConfig{ + Name: "test", + SubjectTransform: &nats.SubjectTransformConfig{ + Source: "events.>.*", + Destination: "events.{{split(1,1)}}", + }, + }) + require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: stream transform source: invalid subject events.>.*"))) } diff --git a/server/stream.go b/server/stream.go index 8bbcc9aa9ce..545a1dac19c 100644 --- a/server/stream.go +++ b/server/stream.go @@ -1306,9 +1306,15 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account) (StreamConfi } // Check subject filters overlap. for outer, tr := range cfg.Mirror.SubjectTransforms { - if !IsValidSubject(tr.Source) { - return StreamConfig{}, NewJSMirrorInvalidSubjectFilterError() + if tr.Source != _EMPTY_ && !IsValidSubject(tr.Source) { + return StreamConfig{}, NewJSMirrorInvalidSubjectFilterError(fmt.Errorf("%w %s", ErrBadSubject, tr.Source)) } + + err := ValidateMapping(tr.Source, tr.Destination) + if err != nil { + return StreamConfig{}, NewJSMirrorInvalidTransformDestinationError(err) + } + for inner, innertr := range cfg.Mirror.SubjectTransforms { if inner != outer && SubjectsCollide(tr.Source, innertr.Source) { return StreamConfig{}, NewJSMirrorOverlappingSubjectFiltersError() @@ -1350,10 +1356,10 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account) (StreamConfi if cfg.Mirror.External.DeliverPrefix != _EMPTY_ { deliveryPrefixes = append(deliveryPrefixes, cfg.Mirror.External.DeliverPrefix) } + if cfg.Mirror.External.ApiPrefix != _EMPTY_ { apiPrefixes = append(apiPrefixes, cfg.Mirror.External.ApiPrefix) } - } } @@ -1386,17 +1392,18 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account) (StreamConfi } for _, tr := range src.SubjectTransforms { - err := ValidateMappingDestination(tr.Destination) + if tr.Source != _EMPTY_ && !IsValidSubject(tr.Source) { + return StreamConfig{}, NewJSSourceInvalidSubjectFilterError(fmt.Errorf("%w %s", ErrBadSubject, tr.Source)) + } + + err := ValidateMapping(tr.Source, tr.Destination) if err != nil { - return StreamConfig{}, NewJSSourceInvalidTransformDestinationError() + return StreamConfig{}, NewJSSourceInvalidTransformDestinationError(err) } } // Check subject filters overlap. for outer, tr := range src.SubjectTransforms { - if !IsValidSubject(tr.Source) { - return StreamConfig{}, NewJSSourceInvalidSubjectFilterError() - } for inner, innertr := range src.SubjectTransforms { if inner != outer && subjectIsSubsetMatch(tr.Source, innertr.Source) { return StreamConfig{}, NewJSSourceOverlappingSubjectFiltersError() @@ -1577,6 +1584,18 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account) (StreamConfi } } + // Check the subject transform if any + if cfg.SubjectTransform != nil { + if cfg.SubjectTransform.Source != _EMPTY_ && !IsValidSubject(cfg.SubjectTransform.Source) { + return StreamConfig{}, NewJSStreamTransformInvalidSourceError(fmt.Errorf("%w %s", ErrBadSubject, cfg.SubjectTransform.Source)) + } + + err := ValidateMapping(cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination) + if err != nil { + return StreamConfig{}, NewJSStreamTransformInvalidDestinationError(err) + } + } + return cfg, nil } diff --git a/server/sublist.go b/server/sublist.go index 0000ad9f9a9..c0e7a1810dc 100644 --- a/server/sublist.go +++ b/server/sublist.go @@ -1225,12 +1225,12 @@ func isValidLiteralSubject(tokens []string) bool { return true } -// ValidateMappingDestination returns nil error if the subject is a valid subject mapping destination subject -func ValidateMappingDestination(subject string) error { - if subject == _EMPTY_ { +// ValidateMapping returns nil error if the subject is a valid subject mapping destination subject +func ValidateMapping(src string, dest string) error { + if dest == _EMPTY_ { return nil } - subjectTokens := strings.Split(subject, tsep) + subjectTokens := strings.Split(dest, tsep) sfwc := false for _, t := range subjectTokens { length := len(t) @@ -1238,6 +1238,7 @@ func ValidateMappingDestination(subject string) error { return &mappingDestinationErr{t, ErrInvalidMappingDestinationSubject} } + // if it looks like it contains a mapping function, it should be a valid mapping function if length > 4 && t[0] == '{' && t[1] == '{' && t[length-2] == '}' && t[length-1] == '}' { if !partitionMappingFunctionRegEx.MatchString(t) && !wildcardMappingFunctionRegEx.MatchString(t) && @@ -1258,7 +1259,10 @@ func ValidateMappingDestination(subject string) error { return ErrInvalidMappingDestinationSubject } } - return nil + + // Finally, verify that the transform can actually be created from the source and destination + _, err := NewSubjectTransform(src, dest) + return err } // Will check tokens and report back if the have any partial or full wildcards. diff --git a/server/sublist_test.go b/server/sublist_test.go index 966401b5f1a..e7d78b7beb0 100644 --- a/server/sublist_test.go +++ b/server/sublist_test.go @@ -679,24 +679,24 @@ func TestSubjectIsLiteral(t *testing.T) { } func TestValidateDestinationSubject(t *testing.T) { - checkError(ValidateMappingDestination("foo"), nil, t) - checkError(ValidateMappingDestination("foo.bar"), nil, t) - checkError(ValidateMappingDestination("*"), nil, t) - checkError(ValidateMappingDestination(">"), nil, t) - checkError(ValidateMappingDestination("foo.*"), nil, t) - checkError(ValidateMappingDestination("foo.>"), nil, t) - checkError(ValidateMappingDestination("foo.*.>"), nil, t) - checkError(ValidateMappingDestination("foo.*.bar"), nil, t) - checkError(ValidateMappingDestination("foo.bar.>"), nil, t) - checkError(ValidateMappingDestination("foo.{{wildcard(1)}}"), nil, 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) + checkError(ValidateMapping("bar", "foo"), nil, t) + checkError(ValidateMapping("foo", "foo.bar"), nil, t) + checkError(ValidateMapping("*", "{{wildcard(1)}}"), nil, t) + checkError(ValidateMapping("foo.>", ">"), nil, t) + checkError(ValidateMapping("foo.*", "bar.{{wildcard(1)}}"), nil, t) + checkError(ValidateMapping("foo.>", "bar.>"), nil, t) + checkError(ValidateMapping("foo.*.>", "bar.{{wildcard(1)}}.>"), nil, t) + checkError(ValidateMapping("foo.*.bar", "bar.{{wildcard(1)}}.foo"), nil, t) + checkError(ValidateMapping("foo.bar.>", "foo.bar.foo.>"), nil, t) + checkError(ValidateMapping("*", "foo.{{wildcard(1)}}"), nil, t) + checkError(ValidateMapping("*", "foo.{{ wildcard(1) }}"), nil, t) + checkError(ValidateMapping("*", "foo.{{wildcard( 1 )}}"), nil, t) + checkError(ValidateMapping("*", "foo.{{partition(2,1)}}"), nil, t) + checkError(ValidateMapping("*.*", "foo.{{SplitFromLeft(2,1)}}"), nil, t) + checkError(ValidateMapping("*.*", "foo.{{SplitFromRight(2,1)}}"), nil, t) + checkError(ValidateMapping("*", "foo.{{unknown(1)}}"), ErrInvalidMappingDestination, t) + checkError(ValidateMapping("foo", "foo..}"), ErrInvalidMappingDestination, t) + checkError(ValidateMapping("foo", "foo. bar}"), ErrInvalidMappingDestinationSubject, t) }