Add StanzaType matcher / Clarify empty route behaviour (#65)

* Add route to match on stanza type

* Add test checking that an empty route "always" matches
This commit is contained in:
Mickaël Rémond 2019-06-21 16:48:13 +02:00 committed by GitHub
parent 5d362b505b
commit 145fce6b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 6 deletions

View File

@ -12,6 +12,9 @@ Here are important rules to keep in mind while setting your routes and matchers:
- Routes are evaluated in the order they are set. - Routes are evaluated in the order they are set.
- When a route matches, it is executed and all others routes are ignored. For each packet, only a single - When a route matches, it is executed and all others routes are ignored. For each packet, only a single
route is executed. route is executed.
- An empty route will match everything. Adding an empty route as the last route in your router will
allow you to get all stanzas that did not match any previous route. You can for example use this to
log all unexpected stanza received by your client or component.
TODO: Automatically reply to IQ that do not match any route, to comply to XMPP standard. TODO: Automatically reply to IQ that do not match any route, to comply to XMPP standard.
*/ */
@ -145,6 +148,41 @@ func (r *Route) Packet(name string) *Route {
return r.addMatcher(nameMatcher(name)) return r.addMatcher(nameMatcher(name))
} }
// -------------------------
// Match on stanza type
// nsTypeMather matches on a list of IQ payload namespaces
type nsTypeMatcher []string
func (m nsTypeMatcher) Match(p Packet, match *RouteMatch) bool {
// TODO: Rework after merge of #62
var stanzaType string
switch packet := p.(type) {
case IQ:
stanzaType = packet.Type
case Presence:
stanzaType = packet.Type
case Message:
if packet.Type == "" {
// optional on message, normal is the default type
stanzaType = "normal"
} else {
stanzaType = packet.Type
}
default:
return false
}
return matchInArray(m, stanzaType)
}
// IQNamespaces adds an IQ matcher, expecting both an IQ and a
func (r *Route) StanzaType(types ...string) *Route {
for k, v := range types {
types[k] = strings.ToLower(v)
}
return r.addMatcher(nsTypeMatcher(types))
}
// ------------------------- // -------------------------
// Match on IQ and namespace // Match on IQ and namespace
@ -152,7 +190,6 @@ func (r *Route) Packet(name string) *Route {
type nsIQMatcher []string type nsIQMatcher []string
func (m nsIQMatcher) Match(p Packet, match *RouteMatch) bool { func (m nsIQMatcher) Match(p Packet, match *RouteMatch) bool {
// TODO
iq, ok := p.(IQ) iq, ok := p.(IQ)
if !ok { if !ok {
return false return false

View File

@ -8,9 +8,6 @@ import (
"gosrc.io/xmpp" "gosrc.io/xmpp"
) )
// ============================================================================
// SenderMock
// ============================================================================ // ============================================================================
// Test route & matchers // Test route & matchers
@ -51,7 +48,6 @@ func TestIQNSMatcher(t *testing.T) {
// Check that an IQ with proper namespace does match // Check that an IQ with proper namespace does match
conn := NewSenderMock() conn := NewSenderMock()
iqDisco := xmpp.NewIQ("get", "", "localhost", "1", "") iqDisco := xmpp.NewIQ("get", "", "localhost", "1", "")
// TODO: Add a function to generate payload with proper namespace initialisation
iqDisco.Payload = &xmpp.DiscoInfo{ iqDisco.Payload = &xmpp.DiscoInfo{
XMLName: xml.Name{ XMLName: xml.Name{
Space: xmpp.NSDiscoInfo, Space: xmpp.NSDiscoInfo,
@ -65,7 +61,6 @@ func TestIQNSMatcher(t *testing.T) {
// Check that another namespace is not matched // Check that another namespace is not matched
conn = NewSenderMock() conn = NewSenderMock()
iqVersion := xmpp.NewIQ("get", "", "localhost", "1", "") iqVersion := xmpp.NewIQ("get", "", "localhost", "1", "")
// TODO: Add a function to generate payload with proper namespace initialisation
iqVersion.Payload = &xmpp.DiscoInfo{ iqVersion.Payload = &xmpp.DiscoInfo{
XMLName: xml.Name{ XMLName: xml.Name{
Space: "jabber:iq:version", Space: "jabber:iq:version",
@ -77,6 +72,144 @@ func TestIQNSMatcher(t *testing.T) {
} }
} }
func TestTypeMatcher(t *testing.T) {
router := xmpp.NewRouter()
router.NewRoute().
StanzaType("normal").
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that a packet with the proper type matches
conn := NewSenderMock()
message := xmpp.NewMessage("normal", "", "test@localhost", "1", "")
message.Body = "hello"
router.Route(conn, message)
if conn.String() != successFlag {
t.Errorf("'normal' message should have been matched and routed: %v", message)
}
// We should match on default type 'normal' for message without a type
conn = NewSenderMock()
message = xmpp.NewMessage("", "", "test@localhost", "1", "")
message.Body = "hello"
router.Route(conn, message)
if conn.String() != successFlag {
t.Errorf("message should have been matched and routed: %v", message)
}
// We do not match on other types
conn = NewSenderMock()
iqVersion := xmpp.NewIQ("get", "service.localhost", "test@localhost", "1", "")
iqVersion.Payload = &xmpp.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
router.Route(conn, iqVersion)
if conn.String() == successFlag {
t.Errorf("iq get should not have been matched and routed: %v", iqVersion)
}
}
func TestCompositeMatcher(t *testing.T) {
router := xmpp.NewRouter()
router.NewRoute().
IQNamespaces("jabber:iq:version").
StanzaType("get").
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
_ = s.SendRaw(successFlag)
})
// Data set
getVersionIq := xmpp.NewIQ("get", "service.localhost", "test@localhost", "1", "")
getVersionIq.Payload = &xmpp.Version{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
setVersionIq := xmpp.NewIQ("set", "service.localhost", "test@localhost", "1", "")
setVersionIq.Payload = &xmpp.Version{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
GetDiscoIq := xmpp.NewIQ("get", "service.localhost", "test@localhost", "1", "")
GetDiscoIq.Payload = &xmpp.DiscoInfo{
XMLName: xml.Name{
Space: "http://jabber.org/protocol/disco#info",
Local: "query",
}}
message := xmpp.NewMessage("normal", "", "test@localhost", "1", "")
message.Body = "hello"
tests := []struct {
name string
input xmpp.Packet
want bool
}{
{name: "match get version iq", input: getVersionIq, want: true},
{name: "ignore set version iq", input: setVersionIq, want: false},
{name: "ignore get discoinfo iq", input: GetDiscoIq, want: false},
{name: "ignore message", input: message, want: false},
}
//
for _, tc := range tests {
t.Run(tc.name, func(st *testing.T) {
conn := NewSenderMock()
router.Route(conn, tc.input)
res := conn.String() == successFlag
if tc.want != res {
st.Errorf("incorrect result for %#v\nMatch = %#v, expecting %#v", tc.input, res, tc.want)
}
})
}
}
// A blank route with empty matcher will always match
// It can be use to receive all packets that do not match any of the previous route.
func TestCatchallMatcher(t *testing.T) {
router := xmpp.NewRouter()
router.NewRoute().
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that we match on several packets
conn := NewSenderMock()
message := xmpp.NewMessage("chat", "", "test@localhost", "1", "")
message.Body = "hello"
router.Route(conn, message)
if conn.String() != successFlag {
t.Errorf("chat message should have been matched and routed: %v", message)
}
conn = NewSenderMock()
iqVersion := xmpp.NewIQ("get", "service.localhost", "test@localhost", "1", "")
iqVersion.Payload = &xmpp.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
router.Route(conn, iqVersion)
if conn.String() != successFlag {
t.Errorf("iq get should have been matched and routed: %v", iqVersion)
}
}
// ============================================================================
// SenderMock
var successFlag = "matched" var successFlag = "matched"
type SenderMock struct { type SenderMock struct {