Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument do

object? description = null;
object? example = null;
object? defaultValue = null;
if (schema is OpenApiSchema actualSchema)
{
actualSchema.Metadata?.TryGetValue(OpenApiConstants.RefDescriptionAnnotation, out description);
actualSchema.Metadata?.TryGetValue(OpenApiConstants.RefExampleAnnotation, out example);
actualSchema.Metadata?.TryGetValue(OpenApiConstants.RefDefaultAnnotation, out defaultValue);
}

return new OpenApiSchemaReference(schemaId, document)
{
Description = description as string,
Examples = example is JsonNode exampleJson ? [exampleJson] : null,
Default = defaultValue is JsonNode defaultValueJson ? defaultValueJson : null,
};
}
}
5 changes: 0 additions & 5 deletions src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,6 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
schema.Metadata ??= new Dictionary<string, object>();
schema.Metadata[OpenApiConstants.RefId] = reader.GetString() ?? string.Empty;
break;
case OpenApiConstants.RefDescriptionAnnotation:
reader.Read();
schema.Metadata ??= new Dictionary<string, object>();
schema.Metadata[OpenApiConstants.RefDescriptionAnnotation] = reader.GetString() ?? string.Empty;
break;

default:
reader.Skip();
Expand Down
1 change: 1 addition & 0 deletions src/OpenApi/src/Services/OpenApiConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal static class OpenApiConstants
internal const string DescriptionId = "x-aspnetcore-id";
internal const string SchemaId = "x-schema-id";
internal const string RefId = "x-ref-id";
internal const string RefDefaultAnnotation = "x-ref-default";
internal const string RefDescriptionAnnotation = "x-ref-description";
internal const string RefExampleAnnotation = "x-ref-example";
internal const string RefKeyword = "$ref";
Expand Down
3 changes: 2 additions & 1 deletion src/OpenApi/src/Services/OpenApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.OpenApi.Services.Schemas.Transformers;

namespace Microsoft.AspNetCore.OpenApi;

Expand All @@ -14,7 +15,7 @@ public sealed class OpenApiOptions
{
internal readonly List<IOpenApiDocumentTransformer> DocumentTransformers = [];
internal readonly List<IOpenApiOperationTransformer> OperationTransformers = [];
internal readonly List<IOpenApiSchemaTransformer> SchemaTransformers = [];
internal readonly List<IOpenApiSchemaTransformer> SchemaTransformers = [new AttributeAnnotationsSchemaTransformer()];

/// <summary>
/// A default implementation for creating a schema reference ID for a given <see cref="JsonTypeInfo"/>.
Expand Down
24 changes: 0 additions & 24 deletions src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.IO.Pipelines;
Expand Down Expand Up @@ -101,36 +100,13 @@ internal sealed class OpenApiSchemaService(
{
schema.ApplyNullabilityContextInfo(jsonPropertyInfo);
}
if (context.TypeInfo.Type.GetCustomAttributes(inherit: false).OfType<DescriptionAttribute>().LastOrDefault() is { } typeDescriptionAttribute)
{
schema[OpenApiSchemaKeywords.DescriptionKeyword] = typeDescriptionAttribute.Description;
}
if (context.PropertyInfo is { AttributeProvider: { } attributeProvider })
{
var propertyAttributes = attributeProvider.GetCustomAttributes(inherit: false);
if (propertyAttributes.OfType<ValidationAttribute>() is { } validationAttributes)
{
schema.ApplyValidationAttributes(validationAttributes);
}
if (propertyAttributes.OfType<DefaultValueAttribute>().LastOrDefault() is { } defaultValueAttribute)
{
schema.ApplyDefaultValue(defaultValueAttribute.Value, context.TypeInfo);
}
var isInlinedSchema = schema[OpenApiConstants.SchemaId] is null;
if (isInlinedSchema)
{
if (propertyAttributes.OfType<DescriptionAttribute>().LastOrDefault() is { } descriptionAttribute)
{
schema[OpenApiSchemaKeywords.DescriptionKeyword] = descriptionAttribute.Description;
}
}
else
{
if (propertyAttributes.OfType<DescriptionAttribute>().LastOrDefault() is { } descriptionAttribute)
{
schema[OpenApiConstants.RefDescriptionAnnotation] = descriptionAttribute.Description;
}
}
}
schema.PruneNullTypeForComponentizedTypes();
return schema;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;

namespace Microsoft.AspNetCore.OpenApi.Services.Schemas.Transformers;

internal class AttributeAnnotationsSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
schema.Metadata ??= new Dictionary<string, object>();
var isInlinedSchema = !schema.Metadata.ContainsKey(OpenApiConstants.SchemaId) || string.IsNullOrEmpty(schema.Metadata[OpenApiConstants.SchemaId] as string);
if (context.JsonTypeInfo.Type.GetCustomAttributes(inherit: false).OfType<DescriptionAttribute>().LastOrDefault() is { } typeDescriptionAttribute)
{
schema.Description = typeDescriptionAttribute.Description;
}

if (context.JsonPropertyInfo?.AttributeProvider?.GetCustomAttributes(inherit: false).OfType<DescriptionAttribute>().LastOrDefault() is { } propertyDescriptionAttribute)
{
if (isInlinedSchema)
{
schema.Description = propertyDescriptionAttribute.Description;
}
else
{
schema.Metadata![OpenApiConstants.RefDescriptionAnnotation] = propertyDescriptionAttribute.Description;
}
}

if (context.JsonTypeInfo.Type.GetCustomAttributes(inherit: false).OfType<DefaultValueAttribute>().LastOrDefault() is { } typeDefaultValueAttribute)
{
schema.Default = GetDefaultValueAsJsonNode(typeDefaultValueAttribute, context.JsonTypeInfo);
}

if (context.JsonPropertyInfo?.AttributeProvider?.GetCustomAttributes(inherit: false).OfType<DefaultValueAttribute>().LastOrDefault() is { } propertyDefaultValueAttribute)
{
var defaultValueJson = GetDefaultValueAsJsonNode(propertyDefaultValueAttribute, context.JsonTypeInfo);
if (isInlinedSchema)
{
schema.Default = defaultValueJson;
}
else
{
schema.Metadata![OpenApiConstants.RefDefaultAnnotation] = defaultValueJson!;
}
}

return Task.CompletedTask;
}

private static JsonNode? GetDefaultValueAsJsonNode(DefaultValueAttribute defaultValueAttribute, JsonTypeInfo jsonTypeInfo)
{
if(defaultValueAttribute.Value is null)
{
return null;
}

return JsonSerializer.SerializeToNode(defaultValueAttribute.Value, jsonTypeInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public void AddDocumentTransformer_WithDocumentTransformerDelegate()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

var transformer = new Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task>((document, context, cancellationToken) =>
{
document.Info.Title = "New Title";
Expand All @@ -32,6 +34,8 @@ public void AddDocumentTransformer_WithDocumentTransformerInstance()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

var transformer = new TestOpenApiDocumentTransformer();

// Act
Expand All @@ -50,6 +54,7 @@ public void AddDocumentTransformer_WithDocumentTransformerType()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

// Act
var result = options.AddDocumentTransformer<TestOpenApiDocumentTransformer>();
Expand All @@ -67,6 +72,8 @@ public void AddOperationTransformer_WithOperationTransformerDelegate()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

var transformer = new Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task>((operation, context, cancellationToken) =>
{
operation.Description = "New Description";
Expand All @@ -89,6 +96,8 @@ public void AddOperationTransformer_WithOperationTransformerInstance()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

var transformer = new TestOpenApiOperationTransformer();

// Act
Expand All @@ -107,6 +116,7 @@ public void AddOperationTransformer_WithOperationTransformerType()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

// Act
var result = options.AddOperationTransformer<TestOpenApiOperationTransformer>();
Expand All @@ -124,6 +134,8 @@ public void AddSchemaTransformer_WithSchemaTransformerDelegate()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

var transformer = new Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>((schema, context, cancellationToken) =>
{
schema.Description = "New Description";
Expand All @@ -146,6 +158,8 @@ public void AddSchemaTransformer_WithSchemaTransformerInstance()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

var transformer = new TestOpenApiSchemaTransformer();

// Act
Expand All @@ -164,6 +178,7 @@ public void AddSchemaTransformer_WithSchemaTransformerType()
{
// Arrange
var options = new OpenApiOptions();
RemoveBuiltInTransformers(options);

// Act
var result = options.AddSchemaTransformer<TestOpenApiSchemaTransformer>();
Expand All @@ -176,6 +191,13 @@ public void AddSchemaTransformer_WithSchemaTransformerType()
Assert.Empty(options.OperationTransformers);
}

private static void RemoveBuiltInTransformers(OpenApiOptions options)
{
options.DocumentTransformers.Clear();
options.OperationTransformers.Clear();
options.SchemaTransformers.Clear();
}

private class TestOpenApiDocumentTransformer : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
Expand Down
Loading