Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
16 changes: 14 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public ParameterStyle? Style
/// <inheritdoc/>
public bool Explode
{
get => _explode ?? Style == ParameterStyle.Form;
get => _explode ?? (Style is ParameterStyle.Form or ParameterStyle.Cookie);
set => _explode = value;
}

Expand Down Expand Up @@ -115,6 +115,12 @@ internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion versio
Action<IOpenApiWriter, IOpenApiSerializable> 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();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.OpenApi/Models/ParameterStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public enum ParameterStyle
/// <summary>
/// Provides a simple way of rendering nested objects using form parameters.
/// </summary>
[Display("deepObject")] DeepObject
[Display("deepObject")] DeepObject,

/// <summary>
/// Cookie style parameters. Introduced in OpenAPI 3.2.
/// </summary>
[Display("cookie")] Cookie
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "sessionId",
"in": "cookie",
"description": "Session identifier stored in cookie",
"style": "cookie",
"schema": {
"type": "string"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"sessionId","in":"cookie","description":"Session identifier stored in cookie","style":"cookie","schema":{"type":"string"}}
Original file line number Diff line number Diff line change
@@ -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<OpenApiException>(() =>
{
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<ParameterStyle>(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);
}
}
}
2 changes: 2 additions & 0 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,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
{
Expand Down
Loading