~samwhited/xmpp

d8c02bf59ae03c3cdb55d12be1c46024d7a7f303 — Sam Whited 4 years ago f047a0b
stanza: simplify Error type and add docs
3 files changed, 284 insertions(+), 191 deletions(-)

M stanza/error.go
M stanza/error_test.go
D stanza/stanzatype_string.go
M stanza/error.go => stanza/error.go +221 -110
@@ 1,89 1,247 @@
// Copyright 2016 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

package stanza

import (
	"encoding/xml"
	"strings"

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

type errorType int
// ErrorType is the type of an stanza error payloads.
// It should normally be one of the constants defined in this package.
type ErrorType string

const (
	// Cancel indicates that the error cannot be remedied and the operation should
	// not be retried.
	Cancel errorType = iota
	Cancel ErrorType = "cancel"

	// Auth indicates that an operation should be retried after providing
	// credentials.
	Auth
	Auth ErrorType = "auth"

	// Continue indicates that the operation can proceed (the condition was only a
	// warning).
	Continue
	Continue ErrorType = "continue"

	// Modify indicates that the operation can be retried after changing the data
	// sent.
	Modify
	Modify ErrorType = "modify"

	// Wait is indicates that an error is temporary and may be retried.
	Wait
	Wait ErrorType = "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
// Condition represents a more specific 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"
	// The sender has sent a stanza containing XML that does not conform to
	// the appropriate schema or that cannot be processed (e.g., an IQ
	// stanza that includes an unrecognized value of the 'type' attribute
	// or an element that is qualified by a recognized namespace but that
	// violates the defined syntax for the element); the associated error
	// type SHOULD be "modify".
	BadRequest Condition = "bad-request"

	// Access cannot be granted because an existing resource exists with the
	// same name or address; the associated error type SHOULD be "cancel".
	Conflict Condition = "conflict"

	// The feature represented in the XML stanza is not implemented by the
	// intended recipient or an intermediate server and therefore the stanza
	// cannot be processed (e.g., the entity understands the namespace but
	// does not recognize the element name); the associated error type
	// SHOULD be "cancel" or "modify".
	FeatureNotImplemented Condition = "feature-not-implemented"

	// The requesting entity does not possess the necessary permissions to
	// perform an action that only certain authorized roles or individuals
	// are allowed to complete (i.e., it typically relates to authorization
	// rather than authentication); the associated error type SHOULD be
	// "auth".
	Forbidden Condition = "forbidden"

	// The recipient or server can no longer be contacted at this address,
	// typically on a permanent basis (as opposed to the <redirect/> error
	// condition, which is used for temporary addressing failures); the
	// associated error type SHOULD be "cancel" and the error stanza SHOULD
	// include a new address (if available) as the XML character data of the
	// <gone/> element (which MUST be a Uniform Resource Identifier [URI] or
	// Internationalized Resource Identifier [IRI] at which the entity can
	// be contacted, typically an XMPP IRI as specified in RFC 5122).
	Gone Condition = "gone"

	// The server has experienced a misconfiguration or other internal error
	// that prevents it from processing the stanza; the associated error
	// type SHOULD be "cancel".
	InternalServerError Condition = "internal-server-error"

	// The addressed JID or item requested cannot be found; the associated
	// error type SHOULD be "cancel".
	//
	// Security Warning: An application MUST NOT return this error if
	// doing so would provide information about the intended recipient's
	// network availability to an entity that is not authorized to know
	// such information (for a more detailed discussion of presence
	// authorization, refer to the discussion of presence subscriptions
	// in RFC 6121); instead it MUST return a ServiceUnavailable
	// stanza error.
	ItemNotFound Condition = "item-not-found"

	// The sending entity has provided (e.g., during resource binding) or
	// communicated (e.g., in the 'to' address of a stanza) an XMPP address
	// that violates the rules of the mellium.im/xmpp/jid package; the
	// associated error type SHOULD be "modify".
	//
	//  Implementation Note: Enforcement of the format for XMPP localparts
	//  is primarily the responsibility of the service at which the
	//  associated account or entity is located (e.g., the example.com
	//  service is responsible for returning <jid-malformed/> errors
	//  related to all JIDs of the form <localpart@example.com>), whereas
	//  enforcement of the format for XMPP domainparts is primarily the
	//  responsibility of the service that seeks to route a stanza to the
	//  service identified by that domainpart (e.g., the example.org
	//  service is responsible for returning <jid-malformed/> errors
	//  related to stanzas that users of that service have to tried send
	//  to JIDs of the form <localpart@example.com>).  However, any entity
	//  that detects a malformed JID MAY return this error.
	JIDMalformed Condition = "jid-malformed"

	// The recipient or server understands the request but cannot process it
	// because the request does not meet criteria defined by the recipient
	// or server (e.g., a request to subscribe to information that does not
	// simultaneously include configuration parameters needed by the
	// recipient); the associated error type SHOULD be "modify".
	NotAcceptable Condition = "not-acceptable"

	// The recipient or server does not allow any entity to perform the
	// action (e.g., sending to entities at a blacklisted domain); the
	// associated error type SHOULD be "cancel".
	NotAllowed Condition = "not-allowed"

	// The sender needs to provide credentials before being allowed to
	// perform the action, or has provided improper credentials (the name
	// "not-authorized", which was borrowed from the "401 Unauthorized"
	// error of HTTP, might lead the reader to think that this condition
	// relates to authorization, but instead it is typically used in
	// relation to authentication); the associated error type SHOULD be
	// "auth".
	NotAuthorized Condition = "not-authorized"

	// The entity has violated some local service policy (e.g., a message
	// contains words that are prohibited by the service) and the server MAY
	// choose to specify the policy in the <text/> element or in an
	// application-specific condition element; the associated error type
	// SHOULD be "modify" or "wait" depending on the policy being violated.
	PolicyViolation Condition = "policy-violation"

	// The intended recipient is temporarily unavailable, undergoing
	// maintenance, etc.; the associated error type SHOULD be "wait".
	//
	// Security Warning: An application MUST NOT return this error if
	// doing so would provide information about the intended recipient's
	// network availability to an entity that is not authorized to know
	// such information (for a more detailed discussion of presence
	// authorization, refer to the discussion of presence subscriptions
	// in RFC 6121); instead it MUST return a ServiceUnavailable stanza
	// error.
	RecipientUnavailable Condition = "recipient-unavailable"

	// The recipient or server is redirecting requests for this information
	// to another entity, typically in a temporary fashion (as opposed to
	// the <gone/> error condition, which is used for permanent addressing
	// failures); the associated error type SHOULD be "modify" and the error
	// stanza SHOULD contain the alternate address in the XML character data
	// of the <redirect/> element (which MUST be a URI or IRI with which the
	// sender can communicate, typically an XMPP IRI as specified in
	// RFC 5122).
	//
	// Security Warning: An application receiving a stanza-level redirect
	// SHOULD warn a human user of the redirection attempt and request
	// approval before proceeding to communicate with the entity whose
	// address is contained in the XML character data of the <redirect/>
	// element, because that entity might have a different identity or
	// might enforce different security policies.  The end-to-end
	// authentication or signing of XMPP stanzas could help to mitigate
	// this risk, since it would enable the sender to determine if the
	// entity to which it has been redirected has the same identity as
	// the entity it originally attempted to contact.  An application MAY
	// have a policy of following redirects only if it has authenticated
	// the receiving entity.  In addition, an application SHOULD abort
	// the communication attempt after a certain number of successive
	// redirects (e.g., at least 2 but no more than 5).
	Redirect Condition = "redirect"

	// The requesting entity is not authorized to access the requested
	// service because prior registration is necessary (examples of prior
	// registration include members-only rooms in XMPP multi-user chat
	// [XEP-0045] and gateways to non-XMPP instant messaging services, which
	// traditionally required registration in order to use the gateway
	// [XEP-0100]); the associated error type SHOULD be "auth".
	RegistrationRequired Condition = "registration-required"

	// A remote server or service specified as part or all of the JID of the
	// intended recipient does not exist or cannot be resolved (e.g., there
	// is no _xmpp-server._tcp DNS SRV record, the A or AAAA fallback
	// resolution fails, or A/AAAA lookups succeed but there is no response
	// on the IANA-registered port 5269); the associated error type SHOULD
	// be "cancel".
	RemoteServerNotFound Condition = "remote-server-not-found"

	// A remote server or service specified as part or all of the JID of the
	// intended recipient (or needed to fulfill a request) was resolved but
	// communications could not be established within a reasonable amount of
	// time (e.g., an XML stream cannot be established at the resolved IP
	// address and port, or an XML stream can be established but stream
	// negotiation fails because of problems with TLS, SASL, Server
	// Dialback, etc.); the associated error type SHOULD be "wait" (unless
	// the error is of a more permanent nature, e.g., the remote server is
	// found but it cannot be authenticated or it violates security
	// policies).
	RemoteServerTimeout Condition = "remote-server-timeout"

	// The server or recipient is busy or lacks the system resources
	// necessary to service the request; the associated error type SHOULD be
	// "wait".
	ResourceConstraint Condition = "resource-constraint"

	// The server or recipient does not currently provide the requested
	// service; the associated error type SHOULD be "cancel".
	//
	// Security Warning: An application MUST return a ServiceUnavailable
	// stanza error instead of ItemNotFound or RecipientUnavailable if
	// if sending one of the latter errors would provide information about
	// the intended recipient's network availability to an entity that is
	// not authorized to know such information (for a more detailed discussion
	// of presence authorization, refer to RFC 6121).
	ServiceUnavailable Condition = "service-unavailable"

	// The requesting entity is not authorized to access the requested
	// service because a prior subscription is necessary (examples of prior
	// subscription include authorization to receive presence information as
	// defined in RFC 6121 and opt-in data feeds for XMPP publish-subscribe
	// as defined in [XEP-0060]); the associated error type SHOULD be
	// "auth".
	SubscriptionRequired Condition = "subscription-required"

	// The error condition is not one of those defined by the other
	// conditions in this list; any error type can be associated with this
	// condition, and it SHOULD NOT be used except in conjunction with an
	// application-specific condition.
	UndefinedCondition Condition = "undefined-condition"

	// The recipient or server understood the request but was not expecting
	// it at this time (e.g., the request was out of order); the associated
	// error type SHOULD be "wait" or "modify".
	UnexpectedRequest Condition = "unexpected-request"
)

// Error is an implementation of error intended to be marshalable and


@@ 91,8 249,8 @@ const (
type Error struct {
	XMLName   xml.Name
	By        *jid.JID
	Type      errorType
	Condition condition
	Type      ErrorType
	Condition Condition
	Lang      language.Tag
	Text      string
}


@@ 106,14 264,15 @@ func (se Error) Error() string {
	return string(se.Condition)
}

// MarshalXML satisfies the xml.Marshaler interface for StanzaError.
// MarshalXML satisfies the xml.Marshaler interface for Error.
func (se Error) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
	start = xml.StartElement{
		Name: xml.Name{Space: ``, Local: "error"},
		Attr: []xml.Attr{},
	}
	typattr, _ := se.Type.MarshalXMLAttr(xml.Name{Space: "", Local: "type"})
	start.Attr = append(start.Attr, typattr)
	if string(se.Type) != "" {
		start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: string(se.Type)})
	}
	if se.By != nil {
		a, _ := se.By.MarshalXMLAttr(xml.Name{Space: "", Local: "by"})
		start.Attr = append(start.Attr, a)


@@ 160,7 319,7 @@ func (se *Error) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
		Condition struct {
			XMLName xml.Name
		} `xml:",any"`
		Type errorType `xml:"type,attr"`
		Type ErrorType `xml:"type,attr"`
		By   *jid.JID  `xml:"by,attr"`
		Text []struct {
			Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr"`


@@ 172,56 331,8 @@ func (se *Error) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	}
	se.Type = decoded.Type
	se.By = decoded.By
	// TODO: Oh god why… maybe I should transform the String() output instead.
	switch decoded.Condition.XMLName.Local {
	case "bad-request":
		se.Condition = BadRequest
	case "conflict":
		se.Condition = Conflict
	case "feature-not-implemented":
		se.Condition = FeatureNotImplemented
	case "forbidden":
		se.Condition = Forbidden
	case "gone":
		se.Condition = Gone
	case "internal-server-error":
		se.Condition = InternalServerError
	case "item-not-found":
		se.Condition = ItemNotFound
	case "jid-malformed":
		se.Condition = JIDMalformed
	case "not-acceptable":
		se.Condition = NotAcceptable
	case "not-allowed":
		se.Condition = NotAllowed
	case "not-authorized":
		se.Condition = NotAuthorized
	case "policy-violation":
		se.Condition = PolicyViolation
	case "recipient-unavailable":
		se.Condition = RecipientUnavailable
	case "redirect":
		se.Condition = Redirect
	case "registration-required":
		se.Condition = RegistrationRequired
	case "remote-server-not-found":
		se.Condition = RemoteServerNotFound
	case "remote-server-timeout":
		se.Condition = RemoteServerTimeout
	case "resource-constraint":
		se.Condition = ResourceConstraint
	case "service-unavailable":
		se.Condition = ServiceUnavailable
	case "subscription-required":
		se.Condition = SubscriptionRequired
	case "undefined-condition":
		se.Condition = UndefinedCondition
	case "unexpected-request":
		se.Condition = UnexpectedRequest
	default:
		if decoded.Condition.XMLName.Space == ns.Stanza {
			se.Condition = condition(decoded.Condition.XMLName.Local)
		}
	if decoded.Condition.XMLName.Space == ns.Stanza {
		se.Condition = Condition(decoded.Condition.XMLName.Local)
	}

	// TODO: Dedup this (and probably a lot of other stuff) with the saslerr

M stanza/error_test.go => stanza/error_test.go +63 -65
@@ 1,6 1,6 @@
// Copyright 2016 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

package stanza



@@ 14,10 14,8 @@ import (
)

var (
	_ error        = (*Error)(nil)
	_ error        = Error{}
	_ fmt.Stringer = (*errorType)(nil)
	_ fmt.Stringer = Auth
	_ error = (*Error)(nil)
	_ error = Error{}
)

func TestErrorReturnsCondition(t *testing.T) {


@@ 32,89 30,89 @@ func TestErrorReturnsCondition(t *testing.T) {
}

func TestMarshalStanzaError(t *testing.T) {
	for _, data := range []struct {
	for i, data := range [...]struct {
		se  Error
		xml string
		err bool
	}{
		{Error{}, "", true},
		{Error{Condition: UnexpectedRequest}, `<error type="cancel"><unexpected-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></unexpected-request></error>`, false},
		{Error{Type: Cancel, Condition: UnexpectedRequest}, `<error type="cancel"><unexpected-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></unexpected-request></error>`, false},
		{Error{Type: Wait, Condition: UndefinedCondition}, `<error type="wait"><undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></undefined-condition></error>`, false},
		{Error{Type: Modify, By: jid.MustParse("test@example.net"), Condition: SubscriptionRequired}, `<error type="modify" by="test@example.net"><subscription-required xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></subscription-required></error>`, false},
		{Error{Type: Continue, Condition: ServiceUnavailable, Text: "test"}, `<error type="continue"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></service-unavailable><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="und">test</text></error>`, false},
		0: {Error{}, "", true},
		1: {Error{Condition: UnexpectedRequest}, `<error><unexpected-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></unexpected-request></error>`, false},
		2: {Error{Type: Cancel, Condition: UnexpectedRequest}, `<error type="cancel"><unexpected-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></unexpected-request></error>`, false},
		3: {Error{Type: Wait, Condition: UndefinedCondition}, `<error type="wait"><undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></undefined-condition></error>`, false},
		4: {Error{Type: Modify, By: jid.MustParse("test@example.net"), Condition: SubscriptionRequired}, `<error type="modify" by="test@example.net"><subscription-required xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></subscription-required></error>`, false},
		5: {Error{Type: Continue, Condition: ServiceUnavailable, Text: "test"}, `<error type="continue"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></service-unavailable><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="und">test</text></error>`, false},
	} {
		b, err := xml.Marshal(data.se)
		switch {
		case data.err && err == nil:
			t.Errorf("Expected an error when marshaling stanza error %v", data.se)
			continue
		case !data.err && err != nil:
			t.Error(err)
			continue
		case err != nil:
			continue
		case string(b) != data.xml:
			t.Errorf("Expected marshaling stanza error %v to be `%s` but got `%s`.", data.se, data.xml, string(b))
		}
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			b, err := xml.Marshal(data.se)
			switch {
			case data.err && err == nil:
				t.Errorf("Expected an error when marshaling stanza error %v", data.se)
			case !data.err && err != nil:
				t.Error(err)
			case err != nil:
				return
			case string(b) != data.xml:
				t.Errorf("Expected marshaling stanza error '%v' to be:\n`%s`\nbut got:\n`%s`.", data.se, data.xml, string(b))
			}
		})
	}
}

func TestUnmarshalStanzaError(t *testing.T) {
	for _, data := range []struct {
	for i, data := range [...]struct {
		xml  string
		lang language.Tag
		se   Error
		err  bool
	}{
		{"", language.Und, Error{}, true},
		{`<error><unexpected-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></unexpected-request></error>`,
		0: {"", language.Und, Error{}, true},
		1: {`<error><unexpected-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></unexpected-request></error>`,
			language.Und, Error{Condition: UnexpectedRequest}, false},
		{`<error type="cancel"><registration-required xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></registration-required></error>`,
			language.Und, Error{Condition: RegistrationRequired}, false},
		{`<error type="cancel"><redirect xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></redirect></error>`,
		2: {`<error type="cancel"><registration-required xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></registration-required></error>`,
			language.Und, Error{Type: Cancel, Condition: RegistrationRequired}, false},
		3: {`<error type="cancel"><redirect xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></redirect></error>`,
			language.Und, Error{Type: Cancel, Condition: Redirect}, false},
		{`<error type="wait"><undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></undefined-condition></error>`,
		4: {`<error type="wait"><undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></undefined-condition></error>`,
			language.Und, Error{Type: Wait, Condition: UndefinedCondition}, false},
		{`<error type="modify" by="test@example.net"><subscription-required xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></subscription-required></error>`,
		5: {`<error type="modify" by="test@example.net"><subscription-required xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></subscription-required></error>`,
			language.Und, Error{Type: Modify, By: jid.MustParse("test@example.net"), Condition: SubscriptionRequired}, false},
		{`<error type="continue"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></service-unavailable><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="und">test</text></error>`,
		6: {`<error type="continue"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></service-unavailable><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="und">test</text></error>`,
			language.Und, Error{Type: Continue, Condition: ServiceUnavailable, Text: "test"}, false},
		{`<error type="auth"><resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></resource-constraint><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">test</text></error>`,
		7: {`<error type="auth"><resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></resource-constraint><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">test</text></error>`,
			language.Und, Error{Type: Auth, Condition: ResourceConstraint, Text: "test", Lang: language.English}, false},
		{`<error type="auth"><resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></resource-constraint><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">test</text><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="de">German</text></error>`,
		8: {`<error type="auth"><resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></resource-constraint><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">test</text><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="de">German</text></error>`,
			language.German, Error{Type: Auth, Condition: ResourceConstraint, Text: "German", Lang: language.German}, false},
		{`<error type="auth"><remote-server-timeout xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></remote-server-timeout><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">test</text><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="es">Spanish</text></error>`,
		9: {`<error type="auth"><remote-server-timeout xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></remote-server-timeout><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">test</text><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="es">Spanish</text></error>`,
			language.LatinAmericanSpanish, Error{Type: Auth, Condition: RemoteServerTimeout, Text: "Spanish", Lang: language.Spanish}, false},
		{`<error by=""><remote-server-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></remote-server-not-found></error>`,
		10: {`<error by=""><remote-server-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></remote-server-not-found></error>`,
			language.Und, Error{By: &jid.JID{}, Condition: RemoteServerNotFound}, false},
		{`<error><other xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></other></error>`,
			language.Und, Error{Condition: condition("other")}, false},
		{`<error><recipient-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></recipient-unavailable><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="ac-u">test</text></error>`,
		11: {`<error><other xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></other></error>`,
			language.Und, Error{Condition: Condition("other")}, false},
		12: {`<error><recipient-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"></recipient-unavailable><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="ac-u">test</text></error>`,
			language.Und, Error{Condition: RecipientUnavailable}, false},
	} {
		se2 := Error{Lang: data.lang}
		err := xml.Unmarshal([]byte(data.xml), &se2)
		j1, j2 := data.se.By, se2.By
		data.se.By = nil
		se2.By = nil
		switch {
		case data.err && err == nil:
			t.Errorf("Expected an error when unmarshaling stanza error `%s`", data.xml)
			continue
		case !data.err && err != nil:
			t.Error(err)
			continue
		case err != nil:
			continue
		case !j1.Equal(j2):
			t.Errorf(`Expected by="%v" but got by="%v"`, j1, j2)
		case data.se.Lang != se2.Lang:
			// This case is included in the next one, but I wanted it to print
			// something nicer for languages…
			t.Errorf("Expected unmarshaled stanza error to have lang `%s` but got `%s`.", data.se.Lang, se2.Lang)
		case data.se != se2:
			t.Errorf("Expected unmarshaled stanza error `%#v` but got `%#v`.", data.se, se2)
		}
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			se2 := Error{Lang: data.lang}
			err := xml.Unmarshal([]byte(data.xml), &se2)
			j1, j2 := data.se.By, se2.By
			data.se.By = nil
			se2.By = nil
			switch {
			case data.err && err == nil:
				t.Errorf("Expected an error when unmarshaling stanza error `%s`", data.xml)
			case !data.err && err != nil:
				t.Error(err)
			case err != nil:
				return
			case !j1.Equal(j2):
				t.Errorf(`Expected by="%v" but got by="%v"`, j1, j2)
			case data.se.Lang != se2.Lang:
				// This case is included in the next one, but I wanted it to print
				// something nicer for languages…
				t.Errorf("Expected unmarshaled stanza error to have lang `%s` but got `%s`.", data.se.Lang, se2.Lang)
			case data.se != se2:
				t.Errorf("Expected unmarshaled stanza error:\n`%#v`\nbut got:\n`%#v`", data.se, se2)
			}
		})
	}
}

D stanza/stanzatype_string.go => stanza/stanzatype_string.go +0 -16
@@ 1,16 0,0 @@
// Code generated by "stringer -type=errorType -output stanzatype_string.go"; DO NOT EDIT

package stanza

import "fmt"

const _errorTypeName = "CancelAuthContinueModifyWait"

var _errorTypeIndex = [...]uint8{0, 6, 10, 18, 24, 28}

func (i errorType) String() string {
	if i < 0 || i >= errorType(len(_errorTypeIndex)-1) {
		return fmt.Sprintf("errorType(%d)", i)
	}
	return _errorTypeName[_errorTypeIndex[i]:_errorTypeIndex[i+1]]
}