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:
parent
5d362b505b
commit
145fce6b3f
39
router.go
39
router.go
|
@ -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
|
||||||
|
|
143
router_test.go
143
router_test.go
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue