{"record":{"id":"ae29cfcc44bb21fb","repo":"GopeedLab/gopeed","slug":"invalid-resource-files","errorCode":null,"errorMessage":"invalid resource files","messagePattern":"invalid resource files","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/base/model.go","lineNumber":101,"sourceCode":"\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}\n\t}\n\tr.Size = size\n}","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/base/model.go#L83-L119","documentation":"base.Resource.Validate() (pkg/base/model.go:100) requires a non-empty Files slice. The comment on the struct says a resource with a Name is treated as a folder whose entries are the Files list; a resource with no files has nothing to download and is rejected. Each file must in turn carry a non-empty name (a third check after this one).","triggerScenarios":"Constructing a single-file Resource and leaving Files nil (single files are usually modeled with FileInfo fields elsewhere); a resolve step that parsed a directory listing but matched zero entries; JSON payloads with an empty or missing files array.","commonSituations":"Custom fetchers for folder downloads whose listing parser broke (selector change, API response shape change) and returned zero entries; hand-built test fixtures; refactors that stopped populating Files.","solutions":["Populate Files with at least one FileInfo before Validate","For folder fetchers, fail loudly when the listing parse yields zero entries instead of validating an empty resource","When decoding external JSON, require a non-empty files array at the boundary"],"exampleFix":"// before\nres := &base.Resource{Name: \"docs\"} // no Files\nerr := res.Validate() // \"invalid resource files\"\n\n// after\nres := &base.Resource{\n    Name:  \"docs\",\n    Files: []*base.FileInfo{{Name: \"index.html\", Size: 2048}},\n}\nerr := res.Validate()","handlingStrategy":"validation","validationCode":"func hasFiles(res *base.Resource) bool {\n    return res != nil && len(res.Files) > 0\n}\n\n// Use before Validate() in folder resolve paths:\n// if !hasFiles(res) { return fmt.Errorf(\"listing produced no files for %s\", url) }","typeGuard":null,"tryCatchPattern":"if err := res.Validate(); err != nil {\n    if strings.Contains(err.Error(), \"invalid resource files\") {\n        // empty listing usually means the parser broke, not an empty folder\n        return fmt.Errorf(\"no files resolved for %q: check the listing parser\", res.Name)\n    }\n    return err\n}","preventionTips":["Treat a zero-entry listing parse as a parser failure in folder fetchers and fail loudly there","Always populate Files when constructing folder resources; add at least the root entry","Require a non-empty files array when decoding resource JSON from external sources"],"tags":["validation","model","resource","file-list"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}