{"record":{"id":"01e16a237a4744f2","repo":"ThreeMammals/Ocelot","slug":"the-timespan-value-doesn-t-include-any-digits-so-it-cannot","errorCode":null,"errorMessage":"The '{timespan}' value doesn't include any digits, so it cannot be considered a number!","messagePattern":"The '(.+?)' value doesn't include any digits, so it cannot be considered a number!","errorType":"validation","errorClass":"FormatException","httpStatus":null,"severity":"error","filePath":"src/Configuration/RateLimitRule.cs","lineNumber":67,"sourceCode":"\r\n    /// <summary>\r\n    /// Parses a timespan string, such as \"1ms\", \"1s\", \"1m\", \"1h\", \"1d\".\r\n    /// </summary>\r\n    /// <remarks>Converts a string to milliseconds when the unit is missing or undefined, automatically applying the 'ms' unit.</remarks>\r\n    /// <param name=\"timespan\">The string value with units: '1ms', '1s', '1m', '1h', '1d'.</param>\r\n    /// <returns>A <see cref=\"TimeSpan\"/> value.</returns>\r\n    /// <exception cref=\"FormatException\">If the value is not a number, or the unit of value cannot be determined.</exception>\r\n    public static TimeSpan ParseTimespan(string timespan)\r\n    {\r\n        if (string.IsNullOrWhiteSpace(timespan))\r\n        {\r\n            return TimeSpan.Zero;\r\n        }\r\n\r\n        if (!timespan.Any(char.IsDigit)) \r\n        {\r\n            // TODO: Make sense to have validation in src/Ocelot/Configuration/Validator/RouteFluentValidator\r\n            throw new FormatException($\"The '{timespan}' value doesn't include any digits, so it cannot be considered a number!\");\r\n        }\r\n\r\n        string val = timespan.Trim();\r\n        int pos = val.Length;\r\n        while (--pos >= 0 && !char.IsDigit(val[pos]) && val[pos] != DecimalSeparator)\r\n        {\r\n        }\r\n\r\n        string floating = val[..++pos], unit = val[pos..];\r\n        double value = Math.Abs(double.Parse(floating)); // negative values should be disallowed as they could cause everything to malfunction; TODO: Make sense to have validation in src/Ocelot/Configuration/Validator/RouteFluentValidator\r\n        return unit.ToLower() switch\r\n        {\r\n            \"d\" => TimeSpan.FromDays(value),\r\n            \"h\" => TimeSpan.FromHours(value),\r\n            \"m\" => TimeSpan.FromMinutes(value),\r\n            \"s\" => TimeSpan.FromSeconds(value),\r\n            \"ms\" => TimeSpan.FromMilliseconds(value),\r\n            \"\" => TimeSpan.FromMilliseconds(value), // an unknown unit defaults to milliseconds as the ms unit\r","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/ThreeMammals/Ocelot/blob/d1f22d930430410bfd0233f5e9e4f92980cf7df0/src/Configuration/RateLimitRule.cs#L49-L85","documentation":"RateLimitRule.ParseTimespan parses rate-limit period strings like '1s', '5m'. If the input string contains no digit characters at all, it throws FormatException because no numeric value can be extracted. The call chain is PeriodSpan/WaitSpan -> ParseTimespan, fed from the RateLimitOptions configuration.","triggerScenarios":"A route's RateLimitRule Period or a client's Wait value is set to a string like 's', 'abc', '', or '.m' — no digits anywhere in the value.","commonSituations":"Typo in ocelot.json rate limiting config ('Period': 'second'); empty string from environment-variable substitution; deserialized default leaving a blank period.","solutions":["Fix the configured value to include a number plus optional unit, e.g. '1s', '5m', '100ms'.","Validate ocelot.json route rate-limit options before startup (RouteFluentValidator per the TODO).","If values come from config providers/env vars, check the substituted value is non-empty and numeric.","Wrap client creation in try/catch for FormatException to surface which route is misconfigured."],"exampleFix":"// before\n\"RateLimitRule\": { \"Period\": \"s\", \"Limit\": 10 }\n// after\n\"RateLimitRule\": { \"Period\": \"1s\", \"Limit\": 10 }","handlingStrategy":"validation","validationCode":"bool IsValidRateLimitPeriod(string p) =>\n    !string.IsNullOrEmpty(p) && p.Any(char.IsDigit);\n// run before loading ocelot.json rate-limit rules","typeGuard":null,"tryCatchPattern":"try { var period = RateLimitRule.PeriodSpan(rule.Period); }\ncatch (FormatException ex) { Error($\"Bad rate-limit period '{rule.Period}': {ex.Message}\"); throw; }","preventionTips":["Always configure periods as number + unit, e.g. '1s', '5m', '100ms'","Never leave Period as an empty string or placeholder","Validate ocelot.json rate-limit fields at startup","Lint configuration with the Ocelot schema in CI"],"tags":["rate-limiting","format-exception","configuration","timespan"],"backgroundTag":"invalid-duration-format","analyzedSha":"d1f22d930430410bfd0233f5e9e4f92980cf7df0","analyzedAt":"2026-09-12T13:50:06.067Z","contentChangedAt":"2026-09-12T13:50:06.067Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}