From c224e6ebc23d96929d9c3fee9b7987762f68f1dd Mon Sep 17 00:00:00 2001 From: xengineering Date: Fri, 30 Jun 2023 11:56:59 +0200 Subject: Add xmpp/router.go This implements a routing function for XML elements received by an XML stream. --- xmpp/router.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ xmpp/stream_pair.go | 8 +------- 2 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 xmpp/router.go diff --git a/xmpp/router.go b/xmpp/router.go new file mode 100644 index 0000000..839f870 --- /dev/null +++ b/xmpp/router.go @@ -0,0 +1,59 @@ +package xmpp + +import ( + "encoding/xml" + "log" +) + +// routingTable is a data structure which contains routing information for XML +// elements. The xml.StartElement at the beginning of an XML element has a name +// containing the XML namespace and a local name. Based on this compisition +// which forms the xml.Name the appropriate handler function is defined by each +// entry of the routingTable. +type routingTable []struct { + name xml.Name + handler func([]xml.Token) +} + +// getRoutingTable returns the routing table used in +// xengineering.eu/limox/xmpp. Since Go does not allow such a datatype as a +// constant such a function is a simple yet inefficient approach to guarantee +// that an unmodified routing table is delivered to each user. A global +// variable would have the problem that it could be altered during execution. +func getRoutingTable() routingTable { + return routingTable{ + // TODO fill with entries + } +} + +// route determines the correct handler function for the given XML element by a +// given routingTable. In addition it executes the determined handler function. +// If no handler function is found an error message is send via the log module. +func route(e []xml.Token, t routingTable) { + var name xml.Name + + // TODO a stronger definition of an XML element (as here + // https://www.w3schools.com/xml/xml_elements.asp) would define that the + // first Token of an element is a StartElement token. This would make this + // code easier. + escape := false + for _, token := range e { + switch s := token.(type) { + case xml.StartElement: + name = s.Name + escape = true + } + if escape { + break + } + } + + for _, r := range t { + if name == r.name { + r.handler(e) + return + } + } + + log.Println("Could not route XML element") +} diff --git a/xmpp/stream_pair.go b/xmpp/stream_pair.go index b13d8a3..c9108d1 100644 --- a/xmpp/stream_pair.go +++ b/xmpp/stream_pair.go @@ -28,13 +28,7 @@ func runStreamPair(s *session) { } if buf.isComplete() { element := buf.reset() - // TODO handle XML element here - this is just a dummy: - switch start := element[0].(type) { - case xml.StartElement: - log.Printf("Got XML element `%s`\n", start.Name.Local) - default: - log.Println("No xml.StartElement at start of element buffer!") - } + route(element, getRoutingTable()) } } } -- cgit v1.2.3-70-g09d2 From 91766058d76044fa7e07f75c586bc7f89b868328 Mon Sep 17 00:00:00 2001 From: xengineering Date: Fri, 30 Jun 2023 11:57:34 +0200 Subject: Add unit test for routing function --- xmpp/router_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 xmpp/router_test.go diff --git a/xmpp/router_test.go b/xmpp/router_test.go new file mode 100644 index 0000000..ea13712 --- /dev/null +++ b/xmpp/router_test.go @@ -0,0 +1,69 @@ +package xmpp + +import ( + "encoding/xml" + "testing" + "strings" +) + +// routerTest contains a single test case for the xmpp.router. The XML element +// is given as a string and the value int describes the expected value of a +// test variable inside the corresponding unit test. See TestRouter for +// details. +type routerTest struct { + xml string + value int +} + +// TestRouter tests the xmpp/router.go file. The central functionality is the +// route() function. To test this a fake routing table is created inside this +// test with XML element handler function produced by the factory function +// defined below. each handler simply sets the value of the testpoint variable +// to the value mentioned by the test point. That way the routing can be +// validated. +func TestRouter(t *testing.T) { + var testpoint int + + factory := func(tp *int, i int) func([]xml.Token) { + return func([]xml.Token) { + *tp = i + } + } + + tests := []routerTest{ + routerTest{``, 1}, + routerTest{``, 2}, + routerTest{``, 3}, + routerTest{``, 4}, + routerTest{``, 5}, + } + + testRouting := routingTable{ + {xml.Name{``, `a`}, factory(&testpoint, 1)}, + {xml.Name{``, `b`}, factory(&testpoint, 2)}, + {xml.Name{``, `c`}, factory(&testpoint, 3)}, + {xml.Name{`https://xengineering.eu`, `b`}, factory(&testpoint, 4)}, + {xml.Name{`https://xengineering.eu`, `a`}, factory(&testpoint, 5)}, + } + + for _, v := range tests { + testpoint = 0 + + r := strings.NewReader(v.xml) + d := xml.NewDecoder(r) + tokens := make([]xml.Token, 0) + for { + token, err := d.Token() + if err != nil { + break + } + tokens = append(tokens, xml.CopyToken(token)) + } + + route(tokens, testRouting) + + if testpoint != v.value { + t.Fatalf("XML element was not routed correctly!\n") + } + } +} -- cgit v1.2.3-70-g09d2