From 3b3356b9fb6b3a8b292618841c6fed61785d6109 Mon Sep 17 00:00:00 2001 From: costabello matthieu Date: Wed, 1 Oct 2025 11:41:38 -0400 Subject: [PATCH 1/3] Add support for Cookie Parameter style --- examples/CookieParameterStyleExample.md | 106 ++++++++ .../Models/OpenApiParameter.cs | 16 +- .../Models/ParameterStyle.cs | 7 +- ...sync_produceTerseOutput=False.verified.txt | 9 + ...Async_produceTerseOutput=True.verified.txt | 1 + .../OpenApiParameterCookieStyleTests.cs | 251 ++++++++++++++++++ .../PublicApi/PublicApi.approved.txt | 2 + ...arameterCookieStyleDeserializationTests.cs | 196 ++++++++++++++ 8 files changed, 585 insertions(+), 3 deletions(-) create mode 100644 examples/CookieParameterStyleExample.md create mode 100644 test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=False.verified.txt create mode 100644 test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=True.verified.txt create mode 100644 test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs create mode 100644 test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs diff --git a/examples/CookieParameterStyleExample.md b/examples/CookieParameterStyleExample.md new file mode 100644 index 000000000..0df844df3 --- /dev/null +++ b/examples/CookieParameterStyleExample.md @@ -0,0 +1,106 @@ +# Cookie Parameter Style Example + +This example demonstrates the new `cookie` parameter style introduced in OpenAPI 3.2. + +## Example OpenAPI 3.2 Document + +```yaml +openapi: 3.2.0 +info: + title: Cookie Parameter Example API + version: 1.0.0 +paths: + /secure-data: + get: + summary: Get secure data using cookie authentication + parameters: + - name: sessionToken + in: cookie + style: cookie + required: true + description: Session authentication token + schema: + type: string + - name: userPreferences + in: cookie + # Note: style defaults to "form" for cookie parameters when not specified + description: User preference settings + schema: + type: string + responses: + '200': + description: Secure data retrieved successfully + '401': + description: Unauthorized - invalid session token +``` + +## Code Examples + +### Creating a Cookie Parameter in C# + +```csharp +// Create a cookie parameter with explicit cookie style +var cookieParameter = new OpenApiParameter +{ + Name = "sessionToken", + In = ParameterLocation.Cookie, + Style = ParameterStyle.Cookie, // New in OpenAPI 3.2 + Required = true, + Description = "Session authentication token", + Schema = new OpenApiSchema { Type = JsonSchemaType.String } +}; + +// Create a cookie parameter with default form style +var preferencesParameter = new OpenApiParameter +{ + Name = "userPreferences", + In = ParameterLocation.Cookie, + // Style will default to ParameterStyle.Form for cookie location + Description = "User preference settings", + Schema = new OpenApiSchema { Type = JsonSchemaType.String } +}; +``` + +### Serializing to Different OpenAPI Versions + +```csharp +// Serialize to OpenAPI 3.2 - Cookie style is supported +var v32Json = await cookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); +// Result: includes "style": "cookie" + +// Attempting to serialize to earlier versions will throw an exception +try +{ + var v31Json = await cookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); +} +catch (OpenApiException ex) +{ + // Exception: "Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions" +} + +// However, using default style (form) works in all versions +var v31JsonDefault = await preferencesParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); +// Works fine - uses default form style +``` + +### Version Compatibility + +| Parameter Style | OpenAPI 2.0 | OpenAPI 3.0 | OpenAPI 3.1 | OpenAPI 3.2+ | +|-----------------|--------------|--------------|--------------|---------------| +| `cookie` | ? Error | ? Error | ? Error | ? Supported | +| `form` (default)| ? Supported | ? Supported | ? Supported | ? Supported | + +### Default Behavior + +- **Default Style**: Cookie parameters default to `form` style when not explicitly specified +- **Default Explode**: Cookie style parameters default to `explode: true` (same as form style) +- **Style Omission**: When a cookie parameter uses the default `form` style, the `style` property is not serialized to avoid redundancy + +### Key Features + +1. **Version Validation**: Automatic validation prevents using `cookie` style in unsupported OpenAPI versions +2. **Automatic Deserialization**: The library automatically recognizes and deserializes `cookie` style parameters +3. **Default Handling**: Smart default behavior ensures compatibility and reduces verbosity +4. **Round-trip Serialization**: Full support for serializing and deserializing cookie style parameters + +This implementation follows the OpenAPI 3.2 specification while maintaining backward compatibility and providing clear error messages when version constraints are violated. \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index f1300f027..4f9bb1e34 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -44,7 +44,7 @@ public ParameterStyle? Style /// public bool Explode { - get => _explode ?? Style == ParameterStyle.Form; + get => _explode ?? (Style is ParameterStyle.Form or ParameterStyle.Cookie); set => _explode = value; } @@ -115,6 +115,12 @@ internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion versio Action callback) { Utils.CheckArgumentNull(writer); + + // Validate that Cookie style is only used in OpenAPI 3.2 and later + if (Style == ParameterStyle.Cookie && version < OpenApiSpecVersion.OpenApi3_2) + { + throw new OpenApiException($"Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions. Current version: {version}"); + } writer.WriteStartObject(); @@ -143,7 +149,7 @@ internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion versio } // explode - writer.WriteProperty(OpenApiConstants.Explode, _explode, Style is ParameterStyle.Form); + writer.WriteProperty(OpenApiConstants.Explode, _explode, Style is ParameterStyle.Form or ParameterStyle.Cookie); // allowReserved writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); @@ -251,6 +257,12 @@ internal virtual void WriteRequestBodySchemaForV2(IOpenApiWriter writer, Diction public virtual void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); + + // Validate that Cookie style is only used in OpenAPI 3.2 and later + if (Style == ParameterStyle.Cookie) + { + throw new OpenApiException($"Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions. Current version: {OpenApiSpecVersion.OpenApi2_0}"); + } writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Models/ParameterStyle.cs b/src/Microsoft.OpenApi/Models/ParameterStyle.cs index 3edb049a5..f948fc6fb 100644 --- a/src/Microsoft.OpenApi/Models/ParameterStyle.cs +++ b/src/Microsoft.OpenApi/Models/ParameterStyle.cs @@ -41,6 +41,11 @@ public enum ParameterStyle /// /// Provides a simple way of rendering nested objects using form parameters. /// - [Display("deepObject")] DeepObject + [Display("deepObject")] DeepObject, + + /// + /// Cookie style parameters. Introduced in OpenAPI 3.2. + /// + [Display("cookie")] Cookie } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..7e87bc43e --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,9 @@ +{ + "name": "sessionId", + "in": "cookie", + "description": "Session identifier stored in cookie", + "style": "cookie", + "schema": { + "type": "string" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..f2b086e6b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.SerializeCookieParameterAsV32JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"name":"sessionId","in":"cookie","description":"Session identifier stored in cookie","style":"cookie","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs new file mode 100644 index 000000000..a0b89c9f8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using Xunit; +using VerifyXunit; + +namespace Microsoft.OpenApi.Tests.Models +{ + [Collection("DefaultSettings")] + public class OpenApiParameterCookieStyleTests + { + private static OpenApiParameter CookieParameter => new() + { + Name = "sessionId", + In = ParameterLocation.Cookie, + Style = ParameterStyle.Cookie, + Description = "Session identifier stored in cookie", + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.String + } + }; + + private static OpenApiParameter CookieParameterWithDefault => new() + { + Name = "preferences", + In = ParameterLocation.Cookie, + Description = "User preferences stored in cookie", + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.String + } + }; + + [Fact] + public void CookieParameterStyleIsAvailable() + { + // Arrange & Act + var parameter = CookieParameter; + + // Assert + Assert.Equal(ParameterStyle.Cookie, parameter.Style); + Assert.Equal(ParameterLocation.Cookie, parameter.In); + } + + [Fact] + public void CookieParameterHasCorrectDefaultStyle() + { + // Arrange & Act + var parameter = CookieParameterWithDefault; + + // Assert + Assert.Equal(ParameterStyle.Form, parameter.Style); // Default for cookie location should be Form + } + + [Fact] + public void CookieParameterStyleDisplayNameIsCookie() + { + // Arrange & Act + var displayName = ParameterStyle.Cookie.GetDisplayName(); + + // Assert + Assert.Equal("cookie", displayName); + } + + [Fact] + public async Task SerializeCookieParameterAsV32JsonWorks() + { + // Arrange + var expected = + """ + { + "name": "sessionId", + "in": "cookie", + "description": "Session identifier stored in cookie", + "style": "cookie", + "schema": { + "type": "string" + } + } + """; + + // Act + var actual = await CookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task SerializeCookieParameterWithDefaultStyleAsV32JsonWorks() + { + // Arrange + var expected = + """ + { + "name": "preferences", + "in": "cookie", + "description": "User preferences stored in cookie", + "schema": { + "type": "string" + } + } + """; + + // Act + var actual = await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + public void SerializeCookieParameterStyleThrowsForEarlierVersions(OpenApiSpecVersion version) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter); + + // Act & Assert + var exception = Assert.Throws(() => + { + switch (version) + { + case OpenApiSpecVersion.OpenApi2_0: + CookieParameter.SerializeAsV2(writer); + break; + case OpenApiSpecVersion.OpenApi3_0: + CookieParameter.SerializeAsV3(writer); + break; + case OpenApiSpecVersion.OpenApi3_1: + CookieParameter.SerializeAsV31(writer); + break; + } + }); + + Assert.Contains("Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions", exception.Message); + Assert.Contains($"Current version: {version}", exception.Message); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + public async Task SerializeCookieParameterWithDefaultStyleWorksForEarlierVersions(OpenApiSpecVersion version) + { + // Arrange & Act + string actual = version switch + { + OpenApiSpecVersion.OpenApi2_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0), + OpenApiSpecVersion.OpenApi3_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0), + OpenApiSpecVersion.OpenApi3_1 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1), + _ => throw new ArgumentOutOfRangeException() + }; + + // Assert - Should not throw because default style (Form) is being used + Assert.NotEmpty(actual); + Assert.DoesNotContain("\"style\"", actual); // Style should not be emitted when it's the default + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeCookieParameterAsV32JsonWorksAsync(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + + // Act + CookieParameter.SerializeAsV32(writer); + await writer.FlushAsync(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Fact] + public void CookieParameterStyleEnumValueExists() + { + // Arrange & Act + var cookieStyleExists = Enum.IsDefined(typeof(ParameterStyle), ParameterStyle.Cookie); + + // Assert + Assert.True(cookieStyleExists); + } + + [Fact] + public void CookieParameterStyleCanBeDeserialized() + { + // Arrange + var cookieStyleString = "cookie"; + + // Act + var success = cookieStyleString.TryGetEnumFromDisplayName(out var parameterStyle); + + // Assert + Assert.True(success); + Assert.Equal(ParameterStyle.Cookie, parameterStyle); + } + + [Fact] + public async Task SerializeCookieParameterAsYamlV32Works() + { + // Arrange + var expected = """ + name: sessionId + in: cookie + description: Session identifier stored in cookie + style: cookie + schema: + type: string + """; + + // Act + var actual = await CookieParameter.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); + } + + [Theory] + [InlineData(ParameterStyle.Form, true)] + [InlineData(ParameterStyle.SpaceDelimited, false)] + [InlineData(ParameterStyle.Cookie, true)] + public void WhenStyleIsFormOrCookieTheDefaultValueOfExplodeShouldBeTrueOtherwiseFalse(ParameterStyle? style, bool expectedExplode) + { + // Arrange + var parameter = new OpenApiParameter + { + Name = "name1", + In = ParameterLocation.Query, + Style = style + }; + + // Act & Assert + Assert.Equal(expectedExplode, parameter.Explode); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index a0deaacfb..e439d577e 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1733,6 +1733,8 @@ namespace Microsoft.OpenApi PipeDelimited = 5, [Microsoft.OpenApi.Display("deepObject")] DeepObject = 6, + [Microsoft.OpenApi.Display("cookie")] + Cookie = 7, } public sealed class PathExpression : Microsoft.OpenApi.SourceExpression { diff --git a/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs b/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs new file mode 100644 index 000000000..6a98dea42 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Reader +{ + [Collection("DefaultSettings")] + public class OpenApiParameterCookieStyleDeserializationTests + { + [Fact] + public void DeserializeCookieParameterStyleFromJsonWorks() + { + // Arrange + var json = """ + { + "openapi": "3.2.0", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "sessionId", + "in": "cookie", + "style": "cookie", + "description": "Session identifier stored in cookie", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + } + } + } + } + } + } + """; + + // Act + var result = OpenApiDocument.Parse(json, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.NotNull(result.Document); + Assert.Empty(result.Diagnostic.Errors); + + var parameter = result.Document.Paths["/test"].Operations[System.Net.Http.HttpMethod.Get].Parameters[0]; + Assert.Equal("sessionId", parameter.Name); + Assert.Equal(ParameterLocation.Cookie, parameter.In); + Assert.Equal(ParameterStyle.Cookie, parameter.Style); + } + + [Fact] + public void DeserializeCookieParameterWithDefaultStyleFromJsonWorks() + { + // Arrange + var json = """ + { + "openapi": "3.2.0", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "preferences", + "in": "cookie", + "description": "User preferences stored in cookie", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + } + } + } + } + } + } + """; + + // Act + var result = OpenApiDocument.Parse(json, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.NotNull(result.Document); + Assert.Empty(result.Diagnostic.Errors); + + var parameter = result.Document.Paths["/test"].Operations[System.Net.Http.HttpMethod.Get].Parameters[0]; + Assert.Equal("preferences", parameter.Name); + Assert.Equal(ParameterLocation.Cookie, parameter.In); + Assert.Equal(ParameterStyle.Form, parameter.Style); // Should default to Form for cookie location + } + + [Fact] + public void DeserializeCookieParameterStyleFromYamlWorks() + { + // Arrange + var yaml = """ + openapi: 3.2.0 + info: + title: Test API + version: 1.0.0 + paths: + /test: + get: + parameters: + - name: sessionId + in: cookie + style: cookie + description: Session identifier stored in cookie + schema: + type: string + responses: + '200': + description: Success + """; + + // Act + var result = OpenApiDocument.Parse(yaml, "yaml", SettingsFixture.ReaderSettings); + + // Assert + Assert.NotNull(result.Document); + Assert.Empty(result.Diagnostic.Errors); + + var parameter = result.Document.Paths["/test"].Operations[System.Net.Http.HttpMethod.Get].Parameters[0]; + Assert.Equal("sessionId", parameter.Name); + Assert.Equal(ParameterLocation.Cookie, parameter.In); + Assert.Equal(ParameterStyle.Cookie, parameter.Style); + } + + [Fact] + public async Task SerializeAndDeserializeCookieParameterRoundTrip() + { + // Arrange + var original = new OpenApiParameter + { + Name = "trackingId", + In = ParameterLocation.Cookie, + Style = ParameterStyle.Cookie, + Description = "Tracking identifier", + Schema = new OpenApiSchema { Type = JsonSchemaType.String } + }; + + var document = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0.0" }, + Paths = new OpenApiPaths + { + ["/test"] = new OpenApiPathItem + { + Operations = new() + { + [System.Net.Http.HttpMethod.Get] = new OpenApiOperation + { + Parameters = new[] { original }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { Description = "Success" } + } + } + } + } + } + }; + + // Act + var json = await document.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var result = OpenApiDocument.Parse(json, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.NotNull(result.Document); + Assert.Empty(result.Diagnostic.Errors); + + var parameter = result.Document.Paths["/test"].Operations[System.Net.Http.HttpMethod.Get].Parameters[0]; + Assert.Equal(original.Name, parameter.Name); + Assert.Equal(original.In, parameter.In); + Assert.Equal(original.Style, parameter.Style); + Assert.Equal(original.Description, parameter.Description); + } + } +} From ecfa0c78c14cddbce5bba69353f8d0111b850d1d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 1 Oct 2025 21:23:35 -0400 Subject: [PATCH 2/3] chore: removes extraneous file --- examples/CookieParameterStyleExample.md | 106 ------------------------ 1 file changed, 106 deletions(-) delete mode 100644 examples/CookieParameterStyleExample.md diff --git a/examples/CookieParameterStyleExample.md b/examples/CookieParameterStyleExample.md deleted file mode 100644 index 0df844df3..000000000 --- a/examples/CookieParameterStyleExample.md +++ /dev/null @@ -1,106 +0,0 @@ -# Cookie Parameter Style Example - -This example demonstrates the new `cookie` parameter style introduced in OpenAPI 3.2. - -## Example OpenAPI 3.2 Document - -```yaml -openapi: 3.2.0 -info: - title: Cookie Parameter Example API - version: 1.0.0 -paths: - /secure-data: - get: - summary: Get secure data using cookie authentication - parameters: - - name: sessionToken - in: cookie - style: cookie - required: true - description: Session authentication token - schema: - type: string - - name: userPreferences - in: cookie - # Note: style defaults to "form" for cookie parameters when not specified - description: User preference settings - schema: - type: string - responses: - '200': - description: Secure data retrieved successfully - '401': - description: Unauthorized - invalid session token -``` - -## Code Examples - -### Creating a Cookie Parameter in C# - -```csharp -// Create a cookie parameter with explicit cookie style -var cookieParameter = new OpenApiParameter -{ - Name = "sessionToken", - In = ParameterLocation.Cookie, - Style = ParameterStyle.Cookie, // New in OpenAPI 3.2 - Required = true, - Description = "Session authentication token", - Schema = new OpenApiSchema { Type = JsonSchemaType.String } -}; - -// Create a cookie parameter with default form style -var preferencesParameter = new OpenApiParameter -{ - Name = "userPreferences", - In = ParameterLocation.Cookie, - // Style will default to ParameterStyle.Form for cookie location - Description = "User preference settings", - Schema = new OpenApiSchema { Type = JsonSchemaType.String } -}; -``` - -### Serializing to Different OpenAPI Versions - -```csharp -// Serialize to OpenAPI 3.2 - Cookie style is supported -var v32Json = await cookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); -// Result: includes "style": "cookie" - -// Attempting to serialize to earlier versions will throw an exception -try -{ - var v31Json = await cookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); -} -catch (OpenApiException ex) -{ - // Exception: "Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions" -} - -// However, using default style (form) works in all versions -var v31JsonDefault = await preferencesParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); -// Works fine - uses default form style -``` - -### Version Compatibility - -| Parameter Style | OpenAPI 2.0 | OpenAPI 3.0 | OpenAPI 3.1 | OpenAPI 3.2+ | -|-----------------|--------------|--------------|--------------|---------------| -| `cookie` | ? Error | ? Error | ? Error | ? Supported | -| `form` (default)| ? Supported | ? Supported | ? Supported | ? Supported | - -### Default Behavior - -- **Default Style**: Cookie parameters default to `form` style when not explicitly specified -- **Default Explode**: Cookie style parameters default to `explode: true` (same as form style) -- **Style Omission**: When a cookie parameter uses the default `form` style, the `style` property is not serialized to avoid redundancy - -### Key Features - -1. **Version Validation**: Automatic validation prevents using `cookie` style in unsupported OpenAPI versions -2. **Automatic Deserialization**: The library automatically recognizes and deserializes `cookie` style parameters -3. **Default Handling**: Smart default behavior ensures compatibility and reduces verbosity -4. **Round-trip Serialization**: Full support for serializing and deserializing cookie style parameters - -This implementation follows the OpenAPI 3.2 specification while maintaining backward compatibility and providing clear error messages when version constraints are violated. \ No newline at end of file From 3ec086fb07e0ab3e65664bf04987f2262c675f10 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 1 Oct 2025 21:24:36 -0400 Subject: [PATCH 3/3] Apply suggestions from code review --- .../Models/OpenApiParameterCookieStyleTests.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs index a0b89c9f8..cf0348fa9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.IO; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Xunit; using VerifyXunit; @@ -88,9 +89,7 @@ public async Task SerializeCookieParameterAsV32JsonWorks() var actual = await CookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } [Fact] @@ -113,9 +112,7 @@ public async Task SerializeCookieParameterWithDefaultStyleAsV32JsonWorks() var actual = await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } [Theory]