~samwhited/xmpp

389c67fab426aad0bb494eb499d1368329fdc07a — Sam Whited 5 years ago c0106c5
Refactor stream errors
2 files changed, 118 insertions(+), 11 deletions(-)

M errors.go
M errors_test.go
M errors.go => errors.go +113 -10
@@ 6,8 6,11 @@ package xmpp

import (
	"encoding/xml"
	"strings"

	"golang.org/x/text/language"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/ns"
)

type errorType int


@@ 33,21 36,121 @@ const (
	Wait
)

func (t errorType) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: strings.ToLower(t.String())}, nil
}

func (t *errorType) UnmarshalXMLAttr(attr xml.Attr) error {
	switch attr.Value {
	case "auth":
		*t = Auth
	case "continue":
		*t = Continue
	case "modify":
		*t = Modify
	case "wait":
		*t = Wait
	default: // case "cancel":
		*t = Cancel
	}
	return nil
}

// condition represents a stanza error condition that can be encapsulated by an
// <error/> element.
type condition string

// A list of stanza error conditions defined in RFC 6120 §8.3.3
const (
	BadRequest            condition = "bad-request"
	Conflict              condition = "conflict"
	FeatureNotImplemented condition = "feature-not-implemented"
	Forbidden             condition = "forbidden"
	Gone                  condition = "gone"
	InternalServerError   condition = "internal-server-error"
	ItemNotFound          condition = "item-not-found"
	JIDMalformed          condition = "jid-malformed"
	NotAcceptable         condition = "not-acceptable"
	NotAllowed            condition = "not-allowed"
	NotAuthorized         condition = "not-authorized"
	PolicyViolation       condition = "policy-violation"
	RecipientUnavailable  condition = "recipient-unavailable"
	Redirect              condition = "redirect"
	RegistrationRequired  condition = "registration-required"
	RemoteServerNotFound  condition = "remote-server-not-found"
	RemoteServerTimeout   condition = "remote-server-timeout"
	ResourceConstraint    condition = "resource-constraint"
	ServiceUnavailable    condition = "service-unavailable"
	SubscriptionRequired  condition = "subscription-required"
	UndefinedCondition    condition = "undefined-condition"
	UnexpectedRequest     condition = "unexpected-request"
)

// StanzaError is an implementation of error intended to be marshalable and
// unmarshalable as XML.
type StanzaError struct {
	XMLName   xml.Name
	By        jid.JID `xml:"by,attr,omitempty"`
	Type      string  `xml:"type,attr"`
	Condition string  `xml:"-"`
	Text      struct {
		Lang     string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
		CharData string `xml:",chardata"`
	} `xml:"text urn:ietf:params:xml:ns:xmpp-stanzas"`
	InnerXML string `xml:",innerxml"`
	By        *jid.JID
	Type      errorType
	Condition condition
	Lang      language.Tag
	Text      string
}

// Error satisfies the error interface and returns the condition.
// Error satisfies the error interface and returns the text if set, or the
// condition otherwise.
func (e StanzaError) Error() string {
	return e.Condition
	if e.Text != "" {
		return e.Text
	}
	return string(e.Condition)
}

func (se StanzaError) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	start = xml.StartElement{
		Name: xml.Name{Space: ``, Local: "error"},
		Attr: []xml.Attr{},
	}
	typattr, err := se.Type.MarshalXMLAttr(xml.Name{Space: "", Local: "type"})
	if err != nil {
		return err
	}
	start.Attr = append(start.Attr, typattr)
	if se.By != nil {
		start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "by"}, Value: ""})
	}
	if err = e.EncodeToken(start); err != nil {
		return err
	}
	condition := xml.StartElement{
		Name: xml.Name{Space: ns.Stanza, Local: string(se.Condition)},
	}
	if err = e.EncodeToken(condition); err != nil {
		return err
	}
	if err = e.EncodeToken(condition.End()); err != nil {
		return err
	}
	if se.Text != "" {
		text := xml.StartElement{
			Name: xml.Name{Space: ns.Stanza, Local: "text"},
			Attr: []xml.Attr{
				xml.Attr{
					Name:  xml.Name{Space: ns.XML, Local: "lang"},
					Value: se.Lang.String(),
				},
			},
		}
		if err = e.EncodeToken(text); err != nil {
			return err
		}
		if err = e.EncodeToken(xml.CharData(se.Text)); err != nil {
			return err
		}
		if err = e.EncodeToken(text.End()); err != nil {
			return err
		}
	}
	e.EncodeToken(start.End())
	return nil
}

M errors_test.go => errors_test.go +5 -1
@@ 18,7 18,11 @@ var (

func TestErrorReturnsCondition(t *testing.T) {
	s := StanzaError{Condition: "leprosy"}
	if s.Condition != s.Error() {
	if string(s.Condition) != s.Error() {
		t.Errorf("Expected stanza error to return condition `leprosy` but got %s", s.Error())
	}
	s = StanzaError{Condition: "nope", Text: "Text"}
	if s.Text != s.Error() {
		t.Errorf("Expected stanza error to return text `Text` but got %s", s.Error())
	}
}