1- using Microsoft . OpenApi ;
2-
31namespace RestClient . Net . OpenApiGenerator ;
42
53/// <summary>Generates C# extension methods from OpenAPI operations.</summary>
@@ -11,17 +9,21 @@ internal static class ExtensionMethodGenerator
119 /// <param name="className">The class name for extension methods.</param>
1210 /// <param name="baseUrl">The base URL for API requests.</param>
1311 /// <param name="basePath">The base path for API requests.</param>
12+ /// <param name="jsonNamingPolicy">JSON naming policy (camelCase, PascalCase, snake_case).</param>
13+ /// <param name="caseInsensitive">Enable case-insensitive JSON deserialization.</param>
1414 /// <returns>Tuple containing the extension methods code and type aliases code.</returns>
1515 public static ( string ExtensionMethods , string TypeAliases ) GenerateExtensionMethods (
1616 OpenApiDocument document ,
1717 string @namespace ,
1818 string className ,
1919 string baseUrl ,
20- string basePath
20+ string basePath ,
21+ string jsonNamingPolicy = "camelCase" ,
22+ bool caseInsensitive = true
2123 )
2224 {
23- var publicMethods = new List < string > ( ) ;
24- var privateDelegates = new List < string > ( ) ;
25+ var groupedMethods =
26+ new Dictionary < string , List < ( string PublicMethod , string PrivateDelegate ) > > ( ) ;
2527 var responseTypes = new HashSet < string > ( ) ;
2628
2729 foreach ( var path in document . Paths )
@@ -31,6 +33,8 @@ string basePath
3133 continue ;
3234 }
3335
36+ var resourceName = GetResourceNameFromPath ( path . Key ) ;
37+
3438 foreach ( var operation in path . Value . Operations )
3539 {
3640 var responseType = GetResponseType ( operation . Value ) ;
@@ -46,16 +50,30 @@ string basePath
4650 ) ;
4751 if ( ! string . IsNullOrEmpty ( publicMethod ) )
4852 {
49- publicMethods . Add ( publicMethod ) ;
50- privateDelegates . Add ( privateDelegate ) ;
53+ if ( ! groupedMethods . TryGetValue ( resourceName , out var methods ) )
54+ {
55+ methods = [ ] ;
56+ groupedMethods [ resourceName ] = methods ;
57+ }
58+ methods . Add ( ( publicMethod , privateDelegate ) ) ;
5159 }
5260 }
5361 }
5462
55- var publicMethodsCode = string . Join ( " \n \n " , publicMethods ) ;
56- var privateDelegatesCode = string . Join ( " \n \n " , privateDelegates ) ;
63+ var publicMethodsCode = GenerateGroupedCode ( groupedMethods , isPublic : true ) ;
64+ var privateDelegatesCode = GenerateGroupedCode ( groupedMethods , isPublic : false ) ;
5765 var typeAliases = GenerateTypeAliasesFile ( responseTypes , @namespace ) ;
5866
67+ var namingPolicyCode = jsonNamingPolicy switch
68+ {
69+ var s when s . Equals ( "PascalCase" , StringComparison . OrdinalIgnoreCase ) => "null" ,
70+ var s when s . Equals ( "snake_case" , StringComparison . OrdinalIgnoreCase )
71+ || s . Equals ( "snakecase" , StringComparison . OrdinalIgnoreCase ) => "JsonNamingPolicy.SnakeCaseLower" ,
72+ _ => "JsonNamingPolicy.CamelCase" ,
73+ } ;
74+
75+ var caseInsensitiveCode = caseInsensitive ? "true" : "false" ;
76+
5977 var extensionMethodsCode = $$ """
6078 using System;
6179 using System.Collections.Generic;
@@ -74,26 +92,24 @@ namespace {{@namespace}};
7492 /// <summary>Extension methods for API operations.</summary>
7593 public static class {{ className }}
7694 {
95+ #region Configuration
96+
7797 private static readonly AbsoluteUrl BaseUrl = "{{ baseUrl }} ".ToAbsoluteUrl();
7898
7999 private static readonly JsonSerializerOptions JsonOptions = new()
80100 {
81- PropertyNameCaseInsensitive = true ,
82- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
101+ PropertyNameCaseInsensitive = {{ caseInsensitiveCode }} ,
102+ PropertyNamingPolicy = {{ namingPolicyCode }}
83103 };
84104
85- #region Public Extension Methods
86-
87- {{ CodeGenerationHelpers . Indent ( publicMethodsCode , 1 ) }}
88-
89105 #endregion
90106
91- #region Private Members
107+ {{ publicMethodsCode }}
92108
93109 private static readonly Deserialize<Unit> _deserializeUnit = static (_, _) =>
94110 Task.FromResult(Unit.Value);
95111
96- {{ CodeGenerationHelpers . Indent ( privateDelegatesCode , 1 ) }}
112+ {{ privateDelegatesCode }}
97113
98114 private static ProgressReportingHttpContent CreateJsonContent<T>(T data)
99115 {
@@ -127,14 +143,50 @@ private static async Task<string> DeserializeError(
127143 var content = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
128144 return string.IsNullOrEmpty(content) ? "Unknown error" : content;
129145 }
130-
131- #endregion
132146 }
133147 """ ;
134148
135149 return ( extensionMethodsCode , typeAliases ) ;
136150 }
137151
152+ private static string GetResourceNameFromPath ( string path )
153+ {
154+ var segments = path . Split ( '/' , StringSplitOptions . RemoveEmptyEntries ) ;
155+ if ( segments . Length == 0 )
156+ {
157+ return "General" ;
158+ }
159+
160+ var resourceSegment = segments [ 0 ] ;
161+ return CodeGenerationHelpers . ToPascalCase ( resourceSegment ) ;
162+ }
163+
164+ private static string GenerateGroupedCode (
165+ Dictionary < string , List < ( string PublicMethod , string PrivateDelegate ) > > groupedMethods ,
166+ bool isPublic
167+ )
168+ {
169+ var sections = new List < string > ( ) ;
170+
171+ foreach ( var group in groupedMethods . OrderBy ( g => g . Key ) )
172+ {
173+ var methods = isPublic
174+ ? group . Value . Select ( m => m . PublicMethod )
175+ : group . Value . Select ( m => m . PrivateDelegate ) ;
176+
177+ var methodsCode = string . Join ( "\n \n " , methods ) ;
178+ var indentedContent = CodeGenerationHelpers . Indent ( methodsCode , 1 ) ;
179+ var regionName = $ "{ group . Key } Operations";
180+
181+ // #region markers at column 0, content indented by 4 spaces
182+ var section = $ " #region { regionName } \n \n { indentedContent } \n \n #endregion";
183+
184+ sections . Add ( section ) ;
185+ }
186+
187+ return string . Join ( "\n \n " , sections ) ;
188+ }
189+
138190 private static ( string PublicMethod , string PrivateDelegate ) GenerateMethod (
139191 string path ,
140192 HttpMethod operationType ,
0 commit comments