Skip to content

Commit 244f105

Browse files
waldekmastykarzgarrytrinder
authored andcommitted
Fixes generating TypeSpec files
1 parent 60c20ba commit 244f105

File tree

4 files changed

+156
-24
lines changed

4 files changed

+156
-24
lines changed

dev-proxy-abstractions/LanguageModel/OllamaLanguageModelClient.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private async Task<bool> IsEnabledInternalAsync()
4545
_logger.LogError("Model is not set. Language model will be disabled");
4646
return false;
4747
}
48-
48+
4949
_logger.LogDebug("Checking LM availability at {url}...", _configuration.Url);
5050

5151
try
@@ -132,14 +132,17 @@ private async Task<bool> IsEnabledInternalAsync()
132132
options
133133
}
134134
);
135-
_logger.LogDebug("Response: {response}", response.StatusCode);
135+
_logger.LogDebug("Response status: {response}", response.StatusCode);
136136

137137
var res = await response.Content.ReadFromJsonAsync<OllamaLanguageModelCompletionResponse>();
138138
if (res is null)
139139
{
140+
_logger.LogDebug("Response: null");
140141
return res;
141142
}
142143

144+
_logger.LogDebug("Response: {response}", res.Response);
145+
143146
res.RequestUrl = url;
144147
return res;
145148
}
@@ -217,7 +220,7 @@ private async Task<bool> IsEnabledInternalAsync()
217220
}
218221
);
219222
_logger.LogDebug("Response: {response}", response.StatusCode);
220-
223+
221224
var res = await response.Content.ReadFromJsonAsync<OllamaLanguageModelChatCompletionResponse>();
222225
if (res is null)
223226
{

dev-proxy-plugins/RequestLogs/TypeSpecGeneratorPlugin.cs

Lines changed: 143 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.Extensions.Configuration;
99
using Microsoft.Extensions.Logging;
1010
using System.Text.Json;
11+
using System.Text.RegularExpressions;
1112
using System.Web;
1213

1314
namespace 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
}

dev-proxy-plugins/TypeSpec/Http.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ override public string ToString()
136136
internal class ModelProperty
137137
{
138138
public required string Name { get; init; }
139-
public required string Type { get; init; }
139+
public required string Type { get; set; }
140140

141141
override public string ToString()
142142
{

dev-proxy-plugins/TypeSpec/TypeSpecExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ public static string MergeModel(this Namespace ns, Model model)
1717

1818
foreach (var prop in model.Properties)
1919
{
20-
if (!existingModel.Properties.Any(p => p.Name == prop.Name))
20+
var existingProp = existingModel.Properties.FirstOrDefault(p => p.Name == prop.Name);
21+
if (existingProp is null)
2122
{
2223
existingModel.Properties.Add(prop);
2324
}
25+
else if (existingProp.Type == "null")
26+
{
27+
existingProp.Type = prop.Type;
28+
}
2429
}
2530

2631
return existingModel.Name;

0 commit comments

Comments
 (0)