forked from jshiffer/go-xmpp
Use an approach to build stanza that do not require a "builder" abstraction
This commit is contained in:
parent
1dacc663d3
commit
20a66dc47d
@ -110,7 +110,7 @@ func discoInfoRoot(iqResp *stanza.IQ, opts xmpp.ComponentOptions) {
|
|||||||
Space: stanza.NSDiscoInfo,
|
Space: stanza.NSDiscoInfo,
|
||||||
Local: "query",
|
Local: "query",
|
||||||
},
|
},
|
||||||
Identity: identity,
|
Identity: []stanza.Identity{identity},
|
||||||
Features: []stanza.Feature{
|
Features: []stanza.Feature{
|
||||||
{Var: stanza.NSDiscoInfo},
|
{Var: stanza.NSDiscoInfo},
|
||||||
{Var: stanza.NSDiscoItems},
|
{Var: stanza.NSDiscoItems},
|
||||||
@ -148,7 +148,7 @@ func discoInfoPEP(iqResp *stanza.IQ) {
|
|||||||
Space: stanza.NSDiscoInfo,
|
Space: stanza.NSDiscoInfo,
|
||||||
Local: "query",
|
Local: "query",
|
||||||
},
|
},
|
||||||
Identity: identity,
|
Identity: []stanza.Identity{identity},
|
||||||
Node: pepNode,
|
Node: pepNode,
|
||||||
Features: []stanza.Feature{
|
Features: []stanza.Feature{
|
||||||
{Var: "http://jabber.org/protocol/pubsub#access-presence"},
|
{Var: "http://jabber.org/protocol/pubsub#access-presence"},
|
||||||
|
@ -8,9 +8,10 @@ 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
|
1. You can create IQ, Presence or Message structs, set the fields and manually prepare extensions struct to add to the
|
||||||
stanza.
|
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
|
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.
|
struct that you can build by hand.
|
||||||
|
|
||||||
### Composing stanzas manually with structs
|
### Composing stanzas manually with structs
|
||||||
@ -28,7 +29,7 @@ Here is for example how you would generate an IQ discovery result:
|
|||||||
Space: stanza.NSDiscoInfo,
|
Space: stanza.NSDiscoInfo,
|
||||||
Local: "query",
|
Local: "query",
|
||||||
},
|
},
|
||||||
Identity: identity,
|
Identity: []stanza.Identity{identity},
|
||||||
Features: []stanza.Feature{
|
Features: []stanza.Feature{
|
||||||
{Var: stanza.NSDiscoInfo},
|
{Var: stanza.NSDiscoInfo},
|
||||||
{Var: stanza.NSDiscoItems},
|
{Var: stanza.NSDiscoItems},
|
||||||
@ -38,19 +39,14 @@ Here is for example how you would generate an IQ discovery result:
|
|||||||
}
|
}
|
||||||
iqResp.Payload = &payload
|
iqResp.Payload = &payload
|
||||||
|
|
||||||
### Using Builder
|
### Using helpers
|
||||||
|
|
||||||
Here is for example how you would generate an IQ discovery result using Builder:
|
Here is for example how you would generate an IQ discovery result using Builder:
|
||||||
|
|
||||||
b := stanza.NewBuilder()
|
iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
|
||||||
iq := b.IQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
|
disco := iq.DiscoInfo()
|
||||||
|
disco.AddIdentity("Test Component", "gateway", "service")
|
||||||
payload := b.DiscoInfo()
|
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
||||||
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
|
|
||||||
|
|
||||||
## Payload and extensions
|
## 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,20 +1,23 @@
|
|||||||
package stanza
|
package stanza
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Disco
|
// Disco Info
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
|
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
|
||||||
NSDiscoItems = "http://jabber.org/protocol/disco#items"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Disco Info
|
// ----------
|
||||||
|
// Namespaces
|
||||||
|
|
||||||
type DiscoInfo struct {
|
type DiscoInfo struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
||||||
Node string `xml:"node,attr,omitempty"`
|
Node string `xml:"node,attr,omitempty"`
|
||||||
Identity Identity `xml:"identity"`
|
Identity []Identity `xml:"identity"`
|
||||||
Features []Feature `xml:"feature"`
|
Features []Feature `xml:"feature"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +25,56 @@ func (d *DiscoInfo) Namespace() string {
|
|||||||
return d.XMLName.Space
|
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 {
|
type Identity struct {
|
||||||
XMLName xml.Name `xml:"identity,omitempty"`
|
XMLName xml.Name `xml:"identity,omitempty"`
|
||||||
Name string `xml:"name,attr,omitempty"`
|
Name string `xml:"name,attr,omitempty"`
|
||||||
@ -34,7 +87,13 @@ type Feature struct {
|
|||||||
Var string `xml:"var,attr"`
|
Var string `xml:"var,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disco Items
|
// ============================================================================
|
||||||
|
// Disco Info
|
||||||
|
|
||||||
|
const (
|
||||||
|
NSDiscoItems = "http://jabber.org/protocol/disco#items"
|
||||||
|
)
|
||||||
|
|
||||||
type DiscoItems struct {
|
type DiscoItems struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
||||||
Node string `xml:"node,attr,omitempty"`
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
55
stanza/iq_disco_test.go
Normal file
55
stanza/iq_disco_test.go
Normal file
@ -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) {
|
func TestGenerateIq(t *testing.T) {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
|
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
payload := stanza.DiscoInfo{
|
payload := stanza.DiscoInfo{
|
||||||
Identity: stanza.Identity{
|
Identity: []stanza.Identity{
|
||||||
Name: "Test Gateway",
|
{Name: "Test Gateway",
|
||||||
Category: "gateway",
|
Category: "gateway",
|
||||||
Type: "mqtt",
|
Type: "mqtt",
|
||||||
},
|
}},
|
||||||
Features: []stanza.Feature{
|
Features: []stanza.Feature{
|
||||||
{Var: stanza.NSDiscoInfo},
|
{Var: stanza.NSDiscoInfo},
|
||||||
{Var: stanza.NSDiscoItems},
|
{Var: stanza.NSDiscoItems},
|
||||||
@ -63,8 +63,8 @@ func TestGenerateIq(t *testing.T) {
|
|||||||
t.Errorf("Unmarshal(%s) returned error", data)
|
t.Errorf("Unmarshal(%s) returned error", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !xmlEqual(parsedIQ.Payload, iq.Payload) {
|
if !xmlEqual(iq.Payload, parsedIQ.Payload) {
|
||||||
t.Errorf("non matching items\n%s", cmp.Diff(parsedIQ.Payload, iq.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
|
// marshal / unmarshal. There is no need to manage them on the manually
|
||||||
// crafted structure.
|
// crafted structure.
|
||||||
func xmlEqual(x, y interface{}) bool {
|
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 })
|
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
|
||||||
opts := cmp.Options{
|
opts := cmp.Options{
|
||||||
cmp.FilterValues(func(x, y interface{}) bool {
|
cmp.FilterValues(func(x, y interface{}) bool {
|
||||||
@ -20,10 +29,12 @@ func xmlEqual(x, y interface{}) bool {
|
|||||||
if xx == zero || yy == zero {
|
if xx == zero || yy == zero {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if xx.Space == "" || yy.Space == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, alwaysEqual),
|
}, alwaysEqual),
|
||||||
}
|
}
|
||||||
|
return opts
|
||||||
return cmp.Equal(x, y, opts)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user