{"record":{"id":"03b63ddeed49f021","repo":"fish2018/pansou","slug":"s-api-w","errorCode":null,"errorMessage":"[%s] 创建 API 请求失败: %w","messagePattern":"\\[(.+?)\\] 创建 API 请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/dyyj/dyyj.go","lineNumber":414,"sourceCode":"\tID         string `json:\"id\"`\n\tAttributes struct {\n\t\tContentHTML string `json:\"contentHtml\"`\n\t\tCreatedAt   string `json:\"createdAt\"`\n\t} `json:\"attributes\"`\n}\n\nfunc (p *DyyjPlugin) executeSearchAPI(client *http.Client, keyword string) ([]model.SearchResult, error) {\n\tparams := url.Values{}\n\tparams.Set(\"filter[q]\", keyword)\n\tparams.Set(\"include\", \"mostRelevantPost\")\n\tparams.Set(\"page[limit]\", fmt.Sprintf(\"%d\", MaxResults))\n\tapiURL := BaseURL + \"/api/discussions?\" + params.Encode()\n\n\tctx, cancel := context.WithTimeout(context.Background(), RequestTimeout)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建 API 请求失败: %w\", p.Name(), err)\n\t}\n\treq.Header.Set(\"User-Agent\", UserAgent)\n\treq.Header.Set(\"Accept\", \"application/vnd.api+json, application/json\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Referer\", BaseURL+\"/\")\n\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] API 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\tvar payload dyyjAPIResponse\n\tif err := encodingjson.NewDecoder(resp.Body).Decode(&payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析 API 响应失败: %w\", p.Name(), err)\n\t}\n\n\tposts := make(map[string]dyyjIncluded, len(payload.Included))","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/dyyj/dyyj.go#L396-L432","documentation":"dyyj.executeSearchAPI failed to construct the Flarum API GET request (http.NewRequestWithContext) for BaseURL + /api/discussions. Like the HTML path, this means the composed apiURL failed URL parsing — usually a bad parameter value in the encoded query.","triggerScenarios":"http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) returned err: apiURL unparsable because a query parameter contains control characters or invalid percent-encoding before params.Encode().","commonSituations":"Keyword containing special characters was inserted into params without proper encoding; BaseURL constant mis-edited; env override of the API host malformed.","solutions":["Print apiURL and run url.Parse on it to reproduce the parse error","Build query values with url.Values and Encode() (the code already calls params.Encode() — check what was put into params)","Validate keyword: strip control characters / newlines before adding to params","Validate BaseURL at startup"],"exampleFix":"// before\nparams.Set(\"filter[q]\", keyword)\n// after\nkeyword = strings.TrimSpace(keyword)\nif keyword == \"\" || strings.ContainsAny(keyword, \"\\x00\\n\\r\") {\n\treturn nil, fmt.Errorf(\"[%s] 无效搜索关键词\", p.Name())\n}\nparams.Set(\"filter[q]\", keyword)","handlingStrategy":"validation","validationCode":"if _, err := url.Parse(apiURL); err != nil {\n\treturn fmt.Errorf(\"invalid api url %q: %w\", apiURL, err)\n}","typeGuard":"func validAPIURL(raw string) bool {\n\tu, err := url.Parse(raw)\n\treturn err == nil && (u.Scheme == \"http\" || u.Scheme == \"https\") && u.Host != \"\"\n}","tryCatchPattern":"req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)\nif err != nil {\n\treturn nil, fmt.Errorf(\"[%s] 创建 API 请求失败: %w\", p.Name(), err)\n}","preventionTips":["Compose query strings exclusively via url.Values.Encode()","Sanitize keywords (trim, strip control chars) before adding to params","Validate BaseURL at startup, fail fast on malformed config","Unit-test URL construction with adversarial keywords"],"tags":["go","http","url-parsing","api"],"backgroundTag":"invalid-url","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}