{"record":{"id":"5536974e5c2974da","repo":"GopeedLab/gopeed","slug":"invalid-blob-options","errorCode":null,"errorMessage":"invalid blob options","messagePattern":"invalid blob options","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/blob/registry.go","lineNumber":31,"sourceCode":"\t\"net/url\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst urlPathPrefix = \"/__blob/\"\n\nconst rangeSourceFailureLimit = 2\n\n// unclaimedSourceTTL bounds how long a session-backed source may keep its\n// engine alive without ever being claimed by a download task.\nvar unclaimedSourceTTL = 10 * time.Minute\n\nvar (\n\tErrInvalidURL      = errors.New(\"invalid blob url\")\n\tErrInvalidOptions  = errors.New(\"invalid blob options\")\n\tErrSourceNotFound  = errors.New(\"blob source not found\")\n\tErrSourceRevoked   = errors.New(\"blob source revoked\")\n\tErrSourceClosed    = errors.New(\"blob source closed\")\n\tErrRangeNotAllowed = errors.New(\"blob range not allowed\")\n)\n\ntype SessionRef interface {\n\tRetain()\n\tRelease()\n}\n\ntype OpenRequest struct {\n\tOffset int64\n\tEnd    int64\n}\n\ntype OpenFunc func(ctx context.Context, req OpenRequest) (io.ReadCloser, error)\n","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/blob/registry.go#L13-L49","documentation":"Returned by Registry.CreateOpener when the caller-supplied opener is invalid: either open == nil (registry.go:136-138), or the options ask for range support without a usable size — opts.Range == true with opts.Size <= 0 fails with the wrapped message 'invalid blob options: range requires positive size' (registry.go:145-147). The registry needs a real OpenFunc to serve bytes and a positive size to advertise and validate Range requests.","triggerScenarios":"Calling CreateOpener(nil, opts); passing &CreateOptions{Range: true} with Size unset (zero) or negative; computing size lazily and forgetting to set it when enabling Range; passing opts with Size set but Range enabled for a stream whose length is genuinely unknown.","commonSituations":"Copying a CreateBlob-style call but hand-rolling CreateOpener and omitting Size; enabling Range by default for all sources regardless of whether the length is known; refactoring that accidentally drops the open callback parameter.","solutions":["Always pass a non-nil OpenFunc that returns an io.ReadCloser","Set CreateOptions.Size to the exact byte length when Range: true (size >= 1)","If the length is unknown, set Range: false — the source will then be served as a whole-body stream and parseRange is skipped","Check the wrapped message with errors.Is(err, ErrInvalidOptions) to distinguish the size problem from the nil-opener problem"],"exampleFix":"// before\nurl, err := registry.CreateOpener(openFn, &blob.CreateOptions{ContentType: \"video/mp4\", Range: true}) // Size missing\n\n// after\nurl, err := registry.CreateOpener(openFn, &blob.CreateOptions{ContentType: \"video/mp4\", Range: true, Size: totalBytes})","handlingStrategy":"validation","validationCode":"if open == nil { return errors.New(\"open func required\") }\nif opts.Range && opts.Size <= 0 { opts.Range = false /* or fail here */ }\nurl, err := registry.CreateOpener(open, opts)","typeGuard":null,"tryCatchPattern":"url, err := registry.CreateOpener(open, opts)\nif err != nil {\n    if errors.Is(err, blob.ErrInvalidOptions) {\n        // nil opener or Range without positive Size: fix CreateOptions and retry once\n    }\n}","preventionTips":["Always pair Range:true with the exact known byte Size","Write a small wrapper (createRangeSource(open, size, contentType)) so options are always consistent","Unit-test source creation with edge sizes (0, 1, huge)"],"tags":["blob","options","validation","range"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}