Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
93d8a47
add new v32 properties for Path Items
kilifu Oct 1, 2025
9f0d2c1
Merge branch 'feat/oai-3-2-support' into feat/5-new-fields-path-item
baywet Oct 2, 2025
1cac1a2
move query and AdditionalOperations to OpenApiOperation
kilifu Oct 3, 2025
e3ae554
Merge branch 'feat/5-new-fields-path-item' of https://github.com/Bink…
kilifu Oct 3, 2025
40a22f8
Merge branch 'feat/oai-3-2-support' into feat/5-new-fields-path-item
kilifu Oct 3, 2025
a256053
chore: reverts some undesired changes
baywet Oct 3, 2025
f7dc7ee
feat: adds support for additional operations in OAI 3.2
baywet Oct 3, 2025
d608cdc
chore: linting
baywet Oct 3, 2025
1a104d9
chore: fixes test files for path item additional operations
baywet Oct 3, 2025
c6b1b0e
chore: updates public export
baywet Oct 3, 2025
4801b06
chore: make new unit tests build
baywet Oct 3, 2025
0520443
chore: refactoring
baywet Oct 3, 2025
4f47916
tests: fixes additional operation test file
baywet Oct 3, 2025
71e51ea
tests: fixes test definitions
baywet Oct 3, 2025
cbc2934
fix: do not serialized extraneous operations in v2
baywet Oct 3, 2025
39f300d
tests: fixes unit tests for path item serialization
baywet Oct 3, 2025
30227ef
Merge branch 'feat/oai-3-2-support' into feat/5-new-fields-path-item
baywet Oct 3, 2025
fefec09
test: adds tests for 30 deserialization of path items with extra ops
baywet Oct 5, 2025
0781a89
chore: moves tests definitions to the right location
baywet Oct 5, 2025
49f9560
tests: adds deserialization tests for 3.1 path item extra operations
baywet Oct 5, 2025
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
14 changes: 12 additions & 2 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiPathItem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

using System.Collections.Generic;
using System.Collections.Generic;
using System.Net.Http;

namespace Microsoft.OpenApi;
Expand All @@ -25,4 +24,15 @@ public interface IOpenApiPathItem : IOpenApiDescribedElement, IOpenApiSummarized
/// These parameters can be overridden at the operation level, but cannot be removed there.
/// </summary>
public IList<IOpenApiParameter>? Parameters { get; }

/// <summary>
/// Gets the query operation for this path (OpenAPI 3.2).
/// </summary>
public OpenApiOperation? Query { get; }

/// <summary>
/// Gets the additional operations for this path (OpenAPI 3.2).
/// A map of additional operations that are not one of the standard HTTP methods (GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE).
/// </summary>
public IDictionary<string, OpenApiOperation>? AdditionalOperations { get; }
}
10 changes: 10 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@ public static class OpenApiConstants
/// </summary>
public const string RequestBody = "requestBody";

/// <summary>
/// Field: Query (OpenAPI 3.2)
/// </summary>
public const string Query = "query";

/// <summary>
/// Field: AdditionalOperations (OpenAPI 3.2)
/// </summary>
public const string AdditionalOperations = "additionalOperations";

/// <summary>
/// Field: ExtensionFieldNamePrefix
/// </summary>
Expand Down
55 changes: 52 additions & 3 deletions src/Microsoft.OpenApi/Models/OpenApiPathItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public class OpenApiPathItem : IOpenApiExtensible, IOpenApiPathItem
/// <inheritdoc/>
public IList<IOpenApiParameter>? Parameters { get; set; }

/// <inheritdoc/>
public OpenApiOperation? Query { get; set; }

/// <inheritdoc/>
public IDictionary<string, OpenApiOperation>? AdditionalOperations { get; set; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }

Expand Down Expand Up @@ -57,6 +63,8 @@ internal OpenApiPathItem(IOpenApiPathItem pathItem)
Operations = pathItem.Operations != null ? new Dictionary<HttpMethod, OpenApiOperation>(pathItem.Operations) : null;
Servers = pathItem.Servers != null ? [.. pathItem.Servers] : null;
Parameters = pathItem.Parameters != null ? [.. pathItem.Parameters] : null;
Query = pathItem.Query != null ? new OpenApiOperation(pathItem.Query) : null;
AdditionalOperations = pathItem.AdditionalOperations != null ? new Dictionary<string, OpenApiOperation>(pathItem.AdditionalOperations) : null;
Extensions = pathItem.Extensions != null ? new Dictionary<string, IOpenApiExtension>(pathItem.Extensions) : null;
}

Expand All @@ -73,15 +81,15 @@ public virtual void SerializeAsV32(IOpenApiWriter writer)
/// </summary>
public virtual void SerializeAsV31(IOpenApiWriter writer)
{
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer));
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer), downgradeFrom32: true);
}

/// <summary>
/// Serialize <see cref="OpenApiPathItem"/> to Open Api v3.0
/// </summary>
public virtual void SerializeAsV3(IOpenApiWriter writer)
{
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer), downgradeFrom32: true);
}

/// <summary>
Expand Down Expand Up @@ -120,14 +128,17 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)
OpenApiConstants.ExtensionFieldNamePrefix + OpenApiConstants.Description,
Description);

// Write Query and AdditionalOperations as extensions when downgrading to v2
WriteV32FieldsAsExtensions(writer);

// specification extensions
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);

writer.WriteEndObject();
}

internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
Action<IOpenApiWriter, IOpenApiSerializable> callback)
Action<IOpenApiWriter, IOpenApiSerializable> callback, bool downgradeFrom32 = false)
{
Utils.CheckArgumentNull(writer);

Expand All @@ -151,6 +162,21 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
}
}

// OpenAPI 3.2 specific fields
if (version == OpenApiSpecVersion.OpenApi3_2)
{
// query operation
writer.WriteOptionalObject(OpenApiConstants.Query, Query, callback);

// additional operations
writer.WriteOptionalMap(OpenApiConstants.AdditionalOperations, AdditionalOperations, callback);
}
else if (downgradeFrom32)
{
// When downgrading from 3.2 to 3.1/3.0, serialize as extensions
WriteV32FieldsAsExtensions(writer);
}

// servers
writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback);

Expand All @@ -163,6 +189,29 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
writer.WriteEndObject();
}

/// <summary>
/// Writes OpenAPI 3.2 specific fields as extensions when downgrading to older versions
/// </summary>
private void WriteV32FieldsAsExtensions(IOpenApiWriter writer)
{
if (Query != null)
{
writer.WritePropertyName(OpenApiConstants.ExtensionFieldNamePrefix + "oas-" + OpenApiConstants.Query);
Query.SerializeAsV31(writer);
}

if (AdditionalOperations != null && AdditionalOperations.Count > 0)
{
writer.WritePropertyName(OpenApiConstants.ExtensionFieldNamePrefix + "oas-" + OpenApiConstants.AdditionalOperations);
writer.WriteStartObject();
foreach (var kvp in AdditionalOperations)
{
writer.WriteOptionalObject(kvp.Key, kvp.Value, (w, o) => o.SerializeAsV31(w));
}
writer.WriteEndObject();
}
}

/// <inheritdoc/>
public IOpenApiPathItem CreateShallowCopy()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public string? Description
/// <inheritdoc/>
public IList<IOpenApiParameter>? Parameters { get => Target?.Parameters; }

/// <inheritdoc/>
public OpenApiOperation? Query { get => Target?.Query; }

/// <inheritdoc/>
public IDictionary<string, OpenApiOperation>? AdditionalOperations { get => Target?.AdditionalOperations; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension>? Extensions { get => Target?.Extensions; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ internal static partial class OpenApiV3Deserializer
private static readonly PatternFieldMap<OpenApiPathItem> _pathItemPatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix + "oas-query", StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.Query = LoadOperation(n, t)},
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix + "oas-additionalOperations", StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.AdditionalOperations = n.CreateMap(LoadOperation, t)},
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal static partial class OpenApiV31Deserializer
private static readonly PatternFieldMap<OpenApiPathItem> _pathItemPatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix + "oas-query", StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.Query = LoadOperation(n, t)},
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix + "oas-additionalOperations", StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.AdditionalOperations = n.CreateMap(LoadOperation, t)},
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Net.Http;

namespace Microsoft.OpenApi.Reader.V32
Expand Down Expand Up @@ -36,8 +36,10 @@ internal static partial class OpenApiV32Deserializer
{"patch", (o, n, t) => o.AddOperation(new HttpMethod("PATCH"), LoadOperation(n, t))},
#endif
{"trace", (o, n, t) => o.AddOperation(HttpMethod.Trace, LoadOperation(n, t))},
{"query", (o, n, t) => o.Query = LoadOperation(n, t)},
{"servers", (o, n, t) => o.Servers = n.CreateList(LoadServer, t)},
{"parameters", (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t)}
{"parameters", (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t)},
{"additionalOperations", (o, n, t) => o.AdditionalOperations = n.CreateMap(LoadOperation, t)}
};

private static readonly PatternFieldMap<OpenApiPathItem> _pathItemPatternFields =
Expand Down
Loading
Loading