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.
authorChris Jones <chris@cjones.org>
Sat, 09 Nov 2013 12:09:37 -0700
changeset 178 ccfebbd9f49b
parent 177 63f33bb8fa33
child 179 4477c9f31f43
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.
TODO.txt
example/interact.go
xmpp/layer3.go
xmpp/sasl.go
xmpp/structs.go
xmpp/structs_test.go
xmpp/xmpp.go
--- 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) {