{"record":{"id":"63c4da7f460f1141","repo":"siyuan-note/siyuan","slug":"carddav-address-book-path-is-invalid","errorCode":null,"errorMessage":"CardDAV: address book path is invalid","messagePattern":"CardDAV: address book path is invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/carddav.go","lineNumber":94,"sourceCode":"\tdefaultAddressBook = carddav.AddressBook{\n\t\tPath:                 CardDavDefaultAddressBookPath,\n\t\tName:                 CardDavDefaultAddressBookName,\n\t\tDescription:          \"Default address book\",\n\t\tMaxResourceSize:      addressBookMaxResourceSize,\n\t\tSupportedAddressData: addressBookSupportedAddressData,\n\t}\n\tcontacts = Contacts{\n\t\tloaded:        false,\n\t\tchanged:       false,\n\t\tlock:          sync.Mutex{},\n\t\tbooks:         sync.Map{},\n\t\tbooksMetaData: []*carddav.AddressBook{},\n\t}\n\n\tErrorCardDavPathInvalid = errors.New(\"CardDAV: path is invalid\")\n\n\tErrorCardDavBookNotFound    = errors.New(\"CardDAV: address book not found\")\n\tErrorCardDavBookPathInvalid = errors.New(\"CardDAV: address book path is invalid\")\n\n\tErrorCardDavAddressNotFound                 = errors.New(\"CardDAV: address not found\")\n\tErrorCardDavAddressFileExtensionNameInvalid = errors.New(\"CardDAV: address file extension name is invalid\")\n)\n\n// ImportAddressBook imports an address book from a vCard file (*.vcf)\nfunc ImportAddressBook(addressBookPath, cardContent string) (addresses []*AddressObject, err error) {\n\t// TODO: Check whether the path is valid (PathDepth: Address)\n\t// TODO: Check whether the address book exists\n\t// TODO: Decode the card content\n\t// TODO: Save the cards to the file system\n\treturn\n}\n\n// ExportAddressBook exports an address book to a vCard file (*.vcf)\nfunc ExportAddressBook(addressBookPath string) (cardContent string, err error) {\n\t// TODO: Check whether the path is valid (PathDepth: AddressBook)\n\t// TODO: Check whether the address book exists","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/carddav.go#L76-L112","documentation":"ErrorCardDavBookPathInvalid is returned by ParseAddressPath (kernel/model/carddav.go:133-136) when the parent portion of an address URL does not sit at exactly cardDavPathDepth_AddressBook — i.e. the path does not have the shape /carddav/principals/main/contacts/<book>/<file>.vcf. It is a structural URL validation error thrown before any storage lookup, distinguishing 'malformed book path' from 'book exists but was not found'.","triggerScenarios":"ParseAddressPath (called from GetAddress, DeleteAddress, PutAddressObject, etc.) receives an address path whose addressBookPath depth is not 5: e.g. an address placed directly in the home set (.../contacts/x.vcf), nested extra segments (.../contacts/a/b/x.vcf), or querying a book path itself as if it were an address.","commonSituations":"A client configured with the home set as its 'address book' URL and then appending only the card file name; hand-built paths with a missing or duplicated segment; older client versions or migrations that stored pre-hierarchy CardDAV URLs; integration scripts using the wrong base path constant.","solutions":["Ensure the URL has exactly the 6-segment shape /carddav/principals/main/contacts/<book>/<id>.vcf; add the missing address book segment or remove extra ones.","Build address paths from the constants (CardDavHomeSetPath + \"/\" + bookName + \"/\" + id + \".vcf\") instead of string concatenation of guesses.","Point the client at a real address book URL (e.g. CardDavDefaultAddressBookPath) rather than the home set when performing per-card operations.","Check GetCardDavPathDepth(bookPath) == 5 (cardDavPathDepth_AddressBook) in test/verification code before calling address APIs."],"exampleFix":"// before: card placed directly under the home set (depth 4 parent)\ncontacts.PutAddressObject(\"/carddav/principals/main/contacts/c1.vcf\", card, nil) // ErrorCardDavBookPathInvalid\n\n// after: include the address book segment\ncontacts.PutAddressObject(model.CardDavDefaultAddressBookPath+\"/c1.vcf\", card, nil)","handlingStrategy":"validation","validationCode":"addressPath := \"/carddav/principals/main/contacts/default/c1.vcf\"\nbookPath, id, err := model.ParseAddressPath(addressPath)\nif err != nil {\n    return fmt.Errorf(\"address path %s malformed: %w\", addressPath, err)\n}\n_ = bookPath; _ = id","typeGuard":"func isAddressPath(p string) bool {\n    _, _, err := model.ParseAddressPath(p)\n    return err == nil\n}","tryCatchPattern":"if _, _, err := model.ParseAddressPath(addressPath); err != nil {\n    if errors.Is(err, model.ErrorCardDavBookPathInvalid) {\n        return fmt.Errorf(\"URL must be /carddav/principals/main/contacts/<book>/<file>.vcf, got %s\", addressPath)\n    }\n    return err\n}","preventionTips":["Compose address URLs from constants: CardDavHomeSetPath + \"/<book>/\" + id + \".vcf\"; never hand-concatenate partial paths.","Point clients at an address book URL, not the home set, for per-card operations.","Assert path depth 5 for book segments in tests before touching address APIs.","Normalize client URLs (strip trailing slash, resolve relative references) before parsing."],"tags":["carddav","address-book","path","validation","webdav"],"backgroundTag":"invalid-argument-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}