Instead of making Stanza an interface that Iq, Message, and Presence implement, change it to an embedded struct.
authorChris Jones <chris@cjones.org>
Sun, 16 Dec 2012 22:17:49 -0700
changeset 110 7696e6a01709
parent 109 3887d7ad19c1
child 111 36287f2cf06e
Instead of making Stanza an interface that Iq, Message, and Presence implement, change it to an embedded struct.
examples/interact.go
roster.go
roster_test.go
stream.go
structs.go
structs_test.go
xmpp.go
--- a/examples/interact.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/examples/interact.go	Sun Dec 16 22:17:49 2012 -0700
@@ -7,21 +7,23 @@
 import (
 	xmpp ".."
 	"crypto/tls"
+	"encoding/xml"
 	"flag"
 	"fmt"
 	"log"
 	"os"
+	"strings"
 )
 
 type StdLogger struct {
 }
 
 func (s *StdLogger) Log(v ...interface{}) {
-	log.Println(v)
+	log.Println(v...)
 }
 
 func (s *StdLogger) Logf(fmt string, v ...interface{}) {
-	log.Printf(fmt, v)
+	log.Printf(fmt, v...)
 }
 
 func init() {
@@ -61,7 +63,7 @@
 		fmt.Printf("%d: %v\n", i, entry)
 	}
 
-	go func(ch <-chan xmpp.Stanza) {
+	go func(ch <-chan interface{}) {
 		for obj := range ch {
 			fmt.Printf("s: %v\n", obj)
 		}
@@ -75,7 +77,31 @@
 			break
 		}
 		s := string(p)
-		stan, err := xmpp.ParseStanza(s)
+		dec := xml.NewDecoder(strings.NewReader(s))
+		t, err := dec.Token()
+		if err != nil {
+			fmt.Printf("token: %s\n", err)
+			break
+		}
+		var se *xml.StartElement
+		var ok bool
+		if se, ok = t.(*xml.StartElement) ; !ok {
+			fmt.Println("Couldn't find start element")
+			break
+		}
+		var stan interface{}
+		switch se.Name.Local {
+		case "iq":
+			stan = &xmpp.Iq{}
+		case "message":
+			stan = &xmpp.Message{}
+		case "presence":
+			stan = &xmpp.Presence{}
+		default:
+			fmt.Println("Can't parse non-stanza.")
+			continue
+		}
+		err = dec.Decode(stan)
 		if err == nil {
 			c.Out <- stan
 		} else {
--- a/roster.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/roster.go	Sun Dec 16 22:17:49 2012 -0700
@@ -48,14 +48,14 @@
 func fetchRoster(client *Client) error {
 	rosterUpdate := rosterClients[client.Uid].rosterUpdate
 
-	iq := &Iq{From: client.Jid.String(), Id: <-Id, Type: "get",
-		Nested: []interface{}{RosterQuery{}}}
+	iq := &Iq{Stanza: Stanza{From: client.Jid.String(), Type: "get",
+		Id: <-Id, Nested: []interface{}{RosterQuery{}}}}
 	ch := make(chan error)
-	f := func(st Stanza) bool {
+	f := func(v interface{}) bool {
 		defer close(ch)
-		iq, ok := st.(*Iq)
+		iq, ok := v.(*Iq)
 		if !ok {
-			ch <- fmt.Errorf("response to iq wasn't iq: %s", st)
+			ch <- fmt.Errorf("response to iq wasn't iq: %s", v)
 			return false
 		}
 		if iq.Type == "error" {
@@ -71,7 +71,7 @@
 		}
 		if rq == nil {
 			ch <- fmt.Errorf(
-				"Roster query result not query: %v", st)
+				"Roster query result not query: %v", v)
 			return false
 		}
 		for _, item := range rq.Item {
@@ -91,9 +91,9 @@
 // the roster feeder, which is the goroutine that provides data on
 // client.Roster.
 func startRosterFilter(client *Client) {
-	out := make(chan Stanza)
+	out := make(chan interface{})
 	in := client.AddFilter(out)
-	go func(in <-chan Stanza, out chan<- Stanza) {
+	go func(in <-chan interface{}, out chan<- interface{}) {
 		defer close(out)
 		for st := range in {
 			maybeUpdateRoster(client, st)
@@ -108,7 +108,7 @@
 	go feedRoster(rosterCh, rosterUpdate)
 }
 
-func maybeUpdateRoster(client *Client, st Stanza) {
+func maybeUpdateRoster(client *Client, st interface{}) {
 	iq, ok := st.(*Iq)
 	if !ok {
 		return
@@ -128,7 +128,8 @@
 			rosterUpdate <- item
 		}
 		// Send a reply.
-		reply := &Iq{To: iq.From, Id: iq.Id, Type: "result"}
+		reply := &Iq{Stanza: Stanza{To: iq.From, Id: iq.Id,
+			Type: "result"}}
 		client.Out <- reply
 	}
 }
--- a/roster_test.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/roster_test.go	Sun Dec 16 22:17:49 2012 -0700
@@ -13,8 +13,8 @@
 // This is mostly just tests of the roster data structures.
 
 func TestRosterIqMarshal(t *testing.T) {
-	iq := &Iq{From: "from", Lang: "en",
-		Nested: []interface{}{RosterQuery{}}}
+	iq := &Iq{Stanza: Stanza{From: "from", Lang: "en",
+		Nested: []interface{}{RosterQuery{}}}}
 	exp := `<iq from="from" xml:lang="en"><query xmlns="` +
 		NsRoster + `"></query></iq>`
 	assertMarshal(t, exp, iq)
@@ -26,7 +26,7 @@
 	iq := Iq{}
 	xml.Unmarshal([]byte(str), &iq)
 	m := map[string]func(*xml.Name) interface{}{NsRoster: newRosterQuery}
-	err := parseExtended(&iq, m)
+	err := parseExtended(&iq.Stanza, m)
 	if err != nil {
 		t.Fatalf("parseExtended: %v", err)
 	}
--- a/stream.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/stream.go	Sun Dec 16 22:17:49 2012 -0700
@@ -29,7 +29,7 @@
 type stanzaHandler struct {
 	id string
 	// Return true means pass this to the application
-	f func(Stanza) bool
+	f func(interface{}) bool
 }
 
 // BUG(cjyar) Review all these *Client receiver methods. They should
@@ -148,7 +148,7 @@
 		// If it's a Stanza, we try to unmarshal its innerxml
 		// into objects of the appropriate respective
 		// types. This is specified by our extensions.
-		if st, ok := obj.(Stanza); ok {
+		if st := getStanza(obj) ; st != nil {
 			err = parseExtended(st, extStanza)
 			if err != nil {
 				Warn.Logf("ext unmarshal: %s", err)
@@ -161,10 +161,10 @@
 	}
 }
 
-func parseExtended(st Stanza, extStanza map[string]func(*xml.Name) interface{}) error {
+func parseExtended(st *Stanza, extStanza map[string]func(*xml.Name) interface{}) error {
 	// Now parse the stanza's innerxml to find the string that we
 	// can unmarshal this nested element from.
-	reader := strings.NewReader(st.innerxml())
+	reader := strings.NewReader(st.Innerxml)
 	p := xml.NewDecoder(reader)
 	for {
 		t, err := p.Token()
@@ -185,7 +185,7 @@
 				if err != nil {
 					return err
 				}
-				st.addNested(nested)
+				st.Nested = append(st.Nested, nested)
 			}
 		}
 	}
@@ -225,10 +225,10 @@
 	}
 }
 
-func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- Stanza) {
+func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- interface{}) {
 	defer close(cliOut)
 
-	handlers := make(map[string]func(Stanza) bool)
+	handlers := make(map[string]func(interface{}) bool)
 Loop:
 	for {
 		select {
@@ -238,7 +238,7 @@
 			if !ok {
 				break Loop
 			}
-			send := false
+			var st *Stanza
 			switch obj := x.(type) {
 			case *stream:
 				handleStream(obj)
@@ -250,24 +250,24 @@
 				cl.handleTls(obj)
 			case *auth:
 				cl.handleSasl(obj)
+			case *Iq, *Message, *Presence:
+				st = getStanza(obj)
 			default:
-				send = true
+				Warn.Logf("Unhandled non-stanza: %T %#v", x, x)
 			}
-			if !send {
+
+			if st == nil {
 				continue
 			}
-			st, ok := x.(Stanza)
-			if !ok {
-				Warn.Logf("Unhandled non-stanza: %v", x)
-				continue
-			}
-			if handlers[st.GetId()] != nil {
-				f := handlers[st.GetId()]
-				delete(handlers, st.GetId())
-				send = f(st)
+
+			send := true
+			if handlers[st.Id] != nil {
+				f := handlers[st.Id]
+				delete(handlers, st.Id)
+				send = f(x)
 			}
 			if send {
-				cliOut <- st
+				cliOut <- x
 			}
 		}
 	}
@@ -277,11 +277,11 @@
 // the app might inject something inappropriate into our negotiations
 // with the server. The control channel controls this loop's
 // activity.
-func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza,
+func writeStream(srvOut chan<- interface{}, cliIn <-chan interface{},
 control <-chan int) {
 	defer close(srvOut)
 
-	var input <-chan Stanza
+	var input <-chan interface{}
 Loop:
 	for {
 		select {
@@ -309,8 +309,8 @@
 
 // Stanzas from the remote go up through a stack of filters to the
 // app. This function manages the filters.
-func filterTop(filterOut <-chan <-chan Stanza, filterIn chan<- <-chan Stanza,
-topFilter <-chan Stanza, app chan<- Stanza) {
+func filterTop(filterOut <-chan <-chan interface{}, filterIn chan<- <-chan interface{},
+topFilter <-chan interface{}, app chan<- interface{}) {
 	defer close(app)
 Loop:
 	for {
@@ -333,7 +333,7 @@
 	}
 }
 
-func filterBottom(from <-chan Stanza, to chan<- Stanza) {
+func filterBottom(from <-chan interface{}, to chan<- interface{}) {
 	defer close(to)
 	for data := range from {
 		to <- data
@@ -596,8 +596,9 @@
 	if res != "" {
 		bindReq.Resource = &res
 	}
-	msg := &Iq{Type: "set", Id: <-Id, Nested: []interface{}{bindReq}}
-	f := func(st Stanza) bool {
+	msg := &Iq{Stanza: Stanza{Type: "set", Id: <-Id,
+		Nested: []interface{}{bindReq}}}
+	f := func(st interface{}) bool {
 		iq, ok := st.(*Iq)
 		if !ok {
 			Warn.Log("non-iq response")
@@ -614,7 +615,7 @@
 			}
 		}
 		if bindRepl == nil {
-			Warn.Logf("Bad bind reply: %v", iq)
+			Warn.Logf("Bad bind reply: %#v", iq)
 			return false
 		}
 		jidStr := bindRepl.Jid
@@ -642,7 +643,7 @@
 // available on the normal Client.In channel. The stanza handler
 // must not read from that channel, as deliveries on it cannot proceed
 // until the handler returns true or false.
-func (cl *Client) HandleStanza(id string, f func(Stanza) bool) {
+func (cl *Client) HandleStanza(id string, f func(interface{}) bool) {
 	h := &stanzaHandler{id: id, f: f}
 	cl.handlers <- h
 }
--- a/structs.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/structs.go	Sun Dec 16 22:17:49 2012 -0700
@@ -9,7 +9,6 @@
 import (
 	"bytes"
 	"encoding/xml"
-	"errors"
 	"flag"
 	"fmt"
 	// BUG(cjyar): We should use stringprep
@@ -80,65 +79,7 @@
 
 // One of the three core XMPP stanza types: iq, message, presence. See
 // RFC3920, section 9.
-type Stanza interface {
-	// // Returns "iq", "message", or "presence".
-	// GetName() string
-	// // The to attribute.
-	// GetTo() string
-	// // The from attribute.
-	// GetFrom() string
-	// The id attribute. TODO maybe remove this.
-	GetId() string
-	// // The type attribute.
-	// GetType() string
-	// // The xml:lang attribute.
-	// GetLang() string
-	// // A nested error element, if any.
-	// GetError() *Error
-	// // Zero or more (non-error) nested elements. These will be in
-	// // namespaces managed by extensions.
-	// GetNested() []interface{}
-	addNested(interface{})
-	innerxml() string
-}
-
-// message stanza
-type Message struct {
-	XMLName  xml.Name `xml:"message"`
-	To       string   `xml:"to,attr,omitempty"`
-	From     string   `xml:"from,attr,omitempty"`
-	Id       string   `xml:"id,attr,omitempty"`
-	Type     string   `xml:"type,attr,omitempty"`
-	Lang     string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
-	Innerxml string   `xml:",innerxml"`
-	Error    *Error
-	Subject  *Generic `xml:"subject"`
-	Body     *Generic `xml:"body"`
-	Thread   *Generic `xml:"thread"`
-	Nested   []interface{}
-}
-var _ Stanza = &Message{}
-
-// presence stanza
-type Presence struct {
-	XMLName  xml.Name `xml:"presence"`
-	To       string   `xml:"to,attr,omitempty"`
-	From     string   `xml:"from,attr,omitempty"`
-	Id       string   `xml:"id,attr,omitempty"`
-	Type     string   `xml:"type,attr,omitempty"`
-	Lang     string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
-	Innerxml string   `xml:",innerxml"`
-	Error    *Error
-	Show     *Generic `xml:"show"`
-	Status   *Generic `xml:"status"`
-	Priority *Generic `xml:"priority"`
-	Nested   []interface{}
-}
-var _ Stanza = &Presence{}
-
-// iq stanza
-type Iq struct {
-	XMLName  xml.Name `xml:"iq"`
+type Stanza struct {
 	To       string   `xml:"to,attr,omitempty"`
 	From     string   `xml:"from,attr,omitempty"`
 	Id       string   `xml:"id,attr,omitempty"`
@@ -148,7 +89,30 @@
 	Error    *Error
 	Nested   []interface{}
 }
-var _ Stanza = &Iq{}
+
+// message stanza
+type Message struct {
+	XMLName  xml.Name `xml:"message"`
+	Stanza
+	Subject  *Generic `xml:"subject"`
+	Body     *Generic `xml:"body"`
+	Thread   *Generic `xml:"thread"`
+}
+
+// presence stanza
+type Presence struct {
+	XMLName  xml.Name `xml:"presence"`
+	Stanza
+	Show     *Generic `xml:"show"`
+	Status   *Generic `xml:"status"`
+	Priority *Generic `xml:"priority"`
+}
+
+// iq stanza
+type Iq struct {
+	XMLName  xml.Name `xml:"iq"`
+	Stanza
+}
 
 // Describes an XMPP stanza error. See RFC 3920, Section 9.3.
 type Error struct {
@@ -280,77 +244,21 @@
 	return string(buf)
 }
 
-func (m *Message) GetId() string {
-	return m.Id
-}
-
-func (m *Message) addNested(n interface{}) {
-	m.Nested = append(m.Nested, n)
-}
-
-func (m *Message) innerxml() string {
-	return m.Innerxml
-}
-
-func (p *Presence) GetId() string {
-	return p.Id
-}
-
-func (p *Presence) addNested(n interface{}) {
-	p.Nested = append(p.Nested, n)
-}
-
-func (p *Presence) innerxml() string {
-	return p.Innerxml
-}
-
-func (iq *Iq) GetId() string {
-	return iq.Id
-}
-
-func (iq *Iq) addNested(n interface{}) {
-	iq.Nested = append(iq.Nested, n)
-}
-
-func (iq *Iq) innerxml() string {
-	return iq.Innerxml
-}
-
-
-// Parse a string into a struct implementing Stanza -- this will be
-// either an Iq, a Message, or a Presence.
-func ParseStanza(str string) (Stanza, error) {
-	r := strings.NewReader(str)
-	p := xml.NewDecoder(r)
-	tok, err := p.Token()
-	if err != nil {
-		return nil, err
-	}
-	se, ok := tok.(xml.StartElement)
-	if !ok {
-		return nil, errors.New("Not a start element")
-	}
-	var stan Stanza
-	switch se.Name.Local {
-	case "iq":
-		stan = &Iq{}
-	case "message":
-		stan = &Message{}
-	case "presence":
-		stan = &Presence{}
-	default:
-		return nil, errors.New("Not iq, message, or presence")
-	}
-	err = p.DecodeElement(stan, &se)
-	if err != nil {
-		return nil, err
-	}
-	return stan, nil
-}
-
 var bindExt Extension = Extension{StanzaHandlers: map[string]func(*xml.Name) interface{}{NsBind: newBind},
 	Start: func(cl *Client) {}}
 
 func newBind(name *xml.Name) interface{} {
 	return &bindIq{}
 }
+
+func getStanza(v interface{}) *Stanza {
+	switch s := v.(type) {
+	case *Iq:
+		return &s.Stanza
+	case *Message:
+		return &s.Stanza
+	case *Presence:
+		return &s.Stanza
+	}
+	return nil
+}
--- a/structs_test.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/structs_test.go	Sun Dec 16 22:17:49 2012 -0700
@@ -89,71 +89,14 @@
 }
 
 func TestIqMarshal(t *testing.T) {
-	iq := &Iq{Type: "set", Id: "3", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsBind,
-		Local: "bind"}}}}
+	iq := &Iq{Stanza: Stanza{Type: "set", Id: "3",
+		Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsBind,
+			Local: "bind"}}}}}
 	exp := `<iq id="3" type="set"><bind xmlns="` + NsBind +
 		`"></bind></iq>`
 	assertMarshal(t, exp, iq)
 }
 
-func TestParseStanza(t *testing.T) {
-	str := `<iq to="alice" from="bob" id="1" type="A"` +
-		` xml:lang="en"><foo>text</foo></iq>`
-	st, err := ParseStanza(str)
-	if err != nil {
-		t.Fatalf("iq: %v", err)
-	}
-	iq, ok := st.(*Iq)
-	if !ok {
-		t.Fatalf("not iq: %v", st)
-	}
-	assertEquals(t, "iq", iq.XMLName.Local)
-	assertEquals(t, "alice", iq.To)
-	assertEquals(t, "bob", iq.From)
-	assertEquals(t, "1", iq.Id)
-	assertEquals(t, "A", iq.Type)
-	assertEquals(t, "en", iq.Lang)
-	if iq.Error != nil {
-		t.Errorf("iq: error %v", iq.Error)
-	}
-	if st.innerxml() == "" {
-		t.Errorf("iq: empty child")
-	}
-	assertEquals(t, "<foo>text</foo>", st.innerxml())
-	assertEquals(t, st.innerxml(), iq.Innerxml)
-
-	str = `<message to="alice" from="bob"/>`
-	st, err = ParseStanza(str)
-	if err != nil {
-		t.Fatalf("message: %v", err)
-	}
-	m, ok := st.(*Message)
-	if !ok {
-		t.Fatalf("not message: %v", st)
-	}
-	assertEquals(t, "message", m.XMLName.Local)
-	assertEquals(t, "alice", m.To)
-	assertEquals(t, "bob", m.From)
-	assertEquals(t, "", m.Id)
-	assertEquals(t, "", m.Lang)
-	if m.Error != nil {
-		t.Errorf("message: error %v", m.Error)
-	}
-	if st.innerxml() != "" {
-		t.Errorf("message: child %v", st.innerxml())
-	}
-
-	str = `<presence/>`
-	st, err = ParseStanza(str)
-	if err != nil {
-		t.Fatalf("presence: %v", err)
-	}
-	_, ok = st.(*Presence)
-	if !ok {
-		t.Fatalf("not presence: %v", st)
-	}
-}
-
 func TestMarshalEscaping(t *testing.T) {
 	msg := &Message{Body: &Generic{XMLName: xml.Name{Local: "body"},
 		Chardata: `&<!-- "`}}
--- a/xmpp.go	Sun Dec 16 19:55:17 2012 -0700
+++ b/xmpp.go	Sun Dec 16 22:17:49 2012 -0700
@@ -82,18 +82,18 @@
 	// Incoming XMPP stanzas from the server will be published on
 	// this channel. Information which is only used by this
 	// library to set up the XMPP stream will not appear here.
-	In <-chan Stanza
+	In <-chan interface{}
 	// Outgoing XMPP stanzas to the server should be sent to this
 	// channel.
-	Out    chan<- Stanza
+	Out    chan<- interface{}
 	xmlOut chan<- interface{}
 	// Features advertised by the remote. This will be updated
 	// asynchronously as new features are received throughout the
 	// connection process. It should not be updated once
 	// StartSession() returns.
 	Features  *Features
-	filterOut chan<- <-chan Stanza
-	filterIn  <-chan <-chan Stanza
+	filterOut chan<- <-chan interface{}
+	filterIn  <-chan <-chan interface{}
 }
 
 // Connect to the appropriate server and authenticate as the given JID
@@ -200,23 +200,23 @@
 	return ch
 }
 
-func (cl *Client) startStreamReader(xmlIn <-chan interface{}, srvOut chan<- interface{}) <-chan Stanza {
-	ch := make(chan Stanza)
+func (cl *Client) startStreamReader(xmlIn <-chan interface{}, srvOut chan<- interface{}) <-chan interface{} {
+	ch := make(chan interface{})
 	go cl.readStream(xmlIn, ch)
 	return ch
 }
 
-func (cl *Client) startStreamWriter(xmlOut chan<- interface{}) chan<- Stanza {
-	ch := make(chan Stanza)
+func (cl *Client) startStreamWriter(xmlOut chan<- interface{}) chan<- interface{} {
+	ch := make(chan interface{})
 	go writeStream(xmlOut, ch, cl.inputControl)
 	return ch
 }
 
-func (cl *Client) startFilter(srvIn <-chan Stanza) <-chan Stanza {
-	cliIn := make(chan Stanza)
-	filterOut := make(chan (<-chan Stanza))
-	filterIn := make(chan (<-chan Stanza))
-	nullFilter := make(chan Stanza)
+func (cl *Client) startFilter(srvIn <-chan interface{}) <-chan interface{} {
+	cliIn := make(chan interface{})
+	filterOut := make(chan (<-chan interface{}))
+	filterIn := make(chan (<-chan interface{}))
+	nullFilter := make(chan interface{})
 	go filterBottom(srvIn, nullFilter)
 	go filterTop(filterOut, filterIn, nullFilter, cliIn)
 	cl.filterOut = filterOut
@@ -268,9 +268,11 @@
 // Presence struct.  See RFC 3921, Section 3.
 func (cl *Client) StartSession(getRoster bool, pr *Presence) error {
 	id := <-Id
-	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}
+	iq := &Iq{Stanza: Stanza{To: cl.Jid.Domain, Id: id, Type: "set",
+		Nested: []interface{}{Generic{XMLName:
+			xml.Name{Space: NsSession, Local: "session"}}}}}
 	ch := make(chan error)
-	f := func(st Stanza) bool {
+	f := func(st interface{}) bool {
 		iq, ok := st.(*Iq)
 		if !ok {
 			Warn.Log("iq reply not iq; can't start session")
@@ -309,7 +311,7 @@
 // filter's output channel is given to this function, and it returns a
 // new input channel which the filter should read from. When its input
 // channel closes, the filter should close its output channel.
-func (cl *Client) AddFilter(out <-chan Stanza) <-chan Stanza {
+func (cl *Client) AddFilter(out <-chan interface{}) <-chan interface{} {
 	cl.filterOut <- out
 	return <-cl.filterIn
 }