Use an approach to build stanza that do not require a "builder" abstraction

disco_info_form
Mickael Remond 5 years ago
parent 1dacc663d3
commit 20a66dc47d
No known key found for this signature in database
GPG Key ID: E6F6045D79965AA3

@ -110,7 +110,7 @@ func discoInfoRoot(iqResp *stanza.IQ, opts xmpp.ComponentOptions) {
Space: stanza.NSDiscoInfo,
Local: "query",
},
Identity: identity,
Identity: []stanza.Identity{identity},
Features: []stanza.Feature{
{Var: stanza.NSDiscoInfo},
{Var: stanza.NSDiscoItems},
@ -148,7 +148,7 @@ func discoInfoPEP(iqResp *stanza.IQ) {
Space: stanza.NSDiscoInfo,
Local: "query",
},
Identity: identity,
Identity: []stanza.Identity{identity},
Node: pepNode,
Features: []stanza.Feature{
{Var: "http://jabber.org/protocol/pubsub#access-presence"},

@ -8,10 +8,11 @@ When creating stanzas, you can use two approaches:
1. You can create IQ, Presence or Message structs, set the fields and manually prepare extensions struct to add to the
stanza.
2. You can use `stanza` Builder providing
2. You can use `stanza` build helper to be guided when creating the stanza, and have more controls performed on the
final stanza.
The methods are equivalent and you can use whatever suits you best. The Builder will generate the same type of
struct that you can build by hand.
The methods are equivalent and you can use whatever suits you best. The helpers will finally generate the same type of
struct that you can build by hand.
### Composing stanzas manually with structs
@ -28,7 +29,7 @@ Here is for example how you would generate an IQ discovery result:
Space: stanza.NSDiscoInfo,
Local: "query",
},
Identity: identity,
Identity: []stanza.Identity{identity},
Features: []stanza.Feature{
{Var: stanza.NSDiscoInfo},
{Var: stanza.NSDiscoItems},
@ -38,19 +39,14 @@ Here is for example how you would generate an IQ discovery result:
}
iqResp.Payload = &payload
### Using Builder
### Using helpers
Here is for example how you would generate an IQ discovery result using Builder:
b := stanza.NewBuilder()
iq := b.IQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
payload := b.DiscoInfo()
identity := b.Identity("Test Component", "gateway", "service")
payload.SetFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1").
SetIdentities(identity)
iq.Payload = payload
iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
disco := iq.DiscoInfo()
disco.AddIdentity("Test Component", "gateway", "service")
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
## Payload and extensions

@ -1,62 +0,0 @@
package stanza
import (
"encoding/xml"
)
type builder struct{ lang string }
// NewBuilder create a builder structure. It act as an interface for packet generation.
// The goal is to work well with code completion to more easily.
//
// Using the builder to format and create packets is optional. You can always prepare
// your packet dealing with the struct manually and initializing them with the right values.
func NewBuilder() *builder {
return &builder{}
}
// Set default language
func (b *builder) Lang(lang string) *builder {
b.lang = lang
return b
}
func (b *builder) IQ(a Attrs) IQ {
return IQ{
XMLName: xml.Name{Local: "iq"},
Attrs: a,
}
}
func (b *builder) Message(a Attrs) Message {
return Message{
XMLName: xml.Name{Local: "message"},
Attrs: a,
}
}
func (b *builder) Presence(a Attrs) Presence {
return Presence{
XMLName: xml.Name{Local: "presence"},
Attrs: a,
}
}
// ======================================================================================
// IQ payloads
// DiscoInfo builds a default DiscoInfo payload
func (*builder) DiscoInfo() *DiscoInfo {
d := DiscoInfo{
XMLName: xml.Name{
Space: NSDiscoInfo,
Local: "query",
},
}
return &d
}
// Identity builds a identity struct for use in Disco
func (*builder) Identity(name, category, typ string) *Identity {
return &Identity{}
}

@ -1,27 +1,80 @@
package stanza
import "encoding/xml"
import (
"encoding/xml"
)
// ============================================================================
// Disco
// Disco Info
const (
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
NSDiscoItems = "http://jabber.org/protocol/disco#items"
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
)
// Disco Info
// ----------
// Namespaces
type DiscoInfo struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
Node string `xml:"node,attr,omitempty"`
Identity Identity `xml:"identity"`
Features []Feature `xml:"feature"`
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
Node string `xml:"node,attr,omitempty"`
Identity []Identity `xml:"identity"`
Features []Feature `xml:"feature"`
}
func (d *DiscoInfo) Namespace() string {
return d.XMLName.Space
}
// ---------------
// Builder helpers
// DiscoInfo builds a default DiscoInfo payload
func (iq *IQ) DiscoInfo() *DiscoInfo {
d := DiscoInfo{
XMLName: xml.Name{
Space: NSDiscoInfo,
Local: "query",
},
}
iq.Payload = &d
return &d
}
func (d *DiscoInfo) AddIdentity(name, category, typ string) {
identity := Identity{
XMLName: xml.Name{Local: "identity"},
Name: name,
Category: category,
Type: typ,
}
d.Identity = append(d.Identity, identity)
}
func (d *DiscoInfo) AddFeatures(namespace ...string) {
for _, ns := range namespace {
d.Features = append(d.Features, Feature{Var: ns})
}
}
func (d *DiscoInfo) SetNode(node string) {
d.Node = node
}
func (d *DiscoInfo) SetIdentities(ident ...Identity) *DiscoInfo {
d.Identity = ident
return d
}
func (d *DiscoInfo) SetFeatures(namespace ...string) *DiscoInfo {
for _, ns := range namespace {
d.Features = append(d.Features, Feature{Var: ns})
}
return d
}
// -----------
// SubElements
type Identity struct {
XMLName xml.Name `xml:"identity,omitempty"`
Name string `xml:"name,attr,omitempty"`
@ -34,7 +87,13 @@ type Feature struct {
Var string `xml:"var,attr"`
}
// Disco Items
// ============================================================================
// Disco Info
const (
NSDiscoItems = "http://jabber.org/protocol/disco#items"
)
type DiscoItems struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
Node string `xml:"node,attr,omitempty"`

@ -0,0 +1,55 @@
package stanza_test
import (
"encoding/xml"
"testing"
"gosrc.io/xmpp/stanza"
)
func TestDiscoInfoBuilder(t *testing.T) {
iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
disco := iq.DiscoInfo()
disco.AddIdentity("Test Component", "gateway", "service")
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
// Marshall
data, err := xml.Marshal(iq)
if err != nil {
t.Errorf("cannot marshal xml structure: %s", err)
return
}
// Unmarshall
var parsedIQ stanza.IQ
if err = xml.Unmarshal(data, &parsedIQ); err != nil {
t.Errorf("Unmarshal(%s) returned error: %s", data, err)
}
// Check result
pp, ok := parsedIQ.Payload.(*stanza.DiscoInfo)
if !ok {
t.Errorf("Parsed stanza does not contain an IQ payload")
}
// Check features
features := []string{stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1"}
if len(pp.Features) != len(features) {
t.Errorf("Features length mismatch: %#v", pp.Features)
} else {
for i, f := range pp.Features {
if f.Var != features[i] {
t.Errorf("Missing feature: %s", features[i])
}
}
}
// Check identity
if len(pp.Identity) != 1 {
t.Errorf("Identity length mismatch: %#v", pp.Identity)
} else {
if pp.Identity[0].Name != "Test Component" {
t.Errorf("Incorrect identity name: %#v", pp.Identity[0].Name)
}
}
}

@ -37,11 +37,11 @@ func TestUnmarshalIqs(t *testing.T) {
func TestGenerateIq(t *testing.T) {
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
payload := stanza.DiscoInfo{
Identity: stanza.Identity{
Name: "Test Gateway",
Category: "gateway",
Type: "mqtt",
},
Identity: []stanza.Identity{
{Name: "Test Gateway",
Category: "gateway",
Type: "mqtt",
}},
Features: []stanza.Feature{
{Var: stanza.NSDiscoInfo},
{Var: stanza.NSDiscoItems},
@ -63,8 +63,8 @@ func TestGenerateIq(t *testing.T) {
t.Errorf("Unmarshal(%s) returned error", data)
}
if !xmlEqual(parsedIQ.Payload, iq.Payload) {
t.Errorf("non matching items\n%s", cmp.Diff(parsedIQ.Payload, iq.Payload))
if !xmlEqual(iq.Payload, parsedIQ.Payload) {
t.Errorf("non matching items\n%s", xmlDiff(iq.Payload, parsedIQ.Payload))
}
}

@ -10,6 +10,15 @@ import (
// marshal / unmarshal. There is no need to manage them on the manually
// crafted structure.
func xmlEqual(x, y interface{}) bool {
return cmp.Equal(x, y, xmlOpts())
}
// xmlDiff compares xml structures ignoring namespace preferences
func xmlDiff(x, y interface{}) string {
return cmp.Diff(x, y, xmlOpts())
}
func xmlOpts() cmp.Options {
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
opts := cmp.Options{
cmp.FilterValues(func(x, y interface{}) bool {
@ -20,10 +29,12 @@ func xmlEqual(x, y interface{}) bool {
if xx == zero || yy == zero {
return true
}
if xx.Space == "" || yy.Space == "" {
return true
}
}
return false
}, alwaysEqual),
}
return cmp.Equal(x, y, opts)
return opts
}

Loading…
Cancel
Save