summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2023-03-15 11:58:25 +0100
committerMartin Polden <mpolden@mpolden.no>2023-03-23 12:13:16 +0100
commit98d07af6a96a3f58762ee3e64550c399fb917ee4 (patch)
treed87060d15ab8c5f668691791458d4db1b7778124 /client
parent442fb4b9cd422523520f2dc29a6e512b621ead18 (diff)
Parse document ID
Diffstat (limited to 'client')
-rw-r--r--client/go/internal/vespa/feed/document.go96
-rw-r--r--client/go/internal/vespa/feed/document_test.go79
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)
+ }
+ }
+}