{"record":{"id":"035cd1db48d1f6e0","repo":"Tencent/WeKnora","slug":"fetch-feed-s-w","errorCode":null,"errorMessage":"fetch feed %s: %w","messagePattern":"fetch feed (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/datasource/connector/rss/connector.go","lineNumber":43,"sourceCode":"func NewConnector() *Connector { return &Connector{} }\n\n// Type returns the connector type identifier.\nfunc (c *Connector) Type() string { return types.ConnectorTypeRSS }\n\n// Validate verifies that every configured feed URL is reachable and parses as\n// a valid feed.\nfunc (c *Connector) Validate(ctx context.Context, config *types.DataSourceConfig) error {\n\tcfg, err := parseConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcli := newClient(cfg.parseHeaders())\n\tparser := gofeed.NewParser()\n\n\tfor _, feedURL := range cfg.feedURLList() {\n\t\tdata, err := cli.fetchFeed(ctx, feedURL)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"fetch feed %s: %w\", feedURL, err)\n\t\t}\n\t\tif _, err := parser.Parse(bytes.NewReader(data)); err != nil {\n\t\t\treturn fmt.Errorf(\"parse feed %s: %w\", feedURL, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// ResolveResourceAncestors has nothing to do: feeds are a flat list with no\n// nesting, so a selection has no ancestors to reveal.\nfunc (c *Connector) ResolveResourceAncestors(\n\tctx context.Context, config *types.DataSourceConfig, resourceIDs []string,\n) ([]string, error) {\n\treturn []string{}, nil\n}\n\n// ListResources returns one resource per configured feed URL. The feed is\n// fetched so the resource can carry its real title; a feed that fails to fetch","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/datasource/connector/rss/connector.go#L25-L61","documentation":"Returned by Connector.Validate when fetching one of the configured feed URLs fails. Validate fetches every feed in the config and wraps any underlying client error (SSRF rejection, invalid URL, transport failure, HTTP error status, body read failure — errors 710-713) with the offending feed URL. It is surfaced to the user while configuring the datasource, so the whole validation fails on the first bad feed.","triggerScenarios":"During datasource configuration, any configured feed URL is unreachable: DNS failure, connection refused, TLS error, HTTP 4xx/5xx, URL rejected by SSRF validation (private/loopback address), malformed URL, or body read failure.","commonSituations":"Admin pastes a wrong or moved feed URL; feed host is down at validation time; internal/localhost feed URLs deliberately blocked by the SSRF guard; feeds requiring VPN access the server doesn't have; auth headers not yet configured for a token-gated feed.","solutions":["Read the wrapped cause after the feed URL in the message: fix the specific root cause (URL typo, HTTP status, DNS) and re-validate.","Verify the feed URL in a browser or with curl from the server running the connector — auth headers and network path differ from your workstation.","If the URL is internal (private IP/localhost), the SSRF guard will block it; publish the feed on an address the SSRF policy allows.","Split the feed list: validate each URL individually so one dead feed doesn't block a config with many good feeds."],"exampleFix":"// before\nfeeds: [\"https://blog.example/feed.xml\", \"htp://typo.example/rss\"]\n// after\nfeeds: [\"https://blog.example/feed.xml\", \"https://good.example/rss.xml\"]","handlingStrategy":"validation","validationCode":"func preflightFeeds(ctx context.Context, urls []string) map[string]error {\n    out := make(map[string]error, len(urls))\n    for _, u := range urls {\n        if err := validFeedURL(u); err != nil {\n            out[u] = err\n            continue\n        }\n        req, _ := http.NewRequestWithContext(ctx, http.MethodHead, u, nil)\n        resp, err := http.DefaultClient.Do(req)\n        if err != nil {\n            out[u] = err\n        } else if resp.StatusCode >= 400 {\n            out[u] = fmt.Errorf(\"HTTP %d\", resp.StatusCode)\n        }\n        resp.Body.Close()\n    }\n    return out\n}","typeGuard":null,"tryCatchPattern":"err := connector.Validate(ctx, cfg)\nif err != nil {\n    var uerr *url.Error\n    if errors.As(err, &uerr) {\n        return fmt.Errorf(\"feed configuration rejected, check URL/network for %s: %w\", uerr.URL, uerr.Err)\n    }\n    return err\n}","preventionTips":["Validate each feed URL with a lightweight HEAD/GET probe before saving the datasource config.","Ensure feed URLs are public (SSRF guard blocks private/loopback addresses).","Configure auth headers for token-gated feeds before validation.","Parse the wrapped error to distinguish fetch vs parse failures and act accordingly."],"tags":["network","http","validation","rss","config"],"backgroundTag":"http-request-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}