Skip to content

Commit 5a8cd38

Browse files
committed
Cleanup some funky logic
1 parent 71dfe86 commit 5a8cd38

File tree

3 files changed

+87
-51
lines changed

3 files changed

+87
-51
lines changed

src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.ComponentModel;
55
using System.ComponentModel.DataAnnotations;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Globalization;
78
using System.Linq;
89
using System.Reflection;
@@ -175,8 +176,9 @@ internal static void ApplyDefaultValue(this JsonNode schema, object? defaultValu
175176
return;
176177
}
177178

178-
var isReferencedSchema = schema[OpenApiConstants.SchemaId] is not null;
179-
var schemaAttribute = isReferencedSchema ? OpenApiConstants.RefDefaultAnnotation : OpenApiSchemaKeywords.DefaultKeyword;
179+
var schemaAttribute = schema.WillBeComponetized()
180+
? OpenApiConstants.RefDefaultAnnotation
181+
: OpenApiSchemaKeywords.DefaultKeyword;
180182

181183
if (defaultValue is null)
182184
{
@@ -432,6 +434,33 @@ internal static void ApplySchemaReferenceId(this JsonNode schema, JsonSchemaExpo
432434
}
433435
}
434436

437+
/// <summary>
438+
/// Determines whether the specified JSON schema will be moved into the components section.
439+
/// </summary>
440+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
441+
/// <returns><see langword="true"/> if the schema will be componentized; otherwise, <see langword="false"/>.</returns>
442+
internal static bool WillBeComponetized(this JsonNode schema)
443+
=> schema.WillBeComponetized(out _);
444+
445+
/// <summary>
446+
/// Determines whether the specified JSON schema node contains a componentized schema identifier.
447+
/// </summary>
448+
/// <param name="schema">The JSON schema node to inspect for a componentized schema identifier.</param>
449+
/// <param name="schemaId">When this method returns <see langword="true"/>, contains the schema identifier found in the node; otherwise,
450+
/// <see langword="null"/>.</param>
451+
/// <returns><see langword="true"/> if the schema will be componentized; otherwise, <see langword="false"/>.</returns>
452+
internal static bool WillBeComponetized(this JsonNode schema, [NotNullWhen(true)] out string? schemaId)
453+
{
454+
if (schema[OpenApiConstants.SchemaId] is JsonNode schemaIdNode
455+
&& schemaIdNode.GetValueKind() == JsonValueKind.String)
456+
{
457+
schemaId = schemaIdNode.GetValue<string>();
458+
return true;
459+
}
460+
schemaId = null;
461+
return false;
462+
}
463+
435464
/// <summary>
436465
/// Returns <langword ref="true" /> if the current type is a non-abstract base class that is not defined as its
437466
/// own derived type.
@@ -461,7 +490,7 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, JsonPrope
461490
schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString();
462491
}
463492
}
464-
if (schema[OpenApiConstants.SchemaId] is not null &&
493+
if (schema.WillBeComponetized() &&
465494
propertyInfo.PropertyType != typeof(object) && propertyInfo.ShouldApplyNullablePropertySchema())
466495
{
467496
schema[OpenApiConstants.NullableProperty] = true;
@@ -475,7 +504,7 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, JsonPrope
475504
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
476505
internal static void PruneNullTypeForComponentizedTypes(this JsonNode schema)
477506
{
478-
if (schema[OpenApiConstants.SchemaId] is not null &&
507+
if (schema.WillBeComponetized() &&
479508
schema[OpenApiSchemaKeywords.TypeKeyword] is JsonArray typeArray)
480509
{
481510
for (var i = typeArray.Count - 1; i >= 0; i--)

src/OpenApi/src/Extensions/OpenApiSchemaExtensions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Json.Nodes;
5+
46
namespace Microsoft.AspNetCore.OpenApi;
57

68
internal static class OpenApiSchemaExtensions
@@ -18,4 +20,42 @@ public static IOpenApiSchema CreateOneOfNullableWrapper(this IOpenApiSchema orig
1820
]
1921
};
2022
}
23+
24+
public static bool IsComponetizedSchema(this OpenApiSchema schema)
25+
=> schema.IsComponetizedSchema(out _);
26+
27+
public static bool IsComponetizedSchema(this OpenApiSchema schema, out string schemaId)
28+
{
29+
if(schema.Metadata is not null
30+
&& schema.Metadata.TryGetValue(OpenApiConstants.SchemaId, out var schemaIdAsObject)
31+
&& schemaIdAsObject is string schemaIdString)
32+
{
33+
schemaId = schemaIdString;
34+
return true;
35+
}
36+
schemaId = string.Empty;
37+
return false;
38+
}
39+
40+
public static OpenApiSchemaReference CreateReference(this OpenApiSchema schema, OpenApiDocument document)
41+
{
42+
if (!schema.IsComponetizedSchema(out var schemaId))
43+
{
44+
throw new InvalidOperationException("Schema is not a componentized schema.");
45+
}
46+
47+
object? description = null;
48+
object? example = null;
49+
object? defaultAnnotation = null;
50+
schema.Metadata?.TryGetValue(OpenApiConstants.RefDescriptionAnnotation, out description);
51+
schema.Metadata?.TryGetValue(OpenApiConstants.RefExampleAnnotation, out example);
52+
schema.Metadata?.TryGetValue(OpenApiConstants.RefDefaultAnnotation, out defaultAnnotation);
53+
54+
return new OpenApiSchemaReference(schemaId, document)
55+
{
56+
Description = description as string,
57+
Examples = example is JsonNode exampleJson ? [exampleJson] : null,
58+
Default = defaultAnnotation as JsonNode,
59+
};
60+
}
2161
}

src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ internal sealed class OpenApiSchemaService(
116116
{
117117
schema.ApplyDefaultValue(defaultValueAttribute.Value, context.TypeInfo);
118118
}
119-
var isInlinedSchema = schema[OpenApiConstants.SchemaId] is null;
119+
var isInlinedSchema = !schema.WillBeComponetized();
120120
if (isInlinedSchema)
121121
{
122122
if (propertyAttributes.OfType<DescriptionAttribute>().LastOrDefault() is { } descriptionAttribute)
@@ -265,29 +265,26 @@ internal static IOpenApiSchema ResolveReferenceForSchema(OpenApiDocument documen
265265
{
266266
var schema = UnwrapOpenApiSchema(inputSchema);
267267

268-
if (inputSchema is OpenApiSchema && schema.Metadata is not null &&
269-
!schema.Metadata.ContainsKey(OpenApiConstants.RefId) &&
270-
schema.Metadata.TryGetValue(OpenApiConstants.SchemaId, out var referenceId) &&
271-
referenceId is string referenceIdString)
268+
var isComponetizedSchema = schema.IsComponetizedSchema(out var schemaId);
269+
270+
// When we register it, this will be the resulting reference
271+
IOpenApiSchema? resultSchemaReference = null;
272+
if (inputSchema is OpenApiSchema && isComponetizedSchema)
272273
{
273274
var targetReferenceId = baseSchemaId is not null
274-
? $"{baseSchemaId}{referenceIdString}"
275-
: referenceIdString;
275+
? $"{baseSchemaId}{schemaId}"
276+
: schemaId;
276277
if (!string.IsNullOrEmpty(targetReferenceId))
277278
{
278-
document.AddOpenApiSchemaByReference(targetReferenceId, schema);
279+
resultSchemaReference = document.AddOpenApiSchemaByReference(targetReferenceId, schema);
279280
}
280281
}
281282

282-
if (schema.Metadata is not null &&
283-
schema.Metadata.TryGetValue(OpenApiConstants.SchemaId, out var resolvedBaseSchemaId))
283+
if (schema.AnyOf is { Count: > 0 })
284284
{
285-
if (schema.AnyOf is { Count: > 0 })
285+
for (var i = 0; i < schema.AnyOf.Count; i++)
286286
{
287-
for (var i = 0; i < schema.AnyOf.Count; i++)
288-
{
289-
schema.AnyOf[i] = ResolveReferenceForSchema(document, schema.AnyOf[i], rootSchemaId, resolvedBaseSchemaId?.ToString());
290-
}
287+
schema.AnyOf[i] = ResolveReferenceForSchema(document, schema.AnyOf[i], rootSchemaId, schemaId);
291288
}
292289
}
293290

@@ -340,39 +337,9 @@ internal static IOpenApiSchema ResolveReferenceForSchema(OpenApiDocument documen
340337
schema.Not = ResolveReferenceForSchema(document, schema.Not, rootSchemaId);
341338
}
342339

343-
// Handle schemas where the references have been inlined by the JsonSchemaExporter. In this case,
344-
// the `#` ID is generated by the exporter since it has no base document to baseline against. In this
345-
// case we we want to replace the reference ID with the schema ID that was generated by the
346-
// `CreateSchemaReferenceId` method in the OpenApiSchemaService.
347-
if (schema.Metadata is not null &&
348-
schema.Metadata.TryGetValue(OpenApiConstants.RefId, out var refId) &&
349-
refId is string refIdString)
350-
{
351-
if (schema.Metadata.TryGetValue(OpenApiConstants.SchemaId, out var schemaId) &&
352-
schemaId is string schemaIdString)
353-
{
354-
return new OpenApiSchemaReference(schemaIdString, document);
355-
}
356-
var relativeSchemaId = $"#/components/schemas/{rootSchemaId}{refIdString.Replace("#", string.Empty)}";
357-
return new OpenApiSchemaReference(relativeSchemaId, document);
358-
}
359-
360-
// If we're resolving schemas for a top-level schema being referenced in the `components.schema` property
361-
// we don't want to replace the top-level inline schema with a reference to itself. We want to replace
362-
// inline schemas to reference schemas for all schemas referenced in the top-level schema though (such as
363-
// `allOf`, `oneOf`, `anyOf`, `items`, `properties`, etc.) which is why `isTopLevel` is only set once.
364-
if (inputSchema is OpenApiSchema && schema.Metadata is not null &&
365-
!schema.Metadata.ContainsKey(OpenApiConstants.RefId) &&
366-
schema.Metadata.TryGetValue(OpenApiConstants.SchemaId, out var referenceId2) &&
367-
referenceId2 is string referenceIdString2)
340+
if (resultSchemaReference is not null)
368341
{
369-
var targetReferenceId = baseSchemaId is not null
370-
? $"{baseSchemaId}{referenceIdString2}"
371-
: referenceIdString2;
372-
if (!string.IsNullOrEmpty(targetReferenceId))
373-
{
374-
return document.AddOpenApiSchemaByReference(targetReferenceId, schema);
375-
}
342+
return resultSchemaReference;
376343
}
377344

378345
return schema;

0 commit comments

Comments
 (0)