From 438164a9d90803c21ca4eedaf2354f83e9a04085 Mon Sep 17 00:00:00 2001 From: Matt Goodall Date: Wed, 18 Jul 2012 10:42:31 +0100 Subject: [PATCH] Better/correct Error type. XMPP errors are a real pain - they have up to 3 elements. 2 are XMPP-specific and have one of two namespaces, the other is application specific. Go's xml package is good, but it's not that good! So, I've had to handle the error data as a generic Payload string attr and provide methods for retrieving the bits as conveniently as possible. Note: there's no application-specific error data yet but it should slot in ok now. --- src/xmpp/ns.go | 6 ++++ src/xmpp/stanza.go | 72 ++++++++++++++++++++++++++++++++++++++++++++-- src/xmpp/xml.go | 15 ++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/xmpp/ns.go diff --git a/src/xmpp/ns.go b/src/xmpp/ns.go new file mode 100644 index 0000000..638122e --- /dev/null +++ b/src/xmpp/ns.go @@ -0,0 +1,6 @@ +package xmpp + +const ( + nsErrorStanzas = "urn:ietf:params:xml:ns:xmpp-stanzas" + nsErrorStreams = "urn:ietf:params:xml:ns:xmpp-streams" +) diff --git a/src/xmpp/stanza.go b/src/xmpp/stanza.go index ad03e7e..aa08bcb 100644 --- a/src/xmpp/stanza.go +++ b/src/xmpp/stanza.go @@ -73,9 +73,77 @@ type Presence struct { type Error struct { XMLName xml.Name `xml:"error"` Type string `xml:"type,attr"` - Text string `xml:"text"` + Payload string `xml:",innerxml"` } func (e Error) Error() string { - return fmt.Sprintf("%s: %s", e.Type, e.Text) + if text := e.Text(); text == "" { + return fmt.Sprintf("[%s] %s", e.Type, e.Condition().Local) + } else { + return fmt.Sprintf("[%s] %s, %s", e.Type, e.Condition().Local, text) + } + panic("unreachable") } + +type errorText struct { + XMLName xml.Name + Text string `xml:",chardata"` +} + +// Create a new Error instance using the args as the payload. +func NewError(errorType string, condition ErrorCondition, text string) *Error { + + // Build payload. + buf := new(bytes.Buffer) + writeXMLStartElement(buf, &xml.StartElement{ + Name: xml.Name{"", condition.Local}, + Attr: []xml.Attr{ + {xml.Name{"", "xmlns"}, condition.Space}, + }, + }) + writeXMLEndElement(buf, &xml.EndElement{Name: xml.Name{"", condition.Local}}) + enc := xml.NewEncoder(buf) + if text != "" { + enc.Encode(errorText{xml.Name{condition.Space, "text"}, text}) + } + + return &Error{Type: errorType, Payload: string(buf.Bytes())} +} + +// Return the error text from the payload, or "" if not present. +func (e Error) Text() string { + dec := xml.NewDecoder(bytes.NewBufferString(e.Payload)) + next := startElementIter(dec) + for start := next(); start != nil; { + if start.Name.Local == "text" { + text := errorText{} + dec.DecodeElement(&text, start) + return text.Text + } + dec.Skip() + start = next() + } + return "" +} + +// Return the error condition from the payload. +func (e Error) Condition() ErrorCondition { + dec := xml.NewDecoder(bytes.NewBufferString(e.Payload)) + next := startElementIter(dec) + for start := next(); start != nil; { + if start.Name.Local != "text" && (start.Name.Space == nsErrorStanzas || start.Name.Space == nsErrorStreams) { + return ErrorCondition(start.Name) + } + dec.Skip() + start = next() + } + return ErrorCondition{} +} + +// Error condition. +type ErrorCondition xml.Name + +// Stanza errors. +var ( + FeatureNotImplemented = ErrorCondition{nsErrorStanzas, "feature-not-implemented"} +) diff --git a/src/xmpp/xml.go b/src/xmpp/xml.go index fbbb9c8..76993ef 100644 --- a/src/xmpp/xml.go +++ b/src/xmpp/xml.go @@ -72,3 +72,18 @@ func writeXMLAttr(w io.Writer, attr xml.Attr) error { } return nil } + +func startElementIter(dec *xml.Decoder) func() *xml.StartElement { + return func() *xml.StartElement { + for { + if tok, err := dec.Token(); err != nil { + return nil + } else { + if start, ok := tok.(xml.StartElement); ok { + return &start + } + } + } + return nil + } +}