{"record":{"id":"491475ae16f848f4","repo":"siyuan-note/siyuan","slug":"newpath-is-required-asset-relink-batch","errorCode":null,"errorMessage":"newPath is required","messagePattern":"newPath is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/asset_relink_batch.go","lineNumber":77,"sourceCode":"\nfunc RelinkAssets(ctx context.Context, mappings []apicontract.AssetRelinkMapping, dryRun bool) (apicontract.AssetReferencesData, error) {\n\treturn runAssetRelinks(ctx, mappings, dryRun, false, true)\n}\n\nfunc newAssetRelinkPlan(ctx context.Context, mappings []apicontract.AssetRelinkMapping, dryRun, lookup, batch bool) (*assetRelinkPlan, error) {\n\tp := &assetRelinkPlan{ctx: ctx, batch: batch, lookup: lookup, assetRelinker: &assetRelinker{\n\t\tcollectOnly: true, routes: map[string]*assetRelinker{},\n\t\tresult: apicontract.AssetReferencesData{References: []apicontract.AssetReference{}, SkippedNotebooks: []string{}, DryRun: dryRun},\n\t}}\n\tif len(mappings) == 0 {\n\t\treturn p, errors.New(\"at least one asset path or mapping is required\")\n\t}\n\tseen := map[string]bool{}\n\tvar validationErr error\n\tfor _, mapping := range mappings {\n\t\trule, err := newAssetRelinker(mapping.OldPath, mapping.NewPath)\n\t\tif !lookup && mapping.NewPath == \"\" {\n\t\t\terr = errors.New(\"newPath is required\")\n\t\t}\n\t\titem := &assetRelinkItem{input: mapping, rule: rule}\n\t\tp.items = append(p.items, item)\n\t\tif rule.oldPath != \"\" {\n\t\t\tif seen[rule.oldPath] {\n\t\t\t\tvalidationErr = fmt.Errorf(\"duplicate oldPath: %s\", mapping.OldPath)\n\t\t\t}\n\t\t\tseen[rule.oldPath] = true\n\t\t}\n\t\tif err != nil {\n\t\t\titem.inputError = true\n\t\t\tp.fail(item, err.Error())\n\t\t\tcontinue\n\t\t}\n\t\tp.routes[rule.oldPath] = rule\n\t}\n\tif validationErr != nil {\n\t\treturn p, validationErr","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/asset_relink_batch.go#L59-L95","documentation":"When performing an actual relink (lookup mode is false), every mapping must carry a NewPath: a relink defines where the old asset should move. A mapping whose NewPath is empty is recorded as an inputError on the item and reported with this message; in lookup mode (FindAssetReferences*) empty NewPath is allowed.","triggerScenarios":"Calling RelinkAssetWithContext or RelinkAssets (dryRun true or false) with any mapping whose NewPath field is an empty string; lookup calls (FindAssetReferencesBatch) are exempt.","commonSituations":"Client built request for the find/references endpoint but sent it to the relink endpoint; JSON payload omitted newPath for some entries; batch UI left a row half-filled.","solutions":["Fill in NewPath for every mapping before calling the relink functions","Use the lookup APIs (FindAssetReferences*/lookup mode) when you only want references, not a move","Validate the payload client-side so rows without newPath are rejected before submission"],"exampleFix":"// before\nmappings := []apicontract.AssetRelinkMapping{{OldPath: \"assets/a.png\"}}\nmodel.RelinkAssets(ctx, mappings, false)\n// after\nmappings := []apicontract.AssetRelinkMapping{{OldPath: \"assets/a.png\", NewPath: \"assets/b.png\"}}\nfor _, m := range mappings {\n\tif m.NewPath == \"\" { return errors.New(\"newPath required for \" + m.OldPath) }\n}\nmodel.RelinkAssets(ctx, mappings, false)","handlingStrategy":"validation","validationCode":"for _, m := range mappings {\n\tif !lookup && m.NewPath == \"\" { return fmt.Errorf(\"newPath required for %q\", m.OldPath) }\n}","typeGuard":"if !lookup && mapping.NewPath == \"\" { return false }","tryCatchPattern":null,"preventionTips":["Distinguish find (lookup) vs relink endpoints and their required fields","Validate each row in batch UI before submission","Use dry-run first to catch per-item input errors safely"],"tags":["go","validation","missing-field"],"backgroundTag":"missing-required-argument","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}