From 8470c01c09556223a962f90e86f17a15e6fd5210 Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Sat, 20 Jan 2018 18:09:13 +0100 Subject: [PATCH] Implement error parsing --- iq.go | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++--- iq_test.go | 24 +++++++++++++ 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/iq.go b/iq.go index 3ef76b3..68dd2b0 100644 --- a/iq.go +++ b/iq.go @@ -6,6 +6,8 @@ import ( "reflect" + "strconv" + "fluux.io/xmpp/iot" ) @@ -60,6 +62,92 @@ TODO support ability to put Raw payload */ +// ============================================================================ +// XMPP Errors + +type Err struct { + XMLName xml.Name `xml:"error"` + Reason string + Code int `xml:"code,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Text string `xml:"urn:ietf:params:xml:ns:xmpp-stanzas text"` +} + +// UnmarshalXML implements custom parsing for IQs +func (x *Err) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + x.XMLName = start.Name + + // Extract attributes + for _, attr := range start.Attr { + if attr.Name.Local == "type" { + x.Type = attr.Value + } + if attr.Name.Local == "code" { + if code, err := strconv.Atoi(attr.Value); err == nil { + x.Code = code + } + } + } + + for { + t, err := d.Token() + if err != nil { + return err + } + + switch tt := t.(type) { + + case xml.StartElement: + elt := new(Node) + + err = d.DecodeElement(elt, &tt) + if err != nil { + return err + } + + textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"} + if elt.XMLName == textName { + x.Text = string(elt.Content) + } else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" { + x.Reason = elt.XMLName.Local + } + + case xml.EndElement: + if tt == start.End() { + return nil + } + } + } +} + +func (x Err) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { + code := xml.Attr{ + Name: xml.Name{Local: "code"}, + Value: strconv.Itoa(x.Code), + } + typ := xml.Attr{ + Name: xml.Name{Local: "type"}, + Value: x.Type, + } + start.Name = xml.Name{Local: "error"} + start.Attr = append(start.Attr, code, typ) + err = e.EncodeToken(start) + + // Subtags + // Reason + reason := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: x.Reason} + e.EncodeToken(xml.StartElement{Name: reason}) + e.EncodeToken(xml.EndElement{Name: reason}) + + // Text + text := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"} + e.EncodeToken(xml.StartElement{Name: text}) + e.EncodeToken(xml.CharData(x.Text)) + e.EncodeToken(xml.EndElement{Name: text}) + + return e.EncodeToken(xml.EndElement{Name: start.Name}) +} + // ============================================================================ // IQ Packet @@ -68,7 +156,7 @@ type IQ struct { // Info/Query PacketAttrs Payload []IQPayload `xml:",omitempty"` RawXML string `xml:",innerxml"` - // Error clientError + Error Err `xml:"error,omitempty"` } func NewIQ(iqtype, from, to, id, lang string) IQ { @@ -196,8 +284,8 @@ type IQPayload interface { type Node struct { XMLName xml.Name Attrs []xml.Attr `xml:"-"` - // Content []byte `xml:",innerxml"` - Nodes []Node `xml:",any"` + Content string `xml:",innerxml"` + Nodes []Node `xml:",any"` } type Attr struct { @@ -217,7 +305,7 @@ func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return d.DecodeElement((*node)(n), &start) } -func (n *Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { +func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { start.Attr = n.Attrs start.Name = n.XMLName @@ -231,6 +319,10 @@ func (*Node) IsIQPayload() {} // ============================================================================ // Disco +const ( + NSDiscoInfo = "http://jabber.org/protocol/disco#info" +) + type DiscoInfo struct { XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"` Identity Identity `xml:"identity"` diff --git a/iq_test.go b/iq_test.go index d833e5b..aaf3343 100644 --- a/iq_test.go +++ b/iq_test.go @@ -61,6 +61,30 @@ func TestGenerateIq(t *testing.T) { } } +func TestErrorTag(t *testing.T) { + xError := Err{ + XMLName: xml.Name{Local: "error"}, + Code: 503, + Type: "cancel", + Reason: "service-unavailable", + Text: "User session not found", + } + + data, err := xml.Marshal(xError) + if err != nil { + t.Errorf("cannot marshal xml structure: %s", err) + } + + parsedError := Err{} + if err = xml.Unmarshal(data, &parsedError); err != nil { + t.Errorf("Unmarshal(%s) returned error", data) + } + + if !xmlEqual(parsedError, xError) { + t.Errorf("non matching items\n%s", cmp.Diff(parsedError, xError)) + } +} + // Compare iq structure but ignore empty namespace as they are set properly on // marshal / unmarshal. There is no need to manage them on the manually // crafted structure.