diff --git a/Endpoints/HardwareEndpoints.cs b/Endpoints/HardwareEndpoints.cs index 585a818..b0c4c19 100644 --- a/Endpoints/HardwareEndpoints.cs +++ b/Endpoints/HardwareEndpoints.cs @@ -262,38 +262,60 @@ public static void MapHardwareEndpoints(this WebApplication app) }); } - private static string? GetString(JsonElement root, params string[] propertyNames) + private static bool TryFindProperty(JsonElement root, string[] propertyNames, out JsonElement element) { + if (root.ValueKind != JsonValueKind.Object) + { + element = default; + return false; + } + + // Fast path: O(1) exact lookup by property name + foreach (var name in propertyNames) + { + if (root.TryGetProperty(name, out element)) + { + return true; + } + } + + // Fallback: Case-insensitive match if property names differ in casing foreach (var prop in root.EnumerateObject()) { foreach (var name in propertyNames) { if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase)) { - return prop.Value.ValueKind == JsonValueKind.String ? prop.Value.GetString() : prop.Value.ToString(); + element = prop.Value; + return true; } } } + + element = default; + return false; + } + + private static string? GetString(JsonElement root, params string[] propertyNames) + { + if (TryFindProperty(root, propertyNames, out var prop)) + { + return prop.ValueKind == JsonValueKind.String ? prop.GetString() : prop.ToString(); + } return null; } private static long? GetLong(JsonElement root, params string[] propertyNames) { - foreach (var prop in root.EnumerateObject()) + if (TryFindProperty(root, propertyNames, out var prop)) { - foreach (var name in propertyNames) + if (prop.ValueKind == JsonValueKind.Number && prop.TryGetInt64(out var val)) { - if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase)) - { - if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt64(out var val)) - { - return val; - } - if (long.TryParse(prop.Value.ToString(), out var parsed)) - { - return parsed; - } - } + return val; + } + if (long.TryParse(prop.ToString(), out var parsed)) + { + return parsed; } } return null; @@ -301,21 +323,15 @@ public static void MapHardwareEndpoints(this WebApplication app) private static int? GetInt(JsonElement root, params string[] propertyNames) { - foreach (var prop in root.EnumerateObject()) + if (TryFindProperty(root, propertyNames, out var prop)) { - foreach (var name in propertyNames) + if (prop.ValueKind == JsonValueKind.Number && prop.TryGetInt32(out var val)) { - if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase)) - { - if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out var val)) - { - return val; - } - if (int.TryParse(prop.Value.ToString(), out var parsed)) - { - return parsed; - } - } + return val; + } + if (int.TryParse(prop.ToString(), out var parsed)) + { + return parsed; } } return null; @@ -323,21 +339,15 @@ public static void MapHardwareEndpoints(this WebApplication app) private static double? GetDouble(JsonElement root, params string[] propertyNames) { - foreach (var prop in root.EnumerateObject()) + if (TryFindProperty(root, propertyNames, out var prop)) { - foreach (var name in propertyNames) + if (prop.ValueKind == JsonValueKind.Number && prop.TryGetDouble(out var val)) { - if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase)) - { - if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetDouble(out var val)) - { - return val; - } - if (double.TryParse(prop.Value.ToString(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var parsed)) - { - return parsed; - } - } + return val; + } + if (double.TryParse(prop.ToString(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var parsed)) + { + return parsed; } } return null; diff --git a/LocalLLMServerManager.Tests/HardwareEndpointsTests.cs b/LocalLLMServerManager.Tests/HardwareEndpointsTests.cs index e784e14..b198bb6 100644 --- a/LocalLLMServerManager.Tests/HardwareEndpointsTests.cs +++ b/LocalLLMServerManager.Tests/HardwareEndpointsTests.cs @@ -259,4 +259,30 @@ public async Task PostHardwareEvaluate_InvalidContentType_Returns400BadRequest() var response = await _client.PostAsync("/api/hardware/evaluate", content); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } + + [Fact] + public async Task PostHardwareEvaluate_MixedCasingAndAlternativeNames_Returns200AndLlmFitResult() + { + var rawJson = """ + { + "MODALITY": "llm", + "model_name": "DeepSeek-R1-Distill-Qwen-14B", + "parameters": 14.0, + "quant": "Q4_K_M", + "context_length": 8192, + "available_vram_mb": 16384, + "available_ram_mb": 65536, + "total_layers": 48 + } + """; + + var content = new StringContent(rawJson, System.Text.Encoding.UTF8, "application/json"); + var response = await _client.PostAsync("/api/hardware/evaluate", content); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var result = await response.Content.ReadFromJsonAsync(JsonOptions); + Assert.NotNull(result); + Assert.Equal(48, result.TotalLayers); + } } +