{"record":{"id":"86f0661cff77bd83","repo":"GopeedLab/gopeed","slug":"invalid-resource-name","errorCode":null,"errorMessage":"invalid resource name","messagePattern":"invalid resource name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/base/model.go","lineNumber":98,"sourceCode":"\n\treturn http.ProxyURL(util.BuildProxyUrl(p.Scheme, p.Host, p.Usr, p.Pwd))\n}\n\n// Resource download resource\ntype Resource struct {\n\t// if name is not empty, the resource is a folder and the name is the folder name\n\tName string `json:\"name\"`\n\tSize int64  `json:\"size\"`\n\t// is support range download\n\tRange bool `json:\"range\"`\n\t// file list\n\tFiles []*FileInfo `json:\"files\"`\n\tHash  string      `json:\"hash\"`\n}\n\nfunc (r *Resource) Validate() error {\n\tif r.Name == \"\" {\n\t\treturn fmt.Errorf(\"invalid resource name\")\n\t}\n\tif len(r.Files) == 0 {\n\t\treturn fmt.Errorf(\"invalid resource files\")\n\t}\n\tfor _, file := range r.Files {\n\t\tif file.Name == \"\" {\n\t\t\treturn fmt.Errorf(\"invalid resource file name\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *Resource) CalcSize(selectFiles []int) {\n\tvar size int64\n\tfor i, file := range r.Files {\n\t\tif len(selectFiles) == 0 || slices.Contains(selectFiles, i) {\n\t\t\tsize += file.Size\n\t\t}","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/base/model.go#L80-L116","documentation":"base.Resource.Validate() (pkg/base/model.go:96) requires a non-empty Name on the resolved resource. A Resource describes what a fetcher resolved (name, size, range support, file list); the name doubles as the target file/folder name, so an empty one cannot be used to write output.","triggerScenarios":"Building a Resource programmatically and leaving Name zero; a protocol extension returning a resolved resource without deriving a name from the URL or Content-Disposition; deserializing JSON that lacked the name field.","commonSituations":"Custom fetcher implementations that forget to populate Name; servers responding without a usable filename; refactors that rename the field and silently break deserialization.","solutions":["Populate Name before Validate — typically from Content-Disposition or the URL path base","In custom fetchers, default the name (e.g. url basename or host+timestamp) instead of leaving it empty","When decoding, require the name key so the failure happens at the boundary"],"exampleFix":"// before\nres := &base.Resource{Size: 1024, Files: files} // Name zero value\nerr := res.Validate() // \"invalid resource name\"\n\n// after\nres := &base.Resource{Name: \"file.zip\", Size: 1024, Files: files}\nerr := res.Validate()","handlingStrategy":"validation","validationCode":"func resourceName(u string, cd string) string {\n    if _, params, err := mime.ParseMediaType(cd); err == nil {\n        if n := params[\"filename\"]; n != \"\" {\n            return n\n        }\n    }\n    if base := path.Base(u); base != \"/\" && base != \".\" {\n        return base\n    }\n    return \"download\"\n}","typeGuard":null,"tryCatchPattern":"if err := res.Validate(); err != nil {\n    if strings.Contains(err.Error(), \"invalid resource name\") {\n        res.Name = fallbackName(req.URL) // derive from URL/headers, then re-validate\n        return res.Validate()\n    }\n    return err\n}","preventionTips":["In custom fetchers, always derive Name from Content-Disposition or the URL basename","Never return a resolved Resource with zero-valued Name","When decoding external JSON, require the name field at the boundary"],"tags":["validation","model","resource","resolve"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}