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
20 changes: 19 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
Expand Down Expand Up @@ -161,6 +161,24 @@ public OpenApiDocument CreateDeepCopy()
return new OpenApiDeepCopyContext().Copy(this);
}

/// <summary>
/// Applies semantic conversions needed to represent this document in a different OpenAPI specification version.
/// </summary>
/// <param name="currentApiSpecVersion">The OpenAPI specification version this document currently represents.</param>
/// <param name="targetApiSpecVersion">The target OpenAPI specification version.</param>
[System.Diagnostics.CodeAnalysis.Experimental("OPENAPI001")]
public void ApplySemanticConversions(OpenApiSpecVersion currentApiSpecVersion, OpenApiSpecVersion targetApiSpecVersion)
{
if (currentApiSpecVersion <= OpenApiSpecVersion.OpenApi3_0 && targetApiSpecVersion >= OpenApiSpecVersion.OpenApi3_1)
{
new OpenApiV3ToV3_1DeserializationConverter().Convert(this);
}
else if (currentApiSpecVersion >= OpenApiSpecVersion.OpenApi3_1 && targetApiSpecVersion <= OpenApiSpecVersion.OpenApi3_0)
{
new OpenApiV3_1ToV3SerializationConverter().Convert(this);
}
}

/// <summary>
/// Serialize <see cref="OpenApiDocument"/> to an Open API document using the specified version.
/// </summary>
Expand Down
24 changes: 2 additions & 22 deletions src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
/// </summary>
public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IOpenApiSchemaMissingProperties, IOpenApiSchemaWithUnevaluatedProperties, IMetadataContainer, IDeepCopyable<IOpenApiSchema>
{
private static readonly IEnumerable<JsonNode> s_singleNullElementList = [ JsonNullSentinel.JsonNull ];

/// <inheritdoc />
public string? Title { get; set; }

Expand Down Expand Up @@ -471,7 +469,7 @@
}
}

private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,

Check failure on line 472 in src/Microsoft.OpenApi/Models/OpenApiSchema.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=microsoft_OpenAPI.NET&issues=AZ-QcfQYmYfklgBeJLd1&open=AZ-QcfQYmYfklgBeJLd1&pullRequest=2991
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
writer.WriteStartObject();
Expand Down Expand Up @@ -536,21 +534,8 @@
: Enum;
writer.WriteOptionalCollection(OpenApiConstants.Enum, enumValue, (nodeWriter, s) => nodeWriter.WriteAny(s));

if (version == OpenApiSpecVersion.OpenApi3_0)
{
// If we have a schema that's only just { "type": "null" }, we serialize it as enum with null value.
if (Type == JsonSchemaType.Null &&
OneOf is not { Count: > 0 } &&
AnyOf is not { Count: > 0 } &&
AllOf is not { Count: > 0 } &&
Enum is not { Count: > 0 })
{
writer.WriteOptionalCollection(OpenApiConstants.Enum, s_singleNullElementList, (nodeWriter, s) => nodeWriter.WriteAny(s));
}
}

// type
var serializedTypeProperty = TrySerializeTypeProperty(writer, version);
TrySerializeTypeProperty(writer, version);

// allOf
writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback);
Expand Down Expand Up @@ -595,13 +580,8 @@
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));

// nullable
if (version == OpenApiSpecVersion.OpenApi3_0 && serializedTypeProperty)
if (version == OpenApiSpecVersion.OpenApi3_0)
{
// https://spec.openapis.org/oas/v3.0.4.html#fixed-fields-20
// This keyword only takes effect if type is explicitly defined within the same Schema Object.
//
// If the user explicitly set IsNullable to true, we serialize it even if redundant.
// But if **we** are inferring it (from oneOf/anyOf), we don't serialize it when it's redundant.
SerializeNullable(writer, version);
}

Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.OpenApi/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@ const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonsc
const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string!
[OPENAPI001]Microsoft.OpenApi.IDeepCopyable<T>
[OPENAPI001]Microsoft.OpenApi.IDeepCopyable<T>.CreateDeepCopy() -> T
[OPENAPI001]Microsoft.OpenApi.OpenApiV3_1ToV3SerializationConverter
[OPENAPI001]Microsoft.OpenApi.OpenApiV3_1ToV3SerializationConverter.Convert(Microsoft.OpenApi.IOpenApiSchema! schema) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiV3_1ToV3SerializationConverter.Convert(Microsoft.OpenApi.OpenApiDocument! document) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiV3_1ToV3SerializationConverter.OpenApiV3_1ToV3SerializationConverter() -> void
[OPENAPI001]override Microsoft.OpenApi.OpenApiV3_1ToV3SerializationConverter.Visit(Microsoft.OpenApi.IOpenApiSchema! schema) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiV3ToV3_1DeserializationConverter
[OPENAPI001]Microsoft.OpenApi.OpenApiV3ToV3_1DeserializationConverter.Convert(Microsoft.OpenApi.IOpenApiSchema! schema) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiV3ToV3_1DeserializationConverter.Convert(Microsoft.OpenApi.OpenApiDocument! document) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiV3ToV3_1DeserializationConverter.OpenApiV3ToV3_1DeserializationConverter() -> void
[OPENAPI001]override Microsoft.OpenApi.OpenApiV3ToV3_1DeserializationConverter.Visit(Microsoft.OpenApi.IOpenApiSchema! schema) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiCallback.CreateDeepCopy() -> Microsoft.OpenApi.IOpenApiCallback!
[OPENAPI001]Microsoft.OpenApi.OpenApiCallbackReference.CreateDeepCopy() -> Microsoft.OpenApi.IOpenApiCallback!
[OPENAPI001]Microsoft.OpenApi.OpenApiComponents.CreateDeepCopy() -> Microsoft.OpenApi.OpenApiComponents!
[OPENAPI001]Microsoft.OpenApi.OpenApiContact.CreateDeepCopy() -> Microsoft.OpenApi.OpenApiContact!
[OPENAPI001]Microsoft.OpenApi.OpenApiDiscriminator.CreateDeepCopy() -> Microsoft.OpenApi.OpenApiDiscriminator!
[OPENAPI001]Microsoft.OpenApi.OpenApiDocument.CreateDeepCopy() -> Microsoft.OpenApi.OpenApiDocument!
[OPENAPI001]Microsoft.OpenApi.OpenApiDocument.ApplySemanticConversions(Microsoft.OpenApi.OpenApiSpecVersion currentApiSpecVersion, Microsoft.OpenApi.OpenApiSpecVersion targetApiSpecVersion) -> void
[OPENAPI001]Microsoft.OpenApi.OpenApiEncoding.CreateDeepCopy() -> Microsoft.OpenApi.OpenApiEncoding!
[OPENAPI001]Microsoft.OpenApi.OpenApiExample.CreateDeepCopy() -> Microsoft.OpenApi.IOpenApiExample!
[OPENAPI001]Microsoft.OpenApi.OpenApiExampleReference.CreateDeepCopy() -> Microsoft.OpenApi.IOpenApiExample!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.OpenApi;

/// <summary>
/// Converts OpenAPI 3.0 deserialized schema constructs into OpenAPI 3.1 constructs.
/// </summary>
[Experimental("OPENAPI001")]
public sealed class OpenApiV3ToV3_1DeserializationConverter : OpenApiVisitorBase

Check warning on line 12 in src/Microsoft.OpenApi/Services/OpenApiV3ToV3_1DeserializationConverter.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename class 'OpenApiV3ToV3_1DeserializationConverter' to match pascal case naming rules, consider using 'OpenApiV3ToV31DeserializationConverter'.

See more on https://sonarcloud.io/project/issues?id=microsoft_OpenAPI.NET&issues=AZ-QL1PhvE2aSu3gc5Lh&open=AZ-QL1PhvE2aSu3gc5Lh&pullRequest=2991
{
/// <summary>
/// Applies the conversion to an OpenAPI document.
/// </summary>
/// <param name="document">The document to convert.</param>
public void Convert(OpenApiDocument document)
{
Utils.CheckArgumentNull(document);
new OpenApiWalker(this).Walk(document);
}

/// <summary>
/// Applies the conversion to a schema.
/// </summary>
/// <param name="schema">The schema to convert.</param>
public void Convert(IOpenApiSchema schema)
{
Utils.CheckArgumentNull(schema);
new OpenApiWalker(this).Walk(schema);
}

/// <inheritdoc />
public override void Visit(IOpenApiSchema schema)
{
if (schema is OpenApiSchema concreteSchema && ShouldConvertNullEnumToNullType(concreteSchema))
{
concreteSchema.Type = JsonSchemaType.Null;
concreteSchema.Enum = null;
}
}

private static bool ShouldConvertNullEnumToNullType(OpenApiSchema schema) =>
schema.Type is null &&
schema.OneOf is not { Count: > 0 } &&
schema.AnyOf is not { Count: > 0 } &&
schema.AllOf is not { Count: > 0 } &&
schema.Enum is { Count: 1 } enumValues &&
enumValues[0].IsJsonNullSentinel();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;

namespace Microsoft.OpenApi;

/// <summary>
/// Converts OpenAPI 3.1 schema constructs into OpenAPI 3.0 serialization-compatible constructs.
/// </summary>
[Experimental("OPENAPI001")]
public sealed class OpenApiV3_1ToV3SerializationConverter : OpenApiVisitorBase

Check warning on line 14 in src/Microsoft.OpenApi/Services/OpenApiV3_1ToV3SerializationConverter.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename class 'OpenApiV3_1ToV3SerializationConverter' to match pascal case naming rules, consider using 'OpenApiV31ToV3SerializationConverter'.

See more on https://sonarcloud.io/project/issues?id=microsoft_OpenAPI.NET&issues=AZ-QL1NWvE2aSu3gc5Lg&open=AZ-QL1NWvE2aSu3gc5Lg&pullRequest=2991
{
/// <summary>
/// Applies the conversion to an OpenAPI document.
/// </summary>
/// <param name="document">The document to convert.</param>
public void Convert(OpenApiDocument document)
{
Utils.CheckArgumentNull(document);
new OpenApiWalker(this).Walk(document);
}

/// <summary>
/// Applies the conversion to a schema.
/// </summary>
/// <param name="schema">The schema to convert.</param>
public void Convert(IOpenApiSchema schema)
{
Utils.CheckArgumentNull(schema);
new OpenApiWalker(this).Walk(schema);
}

/// <inheritdoc />
public override void Visit(IOpenApiSchema schema)
{
if (schema is OpenApiSchema concreteSchema && ShouldConvertNullTypeToNullEnum(concreteSchema))
{
concreteSchema.Enum = new List<JsonNode>
{
JsonNullSentinel.JsonNull
};
concreteSchema.Type = null;
}
}

private static bool ShouldConvertNullTypeToNullEnum(OpenApiSchema schema) =>
schema.Type == JsonSchemaType.Null &&
schema.OneOf is not { Count: > 0 } &&
schema.AnyOf is not { Count: > 0 } &&
schema.AllOf is not { Count: > 0 } &&
schema.Enum is not { Count: > 0 };
}
Loading
Loading