88using Microsoft . Extensions . Configuration ;
99using Microsoft . Extensions . Logging ;
1010using System . Text . Json ;
11+ using System . Text . RegularExpressions ;
1112using System . Web ;
1213
1314namespace DevProxy . Plugins . RequestLogs ;
@@ -113,7 +114,7 @@ request.Method is null ||
113114 var rootModel = models . Last ( ) ;
114115 op . Parameters . Add ( new ( )
115116 {
116- Name = ( rootModel . IsArray ? ( await MakeSingular ( rootModel . Name ) ) : rootModel . Name ) . ToCamelCase ( ) ,
117+ Name = await GetParameterName ( rootModel ) ,
117118 Value = rootModel . Name ,
118119 In = ParameterLocation . Body
119120 } ) ;
@@ -170,7 +171,7 @@ request.Method is null ||
170171 var rootModel = models . Last ( ) ;
171172 if ( rootModel . IsArray )
172173 {
173- res . BodyType = $ "{ await MakeSingular ( rootModel . Name ) } []";
174+ res . BodyType = $ "{ rootModel . Name } []";
174175 op . Name = await GetOperationName ( "list" , url ) ;
175176 }
176177 else
@@ -212,6 +213,22 @@ request.Method is null ||
212213 e . GlobalData [ GeneratedTypeSpecFilesKey ] = generatedTypeSpecFiles ;
213214 }
214215
216+ private async Task < string > GetParameterName ( Model model )
217+ {
218+ Logger . LogTrace ( "Entered GetParameterName" ) ;
219+
220+ var name = model . IsArray ? SanitizeName ( await MakeSingular ( model . Name ) ) : model . Name ;
221+ if ( string . IsNullOrEmpty ( name ) )
222+ {
223+ name = model . Name ;
224+ }
225+
226+ Logger . LogDebug ( "Parameter name: {name}" , name ) ;
227+ Logger . LogTrace ( "Left GetParameterName" ) ;
228+
229+ return name ;
230+ }
231+
215232 private async Task < TypeSpecFile > GetOrCreateTypeSpecFile ( List < TypeSpecFile > files , Uri url )
216233 {
217234 Logger . LogTrace ( "Entered GetOrCreateTypeSpecFile" ) ;
@@ -252,7 +269,11 @@ private string GetRootNamespaceName(Uri url)
252269 {
253270 Logger . LogTrace ( "Entered GetRootNamespaceName" ) ;
254271
255- var ns = string . Join ( "" , url . Host . Split ( '.' ) . Select ( x => x . ToPascalCase ( ) ) ) ;
272+ var ns = SanitizeName ( string . Join ( "" , url . Host . Split ( '.' ) . Select ( x => x . ToPascalCase ( ) ) ) ) ;
273+ if ( string . IsNullOrEmpty ( ns ) )
274+ {
275+ ns = GetRandomName ( ) ;
276+ }
256277
257278 Logger . LogDebug ( "Root namespace name: {ns}" , ns ) ;
258279 Logger . LogTrace ( "Left GetRootNamespaceName" ) ;
@@ -268,14 +289,47 @@ private async Task<string> GetOperationName(string method, Uri url)
268289 Logger . LogDebug ( "Url: {url}" , url ) ;
269290 Logger . LogDebug ( "Last non-parametrizable segment: {lastSegment}" , lastSegment ) ;
270291
271- var operationName = $ "{ method . ToLowerInvariant ( ) } { ( method == "list" ? lastSegment : await MakeSingular ( lastSegment ) ) . ToPascalCase ( ) } ";
292+ var name = method == "list" ? lastSegment : await MakeSingular ( lastSegment ) ;
293+ if ( string . IsNullOrEmpty ( name ) )
294+ {
295+ name = lastSegment ;
296+ }
297+ name = SanitizeName ( name ) ;
298+ if ( string . IsNullOrEmpty ( name ) )
299+ {
300+ name = SanitizeName ( lastSegment ) ;
301+ if ( string . IsNullOrEmpty ( name ) )
302+ {
303+ name = GetRandomName ( ) ;
304+ }
305+ }
306+
307+ var operationName = $ "{ method . ToLowerInvariant ( ) } { name . ToPascalCase ( ) } ";
308+ var sanitizedName = SanitizeName ( operationName ) ;
309+ if ( ! string . IsNullOrEmpty ( sanitizedName ) )
310+ {
311+ Logger . LogDebug ( "Sanitized operation name: {sanitizedName}" , sanitizedName ) ;
312+ operationName = sanitizedName ;
313+ }
272314
273315 Logger . LogDebug ( "Operation name: {operationName}" , operationName ) ;
274316 Logger . LogTrace ( "Left GetOperationName" ) ;
275317
276318 return operationName ;
277319 }
278320
321+ private string GetRandomName ( )
322+ {
323+ Logger . LogTrace ( "Entered GetRandomName" ) ;
324+
325+ var name = Guid . NewGuid ( ) . ToString ( "N" ) ;
326+
327+ Logger . LogDebug ( "Random name: {name}" , name ) ;
328+ Logger . LogTrace ( "Left GetRandomName" ) ;
329+
330+ return name ;
331+ }
332+
279333 private async Task < string > GetOperationDescription ( string method , Uri url )
280334 {
281335 Logger . LogTrace ( "Entered GetOperationDescription" ) ;
@@ -392,7 +446,16 @@ private bool IsParametrizable(string segment)
392446 }
393447 else
394448 {
395- previousSegment = ( await MakeSingular ( segmentTrimmed ) ) . ToCamelCase ( ) ;
449+ previousSegment = SanitizeName ( await MakeSingular ( segmentTrimmed ) ) ;
450+ if ( string . IsNullOrEmpty ( previousSegment ) )
451+ {
452+ previousSegment = SanitizeName ( segmentTrimmed ) ;
453+ if ( previousSegment . Length == 0 )
454+ {
455+ previousSegment = GetRandomName ( ) ;
456+ }
457+ }
458+ previousSegment = previousSegment . ToCamelCase ( ) ;
396459 route . Add ( segmentTrimmed ) ;
397460 }
398461 }
@@ -457,12 +520,6 @@ private async Task<string> AddModelFromJsonElement(JsonElement jsonElement, stri
457520 {
458521 Logger . LogTrace ( "Entered AddModelFromJsonElement" ) ;
459522
460- var model = new Model
461- {
462- Name = await MakeSingular ( name ) ,
463- IsError = isError
464- } ;
465-
466523 switch ( jsonElement . ValueKind )
467524 {
468525 case JsonValueKind . String :
@@ -483,6 +540,12 @@ private async Task<string> AddModelFromJsonElement(JsonElement jsonElement, stri
483540 return "Empty" ;
484541 }
485542
543+ var model = new Model
544+ {
545+ Name = await GetModelName ( name ) ,
546+ IsError = isError
547+ } ;
548+
486549 foreach ( var p in jsonElement . EnumerateObject ( ) )
487550 {
488551 var property = new ModelProperty
@@ -495,16 +558,50 @@ private async Task<string> AddModelFromJsonElement(JsonElement jsonElement, stri
495558 models . Add ( model ) ;
496559 return model . Name ;
497560 case JsonValueKind . Array :
498- await AddModelFromJsonElement ( jsonElement . EnumerateArray ( ) . FirstOrDefault ( ) , name , isError , models ) ;
499- model . IsArray = true ;
500- model . Name = name ;
501- models . Add ( model ) ;
502- return $ "{ name } []";
561+ // we need to create a model for each item in the array
562+ // in case some items have null values or different shapes
563+ // we'll merge them later
564+ var modelName = string . Empty ;
565+ foreach ( var item in jsonElement . EnumerateArray ( ) )
566+ {
567+ modelName = await AddModelFromJsonElement ( item , name , isError , models ) ;
568+ }
569+ models . Add ( new Model
570+ {
571+ Name = modelName ,
572+ IsError = isError ,
573+ IsArray = true ,
574+ } ) ;
575+ return $ "{ modelName } []";
576+ case JsonValueKind . Null :
577+ return "null" ;
503578 default :
504579 return string . Empty ;
505580 }
506581 }
507582
583+ private async Task < string > GetModelName ( string name )
584+ {
585+ Logger . LogTrace ( "Entered GetModelName" ) ;
586+
587+ var modelName = SanitizeName ( await MakeSingular ( name ) ) ;
588+ if ( string . IsNullOrEmpty ( modelName ) )
589+ {
590+ modelName = SanitizeName ( name ) ;
591+ if ( string . IsNullOrEmpty ( modelName ) )
592+ {
593+ modelName = GetRandomName ( ) ;
594+ }
595+ }
596+
597+ modelName = modelName . ToPascalCase ( ) ;
598+
599+ Logger . LogDebug ( "Model name: {modelName}" , modelName ) ;
600+ Logger . LogTrace ( "Left GetModelName" ) ;
601+
602+ return modelName ;
603+ }
604+
508605 private async Task < string > MakeSingular ( string noun )
509606 {
510607 Logger . LogTrace ( "Entered MakeSingular" ) ;
@@ -517,11 +614,26 @@ private async Task<string> MakeSingular(string noun)
517614 }
518615 var singular = singularNoun ? . Response ;
519616
520- if ( singular is null ||
521- string . IsNullOrEmpty ( singular ) ||
617+ if ( string . IsNullOrEmpty ( singular ) ||
522618 singular . Contains ( ' ' ) )
523619 {
524- singular = noun . EndsWith ( 's' ) && ! noun . EndsWith ( "ss" ) ? noun [ 0 ..^ 1 ] : noun ;
620+ if ( noun . EndsWith ( "ies" ) )
621+ {
622+ singular = noun [ 0 ..^ 3 ] + 'y' ;
623+ }
624+ else if ( noun . EndsWith ( "es" ) )
625+ {
626+ singular = noun [ 0 ..^ 2 ] ;
627+ }
628+ else if ( noun . EndsWith ( 's' ) && ! noun . EndsWith ( "ss" ) )
629+ {
630+ singular = noun [ 0 ..^ 1 ] ;
631+ }
632+ else
633+ {
634+ singular = noun ;
635+ }
636+
525637 Logger . LogDebug ( "Failed to get singular form of {noun} from LLM. Using fallback: {singular}" , noun , singular ) ;
526638 }
527639
@@ -530,4 +642,16 @@ private async Task<string> MakeSingular(string noun)
530642
531643 return singular ;
532644 }
645+
646+ private string SanitizeName ( string name )
647+ {
648+ Logger . LogTrace ( "Entered SanitizeName" ) ;
649+
650+ var sanitized = Regex . Replace ( name , "[^a-zA-Z0-9_]" , "" ) ;
651+
652+ Logger . LogDebug ( "Sanitized name: {name} to: {sanitized}" , name , sanitized ) ;
653+ Logger . LogTrace ( "Left SanitizeName" ) ;
654+
655+ return sanitized ;
656+ }
533657}
0 commit comments