-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
1,138 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Package topic implements common methods to handle MQTT topics. | ||
package topic | ||
|
||
import ( | ||
"errors" | ||
"strings" | ||
) | ||
|
||
// ErrZeroLength is returned by Parse if a topics has a zero length. | ||
var ErrZeroLength = errors.New("zero length topic") | ||
|
||
// ErrWildcards is returned by Parse if a topic contains invalid wildcards. | ||
var ErrWildcards = errors.New("invalid use of wildcards") | ||
|
||
// Parse removes duplicate and trailing slashes from the supplied | ||
// string and returns the normalized topic. | ||
func Parse(topic string, allowWildcards bool) (string, error) { | ||
// check for zero length | ||
if topic == "" { | ||
return "", ErrZeroLength | ||
} | ||
|
||
// normalize topic | ||
if hasAdjacentSlashes(topic) { | ||
topic = collapseSlashes(topic) | ||
} | ||
|
||
// remove trailing slashes | ||
topic = strings.TrimRightFunc(topic, trimSlash) | ||
|
||
// check again for zero length | ||
if topic == "" { | ||
return "", ErrZeroLength | ||
} | ||
|
||
// get first segment | ||
remainder := topic | ||
segment := topicSegment(topic, "/") | ||
|
||
// check all segments | ||
for segment != topicEnd { | ||
// check use of wildcards | ||
if (strings.Contains(segment, "+") || strings.Contains(segment, "#")) && len(segment) > 1 { | ||
return "", ErrWildcards | ||
} | ||
|
||
// check if wildcards are allowed | ||
if !allowWildcards && (segment == "#" || segment == "+") { | ||
return "", ErrWildcards | ||
} | ||
|
||
// check if hash is the last character | ||
if segment == "#" && topicShorten(remainder, "/") != topicEnd { | ||
return "", ErrWildcards | ||
} | ||
|
||
// get next segment | ||
remainder = topicShorten(remainder, "/") | ||
segment = topicSegment(remainder, "/") | ||
} | ||
|
||
return topic, nil | ||
} | ||
|
||
// ContainsWildcards tests if the supplied topic contains wildcards. The topic | ||
// is expected to be tested and normalized using Parse beforehand. | ||
func ContainsWildcards(topic string) bool { | ||
return strings.Contains(topic, "+") || strings.Contains(topic, "#") | ||
} | ||
|
||
func hasAdjacentSlashes(str string) bool { | ||
var last rune | ||
|
||
for _, r := range str { | ||
if r == '/' && last == '/' { | ||
return true | ||
} | ||
last = r | ||
} | ||
return false | ||
} | ||
|
||
func collapseSlashes(str string) string { | ||
var b strings.Builder | ||
var last rune | ||
|
||
for _, r := range str { | ||
if r == '/' && last == '/' { | ||
continue | ||
} | ||
b.WriteRune(r) | ||
last = r | ||
} | ||
return b.String() | ||
} | ||
|
||
func trimSlash(r rune) bool { return r == '/' } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package topic | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_Parse(t *testing.T) { | ||
tests := map[string]string{ | ||
"topic/hello": "topic/hello", | ||
"topic//hello": "topic/hello", | ||
"topic///hello": "topic/hello", | ||
"/topic": "/topic", | ||
"//topic": "/topic", | ||
"///topic": "/topic", | ||
"topic/": "topic", | ||
"topic//": "topic", | ||
"topic///": "topic", | ||
"topic///cool//hello": "topic/cool/hello", | ||
"topic//cool///hello": "topic/cool/hello", | ||
} | ||
|
||
for str, result := range tests { | ||
str, err := Parse(str, true) | ||
require.Equal(t, result, str) | ||
require.NoError(t, err, str) | ||
} | ||
} | ||
|
||
func Test_ParseZeroLengthError(t *testing.T) { | ||
_, err := Parse("", true) | ||
require.Equal(t, ErrZeroLength, err) | ||
|
||
_, err = Parse("/", true) | ||
require.Equal(t, ErrZeroLength, err) | ||
|
||
_, err = Parse("//", true) | ||
require.Equal(t, ErrZeroLength, err) | ||
} | ||
|
||
func Test_ParseDisallowWildcards(t *testing.T) { | ||
tests := map[string]bool{ | ||
"topic": true, | ||
"topic/hello": true, | ||
"topic/cool/hello": true, | ||
"+": false, | ||
"#": false, | ||
"topic/+": false, | ||
"topic/#": false, | ||
} | ||
|
||
for str, result := range tests { | ||
_, err := Parse(str, false) | ||
|
||
if result { | ||
require.NoError(t, err, str) | ||
} else { | ||
require.Error(t, err, str) | ||
} | ||
} | ||
} | ||
|
||
func Test_ParseAllowWildcards(t *testing.T) { | ||
tests := map[string]bool{ | ||
"topic": true, | ||
"topic/hello": true, | ||
"topic/cool/hello": true, | ||
"+": true, | ||
"#": true, | ||
"topic/+": true, | ||
"topic/#": true, | ||
"topic/+/hello": true, | ||
"topic/cool/+": true, | ||
"topic/cool/#": true, | ||
"+/cool/#": true, | ||
"+/+/#": true, | ||
"": false, | ||
"++": false, | ||
"##": false, | ||
"#/+": false, | ||
"#/#": false, | ||
} | ||
|
||
for str, result := range tests { | ||
_, err := Parse(str, true) | ||
|
||
if result { | ||
require.NoError(t, err, str) | ||
} else { | ||
require.Error(t, err, str) | ||
} | ||
} | ||
} | ||
|
||
func Test_ContainsWildcards(t *testing.T) { | ||
require.True(t, ContainsWildcards("topic/+")) | ||
require.True(t, ContainsWildcards("topic/#")) | ||
require.False(t, ContainsWildcards("topic/hello")) | ||
} | ||
|
||
func Benchmark_Parse(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
_, err := Parse("foo", true) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
} |
Oops, something went wrong.