Skip to content

Commit

Permalink
Lower AIJsonUtilities to STJv8 and move to Abstractions library. (#…
Browse files Browse the repository at this point in the history
…5582)

* Lower AIJsonUtilities to STJv8 and move to Abstractions library.

* Add README.md
  • Loading branch information
eiriktsarpalis authored and joperezr committed Nov 5, 2024
1 parent cd9da61 commit 365f33c
Show file tree
Hide file tree
Showing 21 changed files with 4,294 additions and 17 deletions.
4 changes: 4 additions & 0 deletions eng/MSBuild/LegacySupport.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\StringSyntaxAttribute\*.cs" LinkBase="LegacySupport\StringSyntaxAttribute" />
</ItemGroup>

<ItemGroup Condition="'$(InjectJsonSchemaExporterOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0')">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\JsonSchemaExporter\**\*.cs" LinkBase="Shared\EmptyCollections" />
</ItemGroup>

<ItemGroup Condition="'$(InjectGetOrAddOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0')">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\GetOrAdd\*.cs" LinkBase="LegacySupport\GetOrAdd" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions eng/packages/TestOnly.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<PackageVersion Include="Verify.Xunit" Version="20.4.0" />
<PackageVersion Include="Xunit.Combinatorial" Version="1.6.24" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.2" />
<PackageVersion Include="JsonSchema.Net" Version="7.2.3" />
<PackageVersion Include="JsonSchema.Net.Generation" Version="4.3.0.2" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</PropertyGroup>

<PropertyGroup>
<InjectJsonSchemaExporterOnLegacy>true</InjectJsonSchemaExporterOnLegacy>
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>
<InjectStringSyntaxAttributeOnLegacy>true</InjectStringSyntaxAttributeOnLegacy>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
#if !NET9_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
Expand All @@ -16,6 +19,7 @@
#pragma warning disable S1121 // Assignments should not be made from within sub-expressions
#pragma warning disable S107 // Methods should not have too many parameters
#pragma warning disable S1075 // URIs should not be hardcoded
#pragma warning disable SA1118 // Parameter should not span multiple lines

using FunctionParameterKey = (
System.Type? Type,
Expand Down Expand Up @@ -174,6 +178,11 @@ private static JsonElement GetJsonSchemaCached(JsonSerializerOptions options, Fu
#endif
}

#if !NET9_0_OR_GREATER
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access",
Justification = "Pre STJ-9 schema extraction can fail with a runtime exception if certain reflection metadata have been trimmed. " +
"The exception message will guide users to turn off 'IlcTrimMetadata' which resolves all issues.")]
#endif
private static JsonElement GetJsonSchemaCore(JsonSerializerOptions options, FunctionParameterKey key)
{
_ = Throw.IfNull(options);
Expand Down Expand Up @@ -236,16 +245,9 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
const string DefaultPropertyName = "default";
const string RefPropertyName = "$ref";

// Find the first DescriptionAttribute, starting first from the property, then the parameter, and finally the type itself.
Type descAttrType = typeof(DescriptionAttribute);
var descriptionAttribute =
GetAttrs(descAttrType, ctx.PropertyInfo?.AttributeProvider)?.FirstOrDefault() ??
GetAttrs(descAttrType, ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider)?.FirstOrDefault() ??
GetAttrs(descAttrType, ctx.TypeInfo.Type)?.FirstOrDefault();

if (descriptionAttribute is DescriptionAttribute attr)
if (ctx.ResolveAttribute<DescriptionAttribute>() is { } attr)
{
ConvertSchemaToObject(ref schema).Insert(0, DescriptionPropertyName, (JsonNode)attr.Description);
ConvertSchemaToObject(ref schema).InsertAtStart(DescriptionPropertyName, (JsonNode)attr.Description);
}

if (schema is JsonObject objSchema)
Expand All @@ -268,7 +270,7 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
// Include the type keyword in enum types
if (key.IncludeTypeInEnumSchemas && ctx.TypeInfo.Type.IsEnum && objSchema.ContainsKey(EnumPropertyName) && !objSchema.ContainsKey(TypePropertyName))
{
objSchema.Insert(0, TypePropertyName, "string");
objSchema.InsertAtStart(TypePropertyName, "string");
}

// Disallow additional properties in object schemas
Expand Down Expand Up @@ -303,7 +305,7 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
if (index < 0)
{
// If there's no description property, insert it at the beginning of the doc.
obj.Insert(0, DescriptionPropertyName, (JsonNode)key.Description!);
obj.InsertAtStart(DescriptionPropertyName, (JsonNode)key.Description!);
}
else
{
Expand All @@ -321,15 +323,12 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
if (key.IncludeSchemaUri)
{
// The $schema property must be the first keyword in the object
ConvertSchemaToObject(ref schema).Insert(0, SchemaPropertyName, (JsonNode)SchemaKeywordUri);
ConvertSchemaToObject(ref schema).InsertAtStart(SchemaPropertyName, (JsonNode)SchemaKeywordUri);
}
}

return schema;

static object[]? GetAttrs(Type attrType, ICustomAttributeProvider? provider) =>
provider?.GetCustomAttributes(attrType, inherit: false);

static JsonObject ConvertSchemaToObject(ref JsonNode schema)
{
JsonObject obj;
Expand Down Expand Up @@ -368,6 +367,62 @@ private static bool TypeIsArrayContainingInteger(JsonObject schema)
return false;
}

private static void InsertAtStart(this JsonObject jsonObject, string key, JsonNode value)
{
#if NET9_0_OR_GREATER
jsonObject.Insert(0, key, value);
#else
jsonObject.Remove(key);
var copiedEntries = jsonObject.ToArray();
jsonObject.Clear();

jsonObject.Add(key, value);
foreach (var entry in copiedEntries)
{
jsonObject[entry.Key] = entry.Value;
}
#endif
}

#if !NET9_0_OR_GREATER
private static int IndexOf(this JsonObject jsonObject, string key)
{
int i = 0;
foreach (var entry in jsonObject)
{
if (string.Equals(entry.Key, key, StringComparison.Ordinal))
{
return i;
}

i++;
}

return -1;
}
#endif

private static TAttribute? ResolveAttribute<TAttribute>(this JsonSchemaExporterContext ctx)
where TAttribute : Attribute
{
// Resolve attributes from locations in the following order:
// 1. Property-level attributes
// 2. Parameter-level attributes and
// 3. Type-level attributes.
return
#if NET9_0_OR_GREATER
GetAttrs(ctx.PropertyInfo?.AttributeProvider) ??
GetAttrs(ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider) ??
#else
GetAttrs(ctx.PropertyAttributeProvider) ??
GetAttrs(ctx.ParameterInfo) ??
#endif
GetAttrs(ctx.TypeInfo.Type);

static TAttribute? GetAttrs(ICustomAttributeProvider? provider) =>
(TAttribute?)provider?.GetCustomAttributes(typeof(TAttribute), inherit: false).FirstOrDefault();
}

private static JsonElement ParseJsonElement(ReadOnlySpan<byte> utf8Json)
{
Utf8JsonReader reader = new(utf8Json);
Expand Down
Loading

0 comments on commit 365f33c

Please sign in to comment.