diff --git a/dnssd/attributes.go b/dnssd/attributes.go index 3f7b702..0f10617 100644 --- a/dnssd/attributes.go +++ b/dnssd/attributes.go @@ -113,8 +113,8 @@ func (a Attributes) Flags() map[string]struct{} { return flags } -// Without returns a clone of the attributes wouth the given keys, regardless of -// whether they are key/value pairs or flags. +// Without returns a clone of the attributes without the given keys, regardless +// of whether they are key/value pairs or flags. func (a Attributes) Without(keys ...string) Attributes { return a.mutate(func(m map[string][]byte) { for _, k := range keys { @@ -319,6 +319,73 @@ func normalizeAttributeKey(k string) (string, error) { // contains the attributes conveyed in a separate TXT record. type AttributeCollection []Attributes +// Get returns the last value that is associated with the key k. +// +// ok is true there is a key/value pair with this key. +func (c AttributeCollection) Get(k string) (v []byte, ok bool) { + k = mustNormalizeAttributeKey(k) + + for i := len(c) - 1; i >= 0; i-- { + a := c[i] + v = a.m[k] + if v != nil { + return v, true + } + } + + return nil, false +} + +// Pairs returns the key/value pair (i.e. non-flag) attributes. +func (c AttributeCollection) Pairs() map[string][]byte { + attrs := map[string][]byte{} + + for _, a := range c { + for k, v := range a.m { + if v != nil { + attrs[k] = v + } + } + } + + return attrs +} + +// HasFlags returns true if all of the given flags are present in the +// attributes. +func (c AttributeCollection) HasFlags(keys ...string) bool { +key: + for _, k := range keys { + k = mustNormalizeAttributeKey(k) + + for _, a := range c { + v, ok := a.m[k] + if ok && v == nil { + continue key + } + } + + return false + } + + return true +} + +// Flags returns the flag (i.e. non-pair) attributes that are set. +func (c AttributeCollection) Flags() map[string]struct{} { + flags := map[string]struct{}{} + + for _, a := range c { + for k, v := range a.m { + if v == nil { + flags[k] = struct{}{} + } + } + } + + return flags +} + // Equal returns true if c and x contain the same sets of attributes, in any // order. func (c AttributeCollection) Equal(x AttributeCollection) bool { diff --git a/dnssd/attributes_test.go b/dnssd/attributes_test.go index 126a8c9..717c667 100644 --- a/dnssd/attributes_test.go +++ b/dnssd/attributes_test.go @@ -95,7 +95,7 @@ var _ = Describe("type Attributes", func() { Expect(ok).To(BeFalse()) }) - It("returns false the key is a flag", func() { + It("returns false if the key is a flag", func() { attrs := NewAttributes(). WithFlag("") @@ -402,3 +402,140 @@ var _ = Describe("type Attributes", func() { }) }) }) + +var _ = Describe("type AttributeCollection", func() { + Describe("func Get()", func() { + It("returns the associated value", func() { + col := AttributeCollection{ + NewAttributes(). + WithPair("", []byte("")), + } + + v, ok := col.Get("") + Expect(v).To(Equal([]byte(""))) + Expect(ok).To(BeTrue()) + }) + + It("returns false if there is no such key", func() { + col := AttributeCollection{} + + _, ok := col.Get("") + Expect(ok).To(BeFalse()) + }) + + It("returns false if the key is a flag", func() { + col := AttributeCollection{ + NewAttributes(). + WithFlag(""), + } + + _, ok := col.Get("") + Expect(ok).To(BeFalse()) + }) + + It("is case insensitive", func() { + col := AttributeCollection{ + NewAttributes(). + WithPair("", []byte("")), + } + + v, ok := col.Get("") + Expect(v).To(Equal([]byte(""))) + Expect(ok).To(BeTrue()) + }) + + It("returns the value from the last / right-most attribute set", func() { + col := AttributeCollection{ + NewAttributes(). + WithPair("", []byte("")), + NewAttributes(). + WithPair("", []byte("")), + } + + v, ok := col.Get("") + Expect(v).To(Equal([]byte(""))) + Expect(ok).To(BeTrue()) + }) + }) + + Describe("func Pairs()", func() { + It("returns the binary attributes", func() { + col := AttributeCollection{ + NewAttributes(). + WithPair("", []byte("")), + NewAttributes(). + WithPair("", nil), + NewAttributes(). + WithFlag(""), + NewAttributes(). + WithPair("", []byte("")), + } + + Expect(col.Pairs()).To(Equal( + map[string][]byte{ + "": []byte(""), + "": {}, + }, + )) + }) + }) + + Describe("func HasFlags()", func() { + DescribeTable( + "it returns the expected result", + func(expect bool, keys ...string) { + col := AttributeCollection{ + NewAttributes(). + WithFlag(""), + NewAttributes(). + WithFlag(""), + } + + Expect(col.HasFlags(keys...)).To(Equal(expect)) + }, + Entry("no flags", true), + Entry("equivalent set", true, "", ""), + Entry("superset", true, ""), + Entry("subset", false, "", "", ""), + Entry("disjoint set", false, "", ""), + ) + + It("returns false if the key has a binary value associated with it", func() { + col := AttributeCollection{ + NewAttributes(). + WithPair("", []byte("")), + } + + Expect(col.HasFlags("")).To(BeFalse()) + }) + + It("is case insensitive", func() { + col := AttributeCollection{ + NewAttributes(). + WithFlag(""), + } + + Expect(col.HasFlags("")).To(BeTrue()) + }) + }) + + Describe("func Flags()", func() { + It("returns the flags that are set", func() { + col := AttributeCollection{ + NewAttributes(). + WithFlag(""), + NewAttributes(). + WithFlag(""), + NewAttributes(). + WithPair("", []byte("")), + } + + Expect(col.Flags()).To(Equal( + map[string]struct{}{ + "": {}, + "": {}, + }, + )) + }) + }) +})