1
0
Fork 0

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.
This commit is contained in:
Matt Goodall 2012-07-18 10:42:31 +01:00
parent dde0afb688
commit 438164a9d9
3 changed files with 91 additions and 2 deletions

6
src/xmpp/ns.go Normal file
View File

@ -0,0 +1,6 @@
package xmpp
const (
nsErrorStanzas = "urn:ietf:params:xml:ns:xmpp-stanzas"
nsErrorStreams = "urn:ietf:params:xml:ns:xmpp-streams"
)

View File

@ -73,9 +73,77 @@ type Presence struct {
type Error struct { type Error struct {
XMLName xml.Name `xml:"error"` XMLName xml.Name `xml:"error"`
Type string `xml:"type,attr"` Type string `xml:"type,attr"`
Text string `xml:"text"` Payload string `xml:",innerxml"`
} }
func (e Error) Error() string { 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"}
)

View File

@ -72,3 +72,18 @@ func writeXMLAttr(w io.Writer, attr xml.Attr) error {
} }
return nil 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
}
}