Skip to content

Commit

Permalink
Test JSON schema generation on pull request, and fix JSON schema gene…
Browse files Browse the repository at this point in the history
…ration (#64)
  • Loading branch information
hach-que authored Nov 30, 2024
1 parent 0a1206f commit 9c33053
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 94 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/uet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Xml;
using System.IO;
using Microsoft.Extensions.Logging;
using System.Diagnostics.CodeAnalysis;

internal sealed class DefaultJsonSchemaGenerator : IJsonSchemaGenerator
{
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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<IDynamicProviderRegistration>()
.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<IDynamicProviderRegistration>()
.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();
Expand Down Expand Up @@ -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<IDynamicProviderRegistration>()
.OfType<IDynamicProvider>()
.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<IDynamicProviderRegistration>()
.OfType<IDynamicProvider>()
.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
{
Expand Down

0 comments on commit 9c33053

Please sign in to comment.