Changed the JID type to be an alias of string, rather than a struct. This allows it to be used as a key in a map, among other benefits.
--- a/TODO.txt Wed Nov 06 20:40:50 2013 -0700
+++ b/TODO.txt Sat Nov 09 12:09:37 2013 -0700
@@ -1,5 +1,2 @@
-Make JID a string type, with receiver functions to get its
-pieces. This way it can be used as a key in a map.
-
Don't force the client to understand the RFCs. Keep message types in a
convenient set of constants, for example.
--- a/example/interact.go Wed Nov 06 20:40:50 2013 -0700
+++ b/example/interact.go Sat Nov 09 12:09:37 2013 -0700
@@ -18,11 +18,11 @@
// Demonstrate the API, and allow the user to interact with an XMPP
// server via the terminal.
func main() {
- var jid xmpp.JID
- flag.Var(&jid, "jid", "JID to log in as")
- var pw *string = flag.String("pw", "", "password")
+ jidStr := flag.String("jid", "", "JID to log in as")
+ pw := flag.String("pw", "", "password")
flag.Parse()
- if jid.Domain == "" || *pw == "" {
+ jid := xmpp.JID(*jidStr)
+ if jid.Domain() == "" || *pw == "" {
flag.Usage()
os.Exit(2)
}
--- a/xmpp/layer3.go Wed Nov 06 20:40:50 2013 -0700
+++ b/xmpp/layer3.go Sat Nov 09 12:09:37 2013 -0700
@@ -136,12 +136,12 @@
// Now re-send the initial handshake message to start the new
// session.
- cl.sendRaw <- &stream{To: cl.Jid.Domain, Version: XMPPVersion}
+ cl.sendRaw <- &stream{To: cl.Jid.Domain(), Version: XMPPVersion}
}
// Send a request to bind a resource. RFC 3920, section 7.
func (cl *Client) bind() {
- res := cl.Jid.Resource
+ res := cl.Jid.Resource()
bindReq := &bindIq{}
if res != "" {
bindReq.Resource = &res
@@ -170,19 +170,13 @@
cl.setError(fmt.Errorf("Bad bind reply: %#v", iq))
return
}
- jidStr := bindRepl.Jid
- if jidStr == nil || *jidStr == "" {
+ jid := bindRepl.Jid
+ if jid == nil || *jid == "" {
cl.setError(fmt.Errorf("empty resource in bind %#v",
iq))
return
}
- jid := new(JID)
- if err := jid.Set(*jidStr); err != nil {
- cl.setError(fmt.Errorf("bind: an't parse JID %s: %v",
- *jidStr, err))
- return
- }
- cl.Jid = *jid
+ cl.Jid = JID(*jid)
cl.setStatus(StatusBound)
}
cl.SetCallback(msg.Id, f)
--- a/xmpp/sasl.go Wed Nov 06 20:40:50 2013 -0700
+++ b/xmpp/sasl.go Sat Nov 09 12:09:37 2013 -0700
@@ -54,7 +54,7 @@
case "success":
cl.setStatus(StatusAuthenticated)
cl.Features = nil
- ss := &stream{To: cl.Jid.Domain, Version: XMPPVersion}
+ ss := &stream{To: cl.Jid.Domain(), Version: XMPPVersion}
cl.sendRaw <- ss
}
}
@@ -80,17 +80,17 @@
passwd := cl.password
nonce := srvMap["nonce"]
- digestUri := "xmpp/" + cl.Jid.Domain
+ digestUri := "xmpp/" + cl.Jid.Domain()
nonceCount := int32(1)
nonceCountStr := fmt.Sprintf("%08x", nonceCount)
// Begin building the response. Username is
// user@domain or just domain.
var username string
- if cl.Jid.Node == "" {
- username = cl.Jid.Domain
+ if cl.Jid.Node() == "" {
+ username = cl.Jid.Domain()
} else {
- username = cl.Jid.Node
+ username = cl.Jid.Node()
}
// Generate our own nonce from random data.
--- a/xmpp/structs.go Wed Nov 06 20:40:50 2013 -0700
+++ b/xmpp/structs.go Sat Nov 09 12:09:37 2013 -0700
@@ -5,11 +5,9 @@
import (
"bytes"
"encoding/xml"
- "flag"
"fmt"
"log"
"reflect"
- "regexp"
"strings"
)
@@ -19,14 +17,7 @@
// JID represents an entity that can communicate with other
// entities. It looks like node@domain/resource. Node and resource are
// sometimes optional.
-type JID struct {
- Node string
- Domain string
- Resource string
-}
-
-var _ fmt.Stringer = &JID{}
-var _ flag.Value = &JID{}
+type JID string
// XMPP's <stream:stream> XML element
type stream struct {
@@ -98,8 +89,8 @@
type Message struct {
XMLName xml.Name `xml:"jabber:client message"`
Header
- Subject []Text `xml:"jabber:client subject"`
- Body []Text `xml:"jabber:client body"`
+ Subject []Text `xml:"jabber:client subject"`
+ Body []Text `xml:"jabber:client body"`
Thread *Data `xml:"jabber:client thread"`
}
@@ -109,9 +100,9 @@
type Presence struct {
XMLName xml.Name `xml:"presence"`
Header
- Show *Data `xml:"jabber:client show"`
- Status []Text `xml:"jabber:client status"`
- Priority *Data `xml:"jabber:client priority"`
+ Show *Data `xml:"jabber:client show"`
+ Status []Text `xml:"jabber:client status"`
+ Priority *Data `xml:"jabber:client priority"`
}
var _ Stanza = &Presence{}
@@ -147,14 +138,14 @@
// together, allowing the software to choose which language to present
// to the user.
type Text struct {
- XMLName xml.Name
+ XMLName xml.Name
Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
Chardata string `xml:",chardata"`
}
// Non-human-readable content of some sort, used by the protocol.
type Data struct {
- XMLName xml.Name
+ XMLName xml.Name
Chardata string `xml:",chardata"`
}
@@ -167,31 +158,29 @@
var _ fmt.Stringer = &Generic{}
-func (jid *JID) String() string {
- result := jid.Domain
- if jid.Node != "" {
- result = jid.Node + "@" + result
+func (j JID) Node() string {
+ at := strings.Index(string(j), "@")
+ if at == -1 {
+ return ""
}
- if jid.Resource != "" {
- result = result + "/" + jid.Resource
- }
- return result
+ return string(j[:at])
}
-// Set implements flag.Value.
-func (jid *JID) Set(val string) error {
- r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$")
- parts := r.FindStringSubmatch(val)
- if parts == nil {
- return fmt.Errorf("%s doesn't match user@domain/resource", val)
+func (j JID) Domain() string {
+ at := strings.Index(string(j), "@")
+ slash := strings.LastIndex(string(j), "/")
+ if slash == -1 {
+ slash = len(j)
}
- // jid.Node = stringprep.Nodeprep(parts[2])
- // jid.Domain = stringprep.Nodeprep(parts[3])
- // jid.Resource = stringprep.Resourceprep(parts[5])
- jid.Node = parts[2]
- jid.Domain = parts[3]
- jid.Resource = parts[5]
- return nil
+ return string(j[at+1 : slash])
+}
+
+func (j JID) Resource() string {
+ slash := strings.LastIndex(string(j), "/")
+ if slash == -1 {
+ return ""
+ }
+ return string(j[slash+1:])
}
func (s *stream) String() string {
--- a/xmpp/structs_test.go Wed Nov 06 20:40:50 2013 -0700
+++ b/xmpp/structs_test.go Sat Nov 09 12:09:37 2013 -0700
@@ -23,28 +23,19 @@
}
func TestJid(t *testing.T) {
- str := "user@domain/res"
- jid := &JID{}
- if err := jid.Set(str); err != nil {
- t.Errorf("Set(%s) failed: %s", str, err)
- }
- assertEquals(t, "user", jid.Node)
- assertEquals(t, "domain", jid.Domain)
- assertEquals(t, "res", jid.Resource)
- assertEquals(t, str, jid.String())
+ jid := JID("user@domain/res")
+ assertEquals(t, "user", jid.Node())
+ assertEquals(t, "domain", jid.Domain())
+ assertEquals(t, "res", jid.Resource())
- str = "domain.tld"
- if err := jid.Set(str); err != nil {
- t.Errorf("Set(%s) failed: %s", str, err)
+ jid = "domain.tld"
+ if jid.Node() != "" {
+ t.Errorf("Node: %v\n", jid.Node())
}
- if jid.Node != "" {
- t.Errorf("Node: %v\n", jid.Node)
+ assertEquals(t, "domain.tld", jid.Domain())
+ if jid.Resource() != "" {
+ t.Errorf("Resource: %v\n", jid.Resource())
}
- assertEquals(t, "domain.tld", jid.Domain)
- if jid.Resource != "" {
- t.Errorf("Resource: %v\n", jid.Resource)
- }
- assertEquals(t, str, jid.String())
}
func assertMarshal(t *testing.T, expected string, marshal interface{}) {
--- a/xmpp/xmpp.go Wed Nov 06 20:40:50 2013 -0700
+++ b/xmpp/xmpp.go Sat Nov 09 12:09:37 2013 -0700
@@ -93,7 +93,7 @@
pr Presence, status chan<- Status) (*Client, error) {
// Resolve the domain in the JID.
- _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
+ _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain())
if err != nil {
return nil, fmt.Errorf("LookupSrv %s: %v", jid.Domain, err)
}
@@ -211,7 +211,7 @@
}
// Initial handshake.
- hsOut := &stream{To: jid.Domain, Version: XMPPVersion}
+ hsOut := &stream{To: jid.Domain(), Version: XMPPVersion}
cl.sendRaw <- hsOut
// Wait until resource binding is complete.
@@ -224,7 +224,7 @@
// Initialize the session.
id := NextId()
- iq := &Iq{Header: Header{To: cl.Jid.Domain, Id: id, Type: "set",
+ iq := &Iq{Header: Header{To: cl.Jid.Domain(), Id: id, Type: "set",
Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}}
ch := make(chan error)
f := func(st Stanza) {