{"record":{"id":"e7148c266a3e2935","repo":"wtfutil/wtf","slug":"invalid-base-url-w","errorCode":null,"errorMessage":"invalid base URL: %w","messagePattern":"invalid base URL: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/devto/client.go","lineNumber":44,"sourceCode":"}\n\n// NewClient creates a Client. Pass nil for httpClient to use http.DefaultClient.\n// baseURL overrides the API endpoint (useful for testing); pass \"\" for the default.\nfunc NewClient(httpClient *http.Client, baseURL string) *Client {\n\tif httpClient == nil {\n\t\thttpClient = http.DefaultClient\n\t}\n\tif baseURL == \"\" {\n\t\tbaseURL = defaultBaseURL\n\t}\n\treturn &Client{httpClient: httpClient, baseURL: baseURL}\n}\n\n// FetchArticles retrieves articles matching the given filters.\nfunc (c *Client) FetchArticles(ctx context.Context, tag, username, state string, perPage int) ([]Article, error) {\n\tu, err := url.Parse(c.baseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid base URL: %w\", err)\n\t}\n\n\tq := u.Query()\n\tif tag != \"\" {\n\t\tq.Set(\"tag\", tag)\n\t}\n\tif username != \"\" {\n\t\tq.Set(\"username\", username)\n\t}\n\tif state != \"\" {\n\t\tq.Set(\"state\", state)\n\t}\n\tif perPage > 0 {\n\t\tq.Set(\"per_page\", fmt.Sprintf(\"%d\", perPage))\n\t}\n\tu.RawQuery = q.Encode()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/wtfutil/wtf/blob/bb838c1ccb0f0f3223690df44afdec663d622881/modules/devto/client.go#L26-L62","documentation":"devto Client.FetchArticles starts by parsing c.baseURL with url.Parse; if that fails the error is wrapped as 'invalid base URL'. This only happens when the configured base URL is not a valid URL string, which normally indicates bad construction of the Client rather than a network problem.","triggerScenarios":"url.Parse(c.baseURL) errors — e.g. baseURL contains control characters or an unparsable scheme — when NewClient was given a malformed override URL instead of the default DEV.to endpoint.","commonSituations":"Passing an empty-with-garbage or typo'd base URL override in tests or custom configuration; concatenating config values into the URL producing invalid characters.","solutions":["Use the default DEV.to base URL (https://dev.to/api) unless overriding intentionally","Validate the custom base URL with url.Parse before constructing the Client","Print/log c.baseURL to spot stray characters or empty scheme"],"exampleFix":"// before\nclient := NewClient(\"http://dev.to/api/{bad url}\")\n// after\nu, err := url.Parse(\"https://dev.to/api\")\nif err != nil {\n    return err\n}\nclient := NewClient(u.String())","handlingStrategy":"validation","validationCode":"func validateBaseURL(raw string) error {\n    if raw == \"\" {\n        return errors.New(\"base URL is empty\")\n    }\n    if _, err := url.Parse(raw); err != nil {\n        return fmt.Errorf(\"invalid base URL %q: %w\", raw, err)\n    }\n    return nil\n}\nif err := validateBaseURL(baseURL); err != nil { return err }","typeGuard":null,"tryCatchPattern":"articles, err := client.FetchArticles(ctx, tag, user, state, perPage)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"invalid base URL\") {\n        return fmt.Errorf(\"misconfigured devto client: %w\", err)\n    }\n    return err\n}","preventionTips":["Default to https://dev.to/api; only override in tests","Validate any override URL with url.Parse before NewClient","Never build URLs by raw string concatenation of config values"],"tags":["devto","url","configuration","go"],"backgroundTag":"invalid-base-url","analyzedSha":"bb838c1ccb0f0f3223690df44afdec663d622881","analyzedAt":"2026-09-03T17:02:45.030Z","contentChangedAt":"2026-09-03T17:02:45.030Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}