Instead of making Stanza an interface that Iq, Message, and Presence implement, change it to an embedded struct.
--- 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
}