diff --git a/.github/workflows/uet.yml b/.github/workflows/uet.yml index 08c4bc82..c2506031 100644 --- a/.github/workflows/uet.yml +++ b/.github/workflows/uet.yml @@ -710,6 +710,37 @@ jobs: UET_ARTIFACT_NAME: pass-2-mac UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }} + test-schema-generate: + name: "Test Schema Generation" + runs-on: windows-latest + if: github.ref != 'refs/heads/main' + needs: + - timestamp + - pass-2-win + env: + UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Download Windows Pass 2 + uses: ./.github/actions/download-artifact + with: + UET_ARTIFACT_NAME: pass-2-win + UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }} + - name: Download package.version + uses: ./.github/actions/download-artifact + with: + UET_ARTIFACT_NAME: timestamp + UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }} + - name: Test Schema Generation + shell: pwsh + run: | + UET/uet/bin/Release/${{ env.UET_FRAMEWORK_TARGET }}/win-x64/publish/uet.exe internal generate-json-schema ` + --output-path test-schema.json + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + publish-nuget: name: "Publish to NuGet" runs-on: windows-latest diff --git a/UET/uet/Commands/Internal/GenerateJsonSchema/DefaultJsonSchemaGenerator.cs b/UET/uet/Commands/Internal/GenerateJsonSchema/DefaultJsonSchemaGenerator.cs index d87389ed..11a22101 100644 --- a/UET/uet/Commands/Internal/GenerateJsonSchema/DefaultJsonSchemaGenerator.cs +++ b/UET/uet/Commands/Internal/GenerateJsonSchema/DefaultJsonSchemaGenerator.cs @@ -16,6 +16,7 @@ using System.Xml; using System.IO; using Microsoft.Extensions.Logging; + using System.Diagnostics.CodeAnalysis; internal sealed class DefaultJsonSchemaGenerator : IJsonSchemaGenerator { @@ -103,6 +104,23 @@ private static string ProcessSummary(XmlNode summary) return nsb.ToString().Trim().Replace("\r\n", "\n", StringComparison.Ordinal); } + private static Type GetDynamicInterfaceFromType(Type type) + { + Type? currentType = type; + while (currentType != null) + { + if (currentType.IsConstructedGenericType && currentType.GetGenericTypeDefinition() == typeof(BuildConfigDynamic<,>)) + { + return currentType; + } + currentType = type.BaseType; + } + + // Fallback - this should produce an error message that can help us diagnose what type is causing the issue. + return typeof(IDynamicProvider<,>) + .MakeGenericType((currentType ?? type).GetGenericArguments()); + } + private void GenerateSchemaForObject(Utf8JsonWriter writer, JsonTypeInfo jsonTypeInfo, JsonSerializerContext jsonTypeInfoResolver) { var isDynamicObject = jsonTypeInfo.Type.IsConstructedGenericType && @@ -152,21 +170,31 @@ private void GenerateSchemaForObject(Utf8JsonWriter writer, JsonTypeInfo jsonTyp // Write out all the possible values of Type. if (isDynamicObject) { - var dynamicProviderType = typeof(IDynamicProvider<,>) - .MakeGenericType(jsonTypeInfo.Type.GetGenericArguments()); - var types = _serviceProvider - .GetServices(dynamicProviderType) - .OfType() - .Select(x => x.Type) - .ToArray(); - writer.WritePropertyName("Type"); - writer.WriteStartArray(); - foreach (var type in types) + Type? dynamicProviderType = null; + try { - writer.WriteStringValue(type); + dynamicProviderType = GetDynamicInterfaceFromType(jsonTypeInfo.Type); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException($"Failed to generate schema for dynamic type {jsonTypeInfo.Type.FullName}: {ex.Message}", ex); + } + if (dynamicProviderType != null) + { + var types = _serviceProvider + .GetServices(dynamicProviderType) + .OfType() + .Select(x => x.Type) + .ToArray(); + writer.WritePropertyName("Type"); + writer.WriteStartArray(); + foreach (var type in types) + { + writer.WriteStringValue(type); + } + writer.WriteStringValue(BuildConfigConstants.Predefined); + writer.WriteEndArray(); } - writer.WriteStringValue(BuildConfigConstants.Predefined); - writer.WriteEndArray(); } writer.WriteEndObject(); @@ -252,98 +280,108 @@ private void GenerateSchemaForObject(Utf8JsonWriter writer, JsonTypeInfo jsonTyp { writer.WritePropertyName("allOf"); writer.WriteStartArray(); - var dynamicProviderType = typeof(IDynamicProvider<,>) - .MakeGenericType(jsonTypeInfo.Type.GetGenericArguments()); - var providers = _serviceProvider - .GetServices(dynamicProviderType) - .OfType() - .OfType() - .ToList(); - foreach (var type in providers) + Type? dynamicProviderType = null; + try { - writer.WriteStartObject(); - writer.WritePropertyName("if"); - writer.WriteStartObject(); - writer.WritePropertyName("properties"); - writer.WriteStartObject(); - writer.WritePropertyName("Type"); - writer.WriteStartObject(); - writer.WriteString("const", ((IDynamicProviderRegistration)type).Type); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WritePropertyName("then"); - writer.WriteStartObject(); - writer.WritePropertyName("required"); - writer.WriteStartArray(); - writer.WriteStringValue(((IDynamicProviderRegistration)type).Type); - writer.WriteEndArray(); - writer.WritePropertyName("properties"); - writer.WriteStartObject(); - writer.WriteBoolean(BuildConfigConstants.Predefined, false); - foreach (var otherProvider in providers.Where(x => ((IDynamicProviderRegistration)x).Type != ((IDynamicProviderRegistration)type).Type)) + dynamicProviderType = GetDynamicInterfaceFromType(jsonTypeInfo.Type); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException($"Failed to generate schema for dynamic type {jsonTypeInfo.Type.FullName}: {ex.Message}", ex); + } + if (dynamicProviderType != null) + { + var providers = _serviceProvider + .GetServices(dynamicProviderType) + .OfType() + .OfType() + .ToList(); + foreach (var type in providers) { - writer.WriteBoolean(((IDynamicProviderRegistration)otherProvider).Type, false); + writer.WriteStartObject(); + writer.WritePropertyName("if"); + writer.WriteStartObject(); + writer.WritePropertyName("properties"); + writer.WriteStartObject(); + writer.WritePropertyName("Type"); + writer.WriteStartObject(); + writer.WriteString("const", ((IDynamicProviderRegistration)type).Type); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WritePropertyName("then"); + writer.WriteStartObject(); + writer.WritePropertyName("required"); + writer.WriteStartArray(); + writer.WriteStringValue(((IDynamicProviderRegistration)type).Type); + writer.WriteEndArray(); + writer.WritePropertyName("properties"); + writer.WriteStartObject(); + writer.WriteBoolean(BuildConfigConstants.Predefined, false); + foreach (var otherProvider in providers.Where(x => ((IDynamicProviderRegistration)x).Type != ((IDynamicProviderRegistration)type).Type)) + { + writer.WriteBoolean(((IDynamicProviderRegistration)otherProvider).Type, false); + } + writer.WritePropertyName(((IDynamicProviderRegistration)type).Type); + writer.WriteStartObject(); + writer.WritePropertyName("properties"); + writer.WriteStartObject(); + GeneratePropertiesForObject( + writer, + type.DynamicSettings.JsonTypeInfo, + type.DynamicSettings.JsonSerializerContext, + type.DynamicSettings.JsonTypeInfo.Properties); + writer.WriteEndObject(); + if (type.DynamicSettings.JsonTypeInfo.Properties.Any(x => x.IsRequired)) + { + writer.WritePropertyName("required"); + writer.WriteStartArray(); + foreach (var property in type.DynamicSettings.JsonTypeInfo.Properties.Where(x => x.IsRequired)) + { + writer.WriteStringValue(property.Name); + } + writer.WriteEndArray(); + } + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WriteEndObject(); } - writer.WritePropertyName(((IDynamicProviderRegistration)type).Type); - writer.WriteStartObject(); - writer.WritePropertyName("properties"); - writer.WriteStartObject(); - GeneratePropertiesForObject( - writer, - type.DynamicSettings.JsonTypeInfo, - type.DynamicSettings.JsonSerializerContext, - type.DynamicSettings.JsonTypeInfo.Properties); - writer.WriteEndObject(); - if (type.DynamicSettings.JsonTypeInfo.Properties.Any(x => x.IsRequired)) { + writer.WriteStartObject(); + writer.WritePropertyName("if"); + writer.WriteStartObject(); + writer.WritePropertyName("properties"); + writer.WriteStartObject(); + writer.WritePropertyName("Type"); + writer.WriteStartObject(); + writer.WriteString("const", BuildConfigConstants.Predefined); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WritePropertyName("then"); + writer.WriteStartObject(); writer.WritePropertyName("required"); writer.WriteStartArray(); - foreach (var property in type.DynamicSettings.JsonTypeInfo.Properties.Where(x => x.IsRequired)) + writer.WriteStringValue(BuildConfigConstants.Predefined); + writer.WriteEndArray(); + writer.WritePropertyName("properties"); + writer.WriteStartObject(); + foreach (var otherProvider in providers) { - writer.WriteStringValue(property.Name); + writer.WriteBoolean(((IDynamicProviderRegistration)otherProvider).Type, false); } - writer.WriteEndArray(); + writer.WritePropertyName(BuildConfigConstants.Predefined); + writer.WriteStartObject(); + writer.WriteString("type", "string"); + writer.WriteString("description", "The predefined name defined earlier in configuration."); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WriteEndObject(); + writer.WriteEndObject(); } - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WriteEndObject(); - } - { - writer.WriteStartObject(); - writer.WritePropertyName("if"); - writer.WriteStartObject(); - writer.WritePropertyName("properties"); - writer.WriteStartObject(); - writer.WritePropertyName("Type"); - writer.WriteStartObject(); - writer.WriteString("const", BuildConfigConstants.Predefined); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WritePropertyName("then"); - writer.WriteStartObject(); - writer.WritePropertyName("required"); - writer.WriteStartArray(); - writer.WriteStringValue(BuildConfigConstants.Predefined); writer.WriteEndArray(); - writer.WritePropertyName("properties"); - writer.WriteStartObject(); - foreach (var otherProvider in providers) - { - writer.WriteBoolean(((IDynamicProviderRegistration)otherProvider).Type, false); - } - writer.WritePropertyName(BuildConfigConstants.Predefined); - writer.WriteStartObject(); - writer.WriteString("type", "string"); - writer.WriteString("description", "The predefined name defined earlier in configuration."); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WriteEndObject(); - writer.WriteEndObject(); } - writer.WriteEndArray(); } else {