{"record":{"id":"ffb3e2009627cff4","repo":"AdguardTeam/AdGuardHome","slug":"s-parsing-interval-s","errorCode":null,"errorMessage":"%s: parsing interval: %s","messagePattern":"(.+?): parsing interval: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stats/http.go","lineNumber":108,"sourceCode":"\t\tconst msg = \"Couldn't get statistics data\"\n\t\taghhttp.ErrorAndLog(ctx, l, r, w, http.StatusInternalServerError, msg)\n\n\t\treturn\n\t}\n\n\taghhttp.WriteJSONResponseOK(ctx, l, w, r, resp)\n}\n\n// parseRecent parses and validates the value of the recent URL parameter.  If\n// the parameter is empty, the original limit is returned.\nfunc parseRecent(recent string, limit time.Duration) (parsedLimit time.Duration, err error) {\n\tif recent == \"\" {\n\t\treturn limit, nil\n\t}\n\n\trecentMs, err := strconv.ParseInt(recent, 10, 64)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"%s: parsing interval: %s\", queryKeyRecent, err)\n\t}\n\n\terr = validate.InRange(queryKeyRecent, recentMs, millisecondsInHour, limit.Milliseconds())\n\tif err != nil {\n\t\t// Don't wrap the error since it's already informative enough as is.\n\t\treturn 0, err\n\t}\n\n\tif recentMs%millisecondsInHour != 0 {\n\t\treturn 0, fmt.Errorf(\"%s: must be a multiple of 1 hour\", queryKeyRecent)\n\t}\n\n\treturn time.Duration(recentMs) * time.Millisecond, nil\n}\n\n// configResp is the response to the GET /control/stats_info.\ntype configResp struct {\n\tIntervalDays uint32 `json:\"interval\"`","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/stats/http.go#L90-L126","documentation":"Returned by the stats HTTP handler when the 'recent' query parameter of GET /control/stats cannot be parsed as a base-10 64-bit integer. The raw strconv error is wrapped with the query key name for context.","triggerScenarios":"Calling /control/stats (handleStats -> parseRecent) with recent=<non-numeric>, e.g. recent=1h, recent=true, or an empty-ish/garbage value (empty string is allowed and returns the limit).","commonSituations":"Passing a human-readable duration ('2h') instead of milliseconds; truncated URLs; client code building the query string incorrectly.","solutions":["Send recent as an integer number of milliseconds, e.g. recent=7200000","Omit the parameter entirely if you want the default limit","Check the client's URL-encoding/serialization of durations"],"exampleFix":"// before\nGET /control/stats?recent=2h\n// after\nGET /control.stats?recent=7200000","handlingStrategy":"validation","validationCode":"ms, err := strconv.ParseInt(recent, 10, 64)\nif err != nil { return fmt.Errorf(\"recent must be integer milliseconds\") }","typeGuard":null,"tryCatchPattern":"resp, err := http.Get(url + \"?recent=\" + strconv.FormatInt(ms, 10))\nif err != nil || resp.StatusCode != 200 { /* inspect body for parsing error */ }","preventionTips":["Always serialize recent as integer milliseconds","Build query strings with url.Values","Test the endpoint with a linter/curl before shipping clients"],"tags":["http","query-param","parsing","stats"],"backgroundTag":"invalid-query-parameter","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}