mirror of
				https://github.com/FluuxIO/go-xmpp.git
				synced 2025-11-04 01:03:44 -08:00 
			
		
		
		
	Resync with Master
Support NullableInt on MUC presence history element
This commit is contained in:
		@@ -2,6 +2,7 @@ package stanza
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -16,12 +17,130 @@ type MucPresence struct {
 | 
			
		||||
	History  History  `xml:"history,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const timeLayout = "2006-01-02T15:04:05Z"
 | 
			
		||||
 | 
			
		||||
// History implements XEP-0045: Multi-User Chat - 19.1
 | 
			
		||||
type History struct {
 | 
			
		||||
	MaxChars   int       `xml:"maxchars,attr,omitempty"`
 | 
			
		||||
	MaxStanzas int       `xml:"maxstanzas,attr,omitempty"`
 | 
			
		||||
	Seconds    int       `xml:"seconds,attr,omitempty"`
 | 
			
		||||
	Since      time.Time `xml:"since,attr,omitempty"`
 | 
			
		||||
	XMLName    xml.Name
 | 
			
		||||
	MaxChars   NullableInt `xml:"maxchars,attr,omitempty"`
 | 
			
		||||
	MaxStanzas NullableInt `xml:"maxstanzas,attr,omitempty"`
 | 
			
		||||
	Seconds    NullableInt `xml:"seconds,attr,omitempty"`
 | 
			
		||||
	Since      time.Time   `xml:"since,attr,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NullableInt struct {
 | 
			
		||||
	Value int
 | 
			
		||||
	isSet bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewNullableInt(val int) NullableInt {
 | 
			
		||||
	return NullableInt{val, true}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (n NullableInt) Get() (v int, ok bool) {
 | 
			
		||||
	return n.Value, n.isSet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalXML implements custom parsing for history element
 | 
			
		||||
func (h *History) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | 
			
		||||
	h.XMLName = start.Name
 | 
			
		||||
 | 
			
		||||
	// Extract attributes
 | 
			
		||||
	for _, attr := range start.Attr {
 | 
			
		||||
		switch attr.Name.Local {
 | 
			
		||||
		case "maxchars":
 | 
			
		||||
			v, err := strconv.Atoi(attr.Value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			h.MaxChars = NewNullableInt(v)
 | 
			
		||||
		case "maxstanzas":
 | 
			
		||||
			v, err := strconv.Atoi(attr.Value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			h.MaxStanzas = NewNullableInt(v)
 | 
			
		||||
		case "seconds":
 | 
			
		||||
			v, err := strconv.Atoi(attr.Value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			h.Seconds = NewNullableInt(v)
 | 
			
		||||
		case "since":
 | 
			
		||||
			t, err := time.Parse(timeLayout, attr.Value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			h.Since = t
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Consume remaining data until element end
 | 
			
		||||
	for {
 | 
			
		||||
		t, err := d.Token()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch tt := t.(type) {
 | 
			
		||||
		case xml.EndElement:
 | 
			
		||||
			if tt == start.End() {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h History) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
 | 
			
		||||
	mc, isMcSet := h.MaxChars.Get()
 | 
			
		||||
	ms, isMsSet := h.MaxStanzas.Get()
 | 
			
		||||
	s, isSSet := h.Seconds.Get()
 | 
			
		||||
 | 
			
		||||
	// We do not have any value, ignore history element
 | 
			
		||||
	if h.Since.IsZero() && !isMcSet && !isMsSet && !isSSet {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Encode start element and attributes
 | 
			
		||||
	start.Name = xml.Name{Local: "history"}
 | 
			
		||||
 | 
			
		||||
	if isMcSet {
 | 
			
		||||
		attr := xml.Attr{
 | 
			
		||||
			Name:  xml.Name{Local: "maxchars"},
 | 
			
		||||
			Value: strconv.Itoa(mc),
 | 
			
		||||
		}
 | 
			
		||||
		start.Attr = append(start.Attr, attr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isMsSet {
 | 
			
		||||
		attr := xml.Attr{
 | 
			
		||||
			Name:  xml.Name{Local: "maxstanzas"},
 | 
			
		||||
			Value: strconv.Itoa(ms),
 | 
			
		||||
		}
 | 
			
		||||
		start.Attr = append(start.Attr, attr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isSSet {
 | 
			
		||||
		attr := xml.Attr{
 | 
			
		||||
			Name:  xml.Name{Local: "seconds"},
 | 
			
		||||
			Value: strconv.Itoa(s),
 | 
			
		||||
		}
 | 
			
		||||
		start.Attr = append(start.Attr, attr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !h.Since.IsZero() {
 | 
			
		||||
		attr := xml.Attr{
 | 
			
		||||
			Name:  xml.Name{Local: "since"},
 | 
			
		||||
			Value: h.Since.Format(timeLayout),
 | 
			
		||||
		}
 | 
			
		||||
		start.Attr = append(start.Attr, attr)
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.EncodeToken(start); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return e.EncodeToken(xml.EndElement{Name: start.Name})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,15 +46,52 @@ func TestMucHistory(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var parsedPresence stanza.Presence
 | 
			
		||||
	if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
 | 
			
		||||
		t.Errorf("Unmarshal(%s) returned error", str)
 | 
			
		||||
		t.Errorf("Unmarshal(%s) returned error: %s", str, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var muc stanza.MucPresence
 | 
			
		||||
	if ok := parsedPresence.Get(&muc); !ok {
 | 
			
		||||
		t.Error("muc presence extension was not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if muc.History.MaxStanzas != 20 {
 | 
			
		||||
		t.Errorf("incorrect max stanza: '%d'", muc.History.MaxStanzas)
 | 
			
		||||
	if v, ok := muc.History.MaxStanzas.Get(); !ok || v != 20 {
 | 
			
		||||
		t.Errorf("incorrect MaxStanzas: '%#v'", muc.History.MaxStanzas)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://xmpp.org/extensions/xep-0045.html#example-37
 | 
			
		||||
func TestMucNoHistory(t *testing.T) {
 | 
			
		||||
	str := "<presence" +
 | 
			
		||||
		" id=\"n13mt3l\"" +
 | 
			
		||||
		" from=\"hag66@shakespeare.lit/pda\"" +
 | 
			
		||||
		" to=\"coven@chat.shakespeare.lit/thirdwitch\">" +
 | 
			
		||||
		"<x xmlns=\"http://jabber.org/protocol/muc\">" +
 | 
			
		||||
		"<history maxstanzas=\"0\"></history>" +
 | 
			
		||||
		"</x>" +
 | 
			
		||||
		"</presence>"
 | 
			
		||||
 | 
			
		||||
	maxstanzas := 0
 | 
			
		||||
 | 
			
		||||
	pres := stanza.Presence{Attrs: stanza.Attrs{
 | 
			
		||||
		From: "hag66@shakespeare.lit/pda",
 | 
			
		||||
		Id:   "n13mt3l",
 | 
			
		||||
		To:   "coven@chat.shakespeare.lit/thirdwitch",
 | 
			
		||||
	},
 | 
			
		||||
		Extensions: []stanza.PresExtension{
 | 
			
		||||
			stanza.MucPresence{
 | 
			
		||||
				History: stanza.History{MaxStanzas: stanza.NewNullableInt(maxstanzas)},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	data, err := xml.Marshal(&pres)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error("error on encode:", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if string(data) != str {
 | 
			
		||||
		t.Errorf("incorrect stanza: \n%s\n%s", str, data)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user