diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/go/internal/vespa/feed/document.go | 96 | ||||
-rw-r--r-- | client/go/internal/vespa/feed/document_test.go | 79 |
2 files changed, 175 insertions, 0 deletions
diff --git a/client/go/internal/vespa/feed/document.go b/client/go/internal/vespa/feed/document.go new file mode 100644 index 00000000000..363620fc863 --- /dev/null +++ b/client/go/internal/vespa/feed/document.go @@ -0,0 +1,96 @@ +package feed + +import ( + "fmt" + "strconv" + "strings" +) + +// A Vespa document ID. +type DocumentId struct { + Type string + Namespace string + Number *int64 + Group string + UserSpecific string +} + +func (d DocumentId) Equal(o DocumentId) bool { + return d.Type == o.Type && + d.Namespace == o.Namespace && + ((d.Number == nil && o.Number == nil) || *d.Number == *o.Number) && + d.Group == o.Group && + d.UserSpecific == o.UserSpecific +} + +func (d DocumentId) String() string { + var sb strings.Builder + sb.WriteString("id:") + sb.WriteString(d.Namespace) + sb.WriteString(":") + sb.WriteString(d.Type) + sb.WriteString(":") + if d.Number != nil { + sb.WriteString("n=") + sb.WriteString(strconv.FormatInt(*d.Number, 10)) + } else if d.Group != "" { + sb.WriteString("g=") + sb.WriteString(d.Group) + } + sb.WriteString(":") + sb.WriteString(d.UserSpecific) + return sb.String() +} + +func parseError(value string) error { + return fmt.Errorf("invalid document: expected id:<namespace>:<document-type>:[n=<number>|g=<group>]:<user-specific>, got %q", value) +} + +// ParseDocumentId parses a serialized document ID string. +func ParseDocumentId(serialized string) (DocumentId, error) { + parts := strings.SplitN(serialized, ":", 4) + if len(parts) < 4 || parts[0] != "id" { + return DocumentId{}, parseError(serialized) + } + namespace := parts[1] + if namespace == "" { + return DocumentId{}, parseError(serialized) + } + docType := parts[2] + if docType == "" { + return DocumentId{}, parseError(serialized) + } + rest := strings.SplitN(parts[3], ":", 2) + if len(rest) < 2 { + return DocumentId{}, parseError(serialized) + } + var number *int64 + group := "" + userSpecific := "" + for _, part := range rest { + if number == nil && strings.HasPrefix(part, "n=") { + n, err := strconv.ParseInt(part[2:], 10, 64) + if err != nil { + return DocumentId{}, parseError(serialized) + } + number = &n + } else if group == "" && strings.HasPrefix(part, "g=") { + group = part[2:] + if len(group) == 0 { + return DocumentId{}, parseError(serialized) + } + } else { + userSpecific = part + } + } + if userSpecific == "" { + return DocumentId{}, parseError(serialized) + } + return DocumentId{ + Namespace: namespace, + Type: docType, + Number: number, + Group: group, + UserSpecific: userSpecific, + }, nil +} diff --git a/client/go/internal/vespa/feed/document_test.go b/client/go/internal/vespa/feed/document_test.go new file mode 100644 index 00000000000..93188f95b22 --- /dev/null +++ b/client/go/internal/vespa/feed/document_test.go @@ -0,0 +1,79 @@ +package feed + +import ( + "testing" +) + +func ptr[T any](v T) *T { return &v } + +func TestParseDocumentId(t *testing.T) { + tests := []struct { + in string + out DocumentId + fail bool + }{ + {"id:ns:type::user", + DocumentId{ + Namespace: "ns", + Type: "type", + UserSpecific: "user", + }, + false, + }, + {"id:ns:type:n=123:user", + DocumentId{ + Namespace: "ns", + Type: "type", + Number: ptr(int64(123)), + UserSpecific: "user", + }, + false, + }, + {"id:ns:type:g=foo:user", + DocumentId{ + Namespace: "ns", + Type: "type", + Group: "foo", + UserSpecific: "user", + }, + false, + }, + {"id:ns:type::user::specific", + DocumentId{ + Namespace: "ns", + Type: "type", + UserSpecific: "user::specific", + }, + false, + }, + {"id:ns:type:::", + DocumentId{ + Namespace: "ns", + Type: "type", + UserSpecific: ":", + }, + false, + }, + {"", DocumentId{}, true}, + {"foobar", DocumentId{}, true}, + {"idd:ns:type:user", DocumentId{}, true}, + {"id:ns::user", DocumentId{}, true}, + {"id::type:user", DocumentId{}, true}, + {"id:ns:type:g=:user", DocumentId{}, true}, + {"id:ns:type:n=:user", DocumentId{}, true}, + {"id:ns:type:n=foo:user", DocumentId{}, true}, + {"id:ns:type::", DocumentId{}, true}, + } + for i, tt := range tests { + parsed, err := ParseDocumentId(tt.in) + if err == nil && tt.fail { + t.Errorf("#%d: expected error for ParseDocumentId(%q), but got none", i, tt.in) + } + if err != nil && !tt.fail { + t.Errorf("#%d: got unexpected error for ParseDocumentId(%q) = (_, %v)", i, tt.in, err) + } + if !parsed.Equal(tt.out) { + t.Errorf("#%d: ParseDocumentId(%q) = (%s, _), want %s", i, tt.in, parsed, tt.out) + } + } +} |