{"record":{"id":"50e07f0eb1ef79ad","repo":"Billionmail/BillionMail","slug":"create-download-request-w","errorCode":null,"errorMessage":"create download request: %w","messagePattern":"create download request: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/lipsync.go","lineNumber":169,"sourceCode":"\t\treturn nil, fmt.Errorf(\"lipsync status API error %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result LipSyncResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode lipsync status: %w\", err)\n\t}\n\treturn &result, nil\n}\n\n// DownloadLipSyncVideo downloads the completed lip sync video to the output directory.\nfunc DownloadLipSyncVideo(ctx context.Context, cfg LipSyncConfig, videoURL, filename string) (string, error) {\n\tif err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {\n\t\treturn \"\", fmt.Errorf(\"create output dir: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", videoURL, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create download request: %w\", err)\n\t}\n\n\tresp, err := cfg.doHTTP(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"download lipsync video: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"download error %d\", resp.StatusCode)\n\t}\n\n\toutPath := filepath.Join(cfg.OutputDir, filename)\n\tf, err := os.Create(outPath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create output file: %w\", err)\n\t}\n\tdefer f.Close()","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/lipsync.go#L151-L187","documentation":"DownloadLipSyncVideo builds the GET request to the completed video's CDN URL with http.NewRequestWithContext. This error wraps request-construction failure, which in practice happens only when videoURL fails Go's URL parsing — empty string, containing spaces/control characters, or otherwise malformed. It is raised locally before any network call.","triggerScenarios":"Calling DownloadLipSyncVideo with an empty videoURL (e.g. caller ignored the job Status and passed the zero-value VideoURL field from LipSyncResponse), a URL with raw spaces or unescaped characters, or a video_url field from the API that is a relative path or malformed.","commonSituations":"Treating status==\"completed\" as sufficient without checking VideoURL != \"\"; storing the URL in a database and losing scheme/host; Sync Labs returning an error message in video_url on partially failed jobs; copy-pasted URLs with trailing whitespace.","solutions":["Guard the caller: only download when status == \"completed\" AND result.VideoURL is a non-empty http(s) URL.","Sanitize the URL (strings.TrimSpace, url.Parse validation) before calling DownloadLipSyncVideo.","If the URL is persisted, re-fetch the job status to get a fresh signed CDN URL — signed URLs can expire and be replaced.","Log the offending videoURL in the caller so the malformed value is identifiable."],"exampleFix":"// before: downloads regardless of validation\npath, err := video_gen.DownloadLipSyncVideo(ctx, cfg, resp.VideoURL, name)\n// after: validate before downloading\nu, err := url.Parse(strings.TrimSpace(resp.VideoURL))\nif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n    return fmt.Errorf(\"invalid video URL %q\", resp.VideoURL)\n}\npath, err := video_gen.DownloadLipSyncVideo(ctx, cfg, u.String(), name)","handlingStrategy":"validation","validationCode":"func validVideoURL(raw string) bool {\n    u, err := url.Parse(strings.TrimSpace(raw))\n    return err == nil && (u.Scheme == \"http\" || u.Scheme == \"https\") && u.Host != \"\"\n}\n// call site:\nif resp.Status != \"completed\" || !validVideoURL(resp.VideoURL) {\n    return fmt.Errorf(\"lipsync job not downloadable (status=%q url=%q)\", resp.Status, resp.VideoURL)\n}","typeGuard":"func downloadableLipSyncResult(r *video_gen.LipSyncResponse) bool {\n    return r != nil && r.Status == \"completed\" && validVideoURL(r.VideoURL)\n}","tryCatchPattern":"path, err := video_gen.DownloadLipSyncVideo(ctx, cfg, videoURL, filename)\nif err != nil {\n    if strings.Contains(err.Error(), \"create download request\") {\n        return fmt.Errorf(\"malformed video URL %q: %w\", videoURL, err)\n    }\n    return err\n}","preventionTips":["Check Status == \"completed\" and VideoURL non-empty before downloading.","Trim whitespace from URLs coming from the API or database.","Re-fetch status if a persisted signed CDN URL may have expired.","Use url.Parse as a cheap pre-check in any code path that consumes VideoURL."],"tags":["url","validation","http-request","lipsync"],"backgroundTag":"invalid-url","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}