Fix string escaping, null safety, and numeric literals in optional parameter default values#804
Conversation
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Comment |
Co-authored-by: christianhelle <710400+christianhelle@users.noreply.github.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #804 +/- ##
==========================================
- Coverage 95.83% 95.23% -0.60%
==========================================
Files 23 23
Lines 1871 1909 +38
Branches 306 311 +5
==========================================
+ Hits 1793 1818 +25
- Misses 39 48 +9
- Partials 39 43 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@copilot the changes here introduced 2 new issues detected by SonarCloud Details found in https://sonarcloud.io/project/issues?id=christianhelle_refitter&pullRequest=804&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true Fix them |
…e warning Co-authored-by: christianhelle <710400+christianhelle@users.noreply.github.com>
Fixed both SonarCloud issues:
All tests pass. Commit: e17d732 |
|
@copilot The new code lacks code coverage. Add tests to cover them. Codecov detected missing coverage, details available here: https://app.codecov.io/gh/christianhelle/refitter/pull/804?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=checks&utm_campaign=pr+comments&utm_term=Christian+Helle |
There was a problem hiding this comment.
Pull Request Overview
This PR adds edge case handling for optional parameters with default values, specifically addressing proper escaping of special characters in string defaults and adding type suffixes for numeric literals (float, decimal).
Key Changes:
- Implemented string escaping logic to handle quotes, backslashes, newlines, carriage returns, and tabs in default parameter values
- Added numeric value formatting with appropriate type suffixes ('f' for float, 'm' for decimal)
- Created comprehensive test suite to validate string escaping and numeric formatting behavior
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/Refitter.Tests/Examples/OptionalParametersWithDefaultValuesEdgeCasesTests.cs | New test file with 4 test methods covering string escaping edge cases, float/decimal type suffixes, and build validation |
| src/Refitter.Core/ParameterExtractor.cs | Enhanced parameter default value formatting with EscapeString() method for string literals and FormatNumericValue() method for numeric type suffixes |
| "decimal" or "Decimal" => $"{numericString}m", | ||
| _ => numericString | ||
| }; | ||
| } | ||
|
|
There was a problem hiding this comment.
The FormatNumericValue method doesn't handle the double type suffix. While doubles don't require a suffix in most cases, if the numeric value is an integer (e.g., 1, 10), it will be treated as int by default in C# unless explicitly cast. For double types with integer values, you should append .0 to ensure the literal is treated as a double. For example, double? value = 10 should be double? value = 10.0 or double? value = 10d.
| "decimal" or "Decimal" => $"{numericString}m", | |
| _ => numericString | |
| }; | |
| } | |
| "decimal" or "Decimal" => $"{numericString}m", | |
| "double" or "Double" => FormatDoubleLiteral(numericString), | |
| _ => numericString | |
| }; | |
| } | |
| // Ensures that double literals are formatted with .0 if integer, or as-is if already floating-point | |
| private static string FormatDoubleLiteral(string numericString) | |
| { | |
| // If the string already contains a decimal point or exponent, return as-is | |
| if (numericString.Contains('.') || numericString.Contains('e') || numericString.Contains('E')) | |
| return numericString; | |
| // Otherwise, append .0 to make it a double literal | |
| return numericString + ".0"; | |
| } |
| private static string EscapeString(string value) | ||
| { | ||
| var sb = new StringBuilder(value.Length * 2); | ||
| foreach (var c in value) | ||
| { | ||
| switch (c) | ||
| { | ||
| case '\\': | ||
| sb.Append("\\\\"); | ||
| break; | ||
| case '"': | ||
| sb.Append("\\\""); | ||
| break; | ||
| case '\n': | ||
| sb.Append("\\n"); | ||
| break; | ||
| case '\r': | ||
| sb.Append("\\r"); | ||
| break; | ||
| case '\t': | ||
| sb.Append("\\t"); | ||
| break; | ||
| default: | ||
| sb.Append(c); | ||
| break; | ||
| } | ||
| } | ||
| return sb.ToString(); | ||
| } |
There was a problem hiding this comment.
The EscapeString method should handle additional escape sequences for completeness. Consider adding cases for:
- Form feed (
\f→\\f) - Vertical tab (
\v→\\v) - Backspace (
\b→\\b)
While these are less common in API parameters, handling them ensures robustness when dealing with various string defaults.
| return type switch | ||
| { | ||
| "float" or "Single" => $"{numericString}f", | ||
| "decimal" or "Decimal" => $"{numericString}m", |
There was a problem hiding this comment.
The FormatNumericValue method doesn't handle long and ulong type suffixes. In C#, large integer values that exceed int.MaxValue (2,147,483,647) must have an L suffix to be treated as long literals. Without the suffix, values like long? value = 3000000000 will cause a compilation error. Add cases for:
"long"or"Int64"→ appendL"ulong"or"UInt64"→ appendUL
Additionally, consider appending U for uint types to be explicit, though this is optional for values within int range.
| "decimal" or "Decimal" => $"{numericString}m", | |
| "decimal" or "Decimal" => $"{numericString}m", | |
| "long" or "Int64" => $"{numericString}L", | |
| "ulong" or "UInt64" => $"{numericString}UL", | |
| "uint" or "UInt32" => $"{numericString}U", |
| private static string EscapeString(string value) | ||
| { | ||
| var sb = new StringBuilder(value.Length * 2); | ||
| foreach (var c in value) | ||
| { | ||
| switch (c) | ||
| { | ||
| case '\\': | ||
| sb.Append("\\\\"); | ||
| break; | ||
| case '"': | ||
| sb.Append("\\\""); | ||
| break; | ||
| case '\n': | ||
| sb.Append("\\n"); | ||
| break; | ||
| case '\r': | ||
| sb.Append("\\r"); | ||
| break; | ||
| case '\t': | ||
| sb.Append("\\t"); | ||
| break; | ||
| default: | ||
| sb.Append(c); | ||
| break; | ||
| } | ||
| } | ||
| return sb.ToString(); | ||
| } |
There was a problem hiding this comment.
The EscapeString method does not handle the null character (\0) which can appear in string defaults. If a string contains \0, it should be escaped as \\0 to be valid in C# string literals. Consider adding a case for '\0' that appends "\\0".
…al formatting Co-authored-by: christianhelle <710400+christianhelle@users.noreply.github.com>
Added comprehensive test coverage for the new code. Created 12 new tests across two test files: OptionalParametersWithDefaultValuesEdgeCasesTests (6 new tests):
DynamicQueryStringParametersEdgeCasesTests (6 new tests):
All 16 tests pass and generated code compiles. Commit: 7402111 |
|
@copilot apply changes based on the comments in this thread |
…al formatting Co-authored-by: christianhelle <710400+christianhelle@users.noreply.github.com>
|
Please retry analysis of this Pull-Request directly on SonarQube Cloud |
|
Applied all changes from the review thread:
All 21 edge case tests pass. Commit: c7ca8f8 Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Updated [refitter](https://github.com/christianhelle/refitter) from 1.7.0 to 1.7.1. <details> <summary>Release notes</summary> _Sourced from [refitter's releases](https://github.com/christianhelle/refitter/releases)._ ## 1.7.1 ### Implemented enhancements - Improved handling of optional parameters [\#448](christianhelle/refitter#448) by @christianhelle - Allow omitting certain operation headers \#840 [\#841](christianhelle/refitter#841) by @kmc059000 - Migrate unit tests from xUnit to TUnit [\#830](christianhelle/refitter#830) by @christianhelle - Refit v9.0.2 [\#829](christianhelle/refitter#829) - Add .NET 10 support [\#822](christianhelle/refitter#822) ([christianhelle](https://github.com/christianhelle)) - Fix missing XML doc for CancellationToken [\#819](christianhelle/refitter#819) by @christianhelle - Fix incorrect casing on multi-part form data parameters [\#806](christianhelle/refitter#806) by @christianhelle - Optional parameters with default values [\#803](christianhelle/refitter#803) by @christianhelle - Asana API specs strange naming [\#364](christianhelle/refitter#364) by @christianhelle ### Fixed bugs - Using cancellation tokens with xml doc comments plus TreatWarningsAsErrors and documentation file [\#817](christianhelle/refitter#817) - Multipart form data parameters wrong casing [\#805](christianhelle/refitter#805) - Use of non generic `JsonStringEnumConverter` prohibits usage of Json-SourceGenerationContext [\#778](christianhelle/refitter#778) - SourceGenerator 1.5 and newer causes build error with Visual Studio 2022 [\#627](christianhelle/refitter#627) - Fix string escaping, null safety, and numeric literals in optional parameter default values [\#804](christianhelle/refitter#804) - Fix typo in class name and Spectre.Console markup escaping issue [\#828](christianhelle/refitter#828) ### Merged pull requests - docs: add kmc059000 as a contributor for ideas, and code [\#842](christianhelle/refitter#842) ([allcontributors[bot]](https://github.com/apps/allcontributors)) - Bump actions/upload-artifact from 5 to 6 [\#838](christianhelle/refitter#838) ([dependabot[bot]](https://github.com/apps/dependabot)) - Update dependency TUnit to 1.5.37 [\#836](christianhelle/refitter#836) ([renovate[bot]](https://github.com/apps/renovate)) - Update dotnet monorepo [\#834](christianhelle/refitter#834) ([renovate[bot]](https://github.com/apps/renovate)) - Update dependency TUnit to 1.5.6 [\#833](christianhelle/refitter#833) ([renovate[bot]](https://github.com/apps/renovate)) - Update dependency TUnit to 1.5.0 [\#832](christianhelle/refitter#832) ([renovate[bot]](https://github.com/apps/renovate)) - chore\(deps\): update dependency spectre.console.cli to 0.53.1 [\#827](christianhelle/refitter#827) ([renovate[bot]](https://github.com/apps/renovate)) - chore\(deps\): update dependency polly to 8.6.5 [\#826](christianhelle/refitter#826) ([renovate[bot]](https://github.com/apps/renovate)) - Bump actions/checkout from 5 to 6 [\#825](christianhelle/refitter#825) ([dependabot[bot]](https://github.com/apps/dependabot)) - chore\(deps\): update nswag monorepo to 14.6.3 [\#824](christianhelle/refitter#824) ([renovate[bot]](https://github.com/apps/renovate)) - chore\(deps\): update dependency microsoft.build.utilities.core to v18 [\#821](christianhelle/refitter#821) ([renovate[bot]](https://github.com/apps/renovate)) - chore\(deps\): update dependency swashbuckle.aspnetcore to 10.0.1 [\#820](christianhelle/refitter#820) ([renovate[bot]](https://github.com/apps/renovate)) - docs: add karoberts as a contributor for bug [\#818](christianhelle/refitter#818) ([allcontributors[bot]](https://github.com/apps/allcontributors)) - chore\(deps\): update dependency swashbuckle.aspnetcore to v10 [\#816](christianhelle/refitter#816) ([renovate[bot]](https://github.com/apps/renovate)) - chore\(deps\): update dependency microsoft.extensions.http.resilience to v10 [\#815](christianhelle/refitter#815) ([renovate[bot]](https://github.com/apps/renovate)) - Update Spectre.Console.Cli to 0.53.0 [\#814](christianhelle/refitter#814) ([Copilot](https://github.com/apps/copilot-swe-agent)) - chore\(deps\): update dotnet monorepo to v10 \(major\) [\#813](christianhelle/refitter#813) ([renovate[bot]](https://github.com/apps/renovate)) - chore\(deps\): update dependency microsoft.net.test.sdk to 18.0.1 [\#808](christianhelle/refitter#808) ([renovate[bot]](https://github.com/apps/renovate)) - docs: add mhartmair-cubido as a contributor for bug [\#807](christianhelle/refitter#807) ([allcontributors[bot]](https://github.com/apps/allcontributors)) ### New Contributors * @kmc059000 made their first contribution in christianhelle/refitter#841 Full Changelog: christianhelle/refitter@1.7.0...1.7.1 Commits viewable in [compare view](christianhelle/refitter@1.7.0...1.7.1). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>



Description:
Addresses code review feedback for optional parameter default value generation. Three critical issues prevented correct code generation when OpenAPI specs contained default values with special characters, null type references, or floating-point numbers. Additionally, fixes two SonarCloud code quality issues related to performance and null reference warnings, and adds comprehensive test coverage for all edge cases.
Changes:
EscapeString()to handle quotes, backslashes, newlines, carriage returns, tabs, and additional escape sequences (form feed, vertical tab, backspace, null character) in default string values. Optimized implementation usingStringBuilderwith conservative memory allocation (value.Length + 10) for better performance.parameterModel.Typeto preventNullReferenceException. Restructured conditional logic to eliminate CS8602 compiler warning about possible null dereference.FormatNumericValue()to append type suffixes:fsuffix for float/Single typesmsuffix for decimal/Decimal typesLsuffix for long/Int64 types (for values exceeding int.MaxValue)ULsuffix for ulong/UInt64 types.0appended to double/Double types with integer values to ensure proper type inferenceSonarCloud Issues Fixed:
string.Replace()calls withStringBuilderfor efficient string manipulationCode Review Improvements:
value.Length * 2tovalue.Length + 10for more conservative memory allocationFormatDoubleLiteral()method to ensure integer values in double types are formatted with.0(e.g.,10.0instead of10)Tests Added:
Example OpenAPI Specifications:
{ "openapi": "3.0.1", "paths": { "/api/test": { "get": { "parameters": [ { "name": "message", "in": "query", "required": false, "schema": { "type": "string", "default": "path\\to\\file with \"quotes\"" } }, { "name": "ratio", "in": "query", "required": false, "schema": { "type": "number", "format": "float", "default": 1.5 } }, { "name": "price", "in": "query", "required": false, "schema": { "type": "number", "format": "decimal", "default": 99.99 } }, { "name": "count", "in": "query", "required": false, "schema": { "type": "integer", "format": "int64", "default": 3000000000 } }, { "name": "threshold", "in": "query", "required": false, "schema": { "type": "number", "format": "double", "default": 10 } } ], "responses": { "200": { "description": "Success" } } } } } }Example generated Refit interface
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.