~samwhited/xmpp

c3b37d32a31923ddd2c308dfe4fb8446c13c6838 — Sam Whited 10 months ago dd64cf4
stream: support text elements in errors

Signed-off-by: Sam Whited <sam@samwhited.com>
3 files changed, 80 insertions(+), 2 deletions(-)

M CHANGELOG.md
M stream/error.go
M stream/error_test.go
M CHANGELOG.md => CHANGELOG.md +1 -0
@@ 28,6 28,7 @@ All notable changes to this project will be documented in this file.
- stream: new `InnerXML` and `ApplicationError` methods on `Error` provide a way
  to easily construct customized stream errors
- stream: ability to compare errors with `errors.Is`
- stream: support adding human-readable text to errors
- styling: add `Disable` and `Unstyled` to support disabling styling on some
  messages
- xmpp: `SetReadDeadline` and `SetWriteDeadline` are now proxied even if the

M stream/error.go => stream/error.go +50 -1
@@ 10,6 10,7 @@ import (
	"net"

	"mellium.im/xmlstream"
	"mellium.im/xmpp/internal/ns"
)

// A list of stream errors defined in RFC 6120 §4.9.3


@@ 169,7 170,11 @@ func SeeOtherHostError(addr net.Addr) Error {
// Error represents an unrecoverable stream-level error that may include
// character data or arbitrary inner XML.
type Error struct {
	Err string
	Err  string
	Text []struct {
		Lang  string
		Value string
	}

	innerXML xml.TokenReader
	payload  xml.TokenReader


@@ 206,6 211,12 @@ func (s Error) Error() string {
func (s *Error) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	for {
		tok, err := d.Token()
		if err == io.EOF {
			err = nil
			if tok == nil {
				return nil
			}
		}
		if err != nil {
			return err
		}


@@ 223,6 234,28 @@ func (s *Error) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

		switch {
		case start.Name.Local == "text" && start.Name.Space == ErrorNS:
			var lang string
			for _, attr := range start.Attr {
				if attr.Name.Local == "lang" && attr.Name.Space == ns.XML {
					lang = attr.Value
					break
				}
			}
			t := struct {
				XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-streams text"`
				Text    string   `xml:",chardata"`
			}{}
			err = d.DecodeElement(&t, &start)
			if err != nil {
				return err
			}
			s.Text = append(s.Text, struct {
				Lang  string
				Value string
			}{
				Lang:  lang,
				Value: t.Text,
			})
		case start.Name.Space == ErrorNS:
			s.Err = start.Name.Local
		}


@@ 255,6 288,22 @@ func (s Error) TokenReader() xml.TokenReader {
			s.payload,
		)
	}
	for _, txt := range s.Text {
		start := xml.StartElement{Name: xml.Name{Space: ErrorNS, Local: "text"}}
		if txt.Lang != "" {
			start.Attr = append(start.Attr, xml.Attr{
				Name:  xml.Name{Space: ns.XML, Local: "lang"},
				Value: txt.Lang,
			})
		}
		inner = xmlstream.MultiReader(
			inner,
			xmlstream.Wrap(
				xmlstream.Token(xml.CharData(txt.Value)),
				start,
			),
		)
	}
	return xmlstream.Wrap(
		inner,
		xml.StartElement{

M stream/error_test.go => stream/error_test.go +29 -1
@@ 73,6 73,22 @@ var marshalTests = [...]struct {
		se:  stream.UnsupportedEncoding.ApplicationError(xmlstream.Token(xml.CharData("test"))).InnerXML(xmlstream.Token(xml.CharData("foo"))),
		xml: `<error xmlns="http://etherx.jabber.org/streams"><unsupported-encoding xmlns="urn:ietf:params:xml:ns:xmpp-streams">foo</unsupported-encoding>test</error>`,
	},
	6: {
		se:  stream.UnsupportedEncoding.ApplicationError(xmlstream.Token(xml.CharData("test"))).InnerXML(xmlstream.Token(xml.CharData("foo"))),
		xml: `<error xmlns="http://etherx.jabber.org/streams"><unsupported-encoding xmlns="urn:ietf:params:xml:ns:xmpp-streams">foo</unsupported-encoding>test</error>`,
	},
	7: {
		se: stream.Error{Err: "undefined-condition", Text: []struct {
			Lang  string
			Value string
		}{{
			Lang:  "en",
			Value: "some value",
		}, {
			Value: "some error",
		}}},
		xml: `<error xmlns="http://etherx.jabber.org/streams"><undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-streams"></undefined-condition><text xmlns="urn:ietf:params:xml:ns:xmpp-streams" xml:lang="en">some value</text><text xmlns="urn:ietf:params:xml:ns:xmpp-streams">some error</text></error>`,
	},
}

func TestMarshal(t *testing.T) {


@@ 110,6 126,18 @@ var unmarshalTests = [...]struct {
		se:  stream.RestrictedXML,
		err: true,
	},
	2: {
		xml: `<error xmlns="http://etherx.jabber.org/streams"><undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-streams"></undefined-condition><text xmlns="urn:ietf:params:xml:ns:xmpp-streams" xml:lang="en">some value</text><text xmlns="urn:ietf:params:xml:ns:xmpp-streams">some error</text></error>`,
		se: stream.Error{Err: "undefined-condition", Text: []struct {
			Lang  string
			Value string
		}{{
			Lang:  "en",
			Value: "some value",
		}, {
			Value: "some error",
		}}},
	},
}

func TestUnmarshal(t *testing.T) {


@@ 122,7 150,7 @@ func TestUnmarshal(t *testing.T) {
				t.Errorf("expected unmarshaling error for `%v` to fail", test.xml)
				return
			case !test.err && err != nil:
				t.Error(err)
				t.Errorf("unexpected error: %v", err)
				return
			case err != nil:
				return