Skip to content

Commit ec9b962

Browse files
authored
Merge pull request #35 from BinkyLabs/feat/6-Add-support-for-Cookie-Parameter-style
feat: Add support for Cookie Parameter style
2 parents ced60c9 + 3ec086f commit ec9b962

7 files changed

+476
-3
lines changed

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public ParameterStyle? Style
4444
/// <inheritdoc/>
4545
public bool Explode
4646
{
47-
get => _explode ?? Style == ParameterStyle.Form;
47+
get => _explode ?? (Style is ParameterStyle.Form or ParameterStyle.Cookie);
4848
set => _explode = value;
4949
}
5050

@@ -115,6 +115,12 @@ internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion versio
115115
Action<IOpenApiWriter, IOpenApiSerializable> callback)
116116
{
117117
Utils.CheckArgumentNull(writer);
118+
119+
// Validate that Cookie style is only used in OpenAPI 3.2 and later
120+
if (Style == ParameterStyle.Cookie && version < OpenApiSpecVersion.OpenApi3_2)
121+
{
122+
throw new OpenApiException($"Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions. Current version: {version}");
123+
}
118124

119125
writer.WriteStartObject();
120126

@@ -143,7 +149,7 @@ internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion versio
143149
}
144150

145151
// explode
146-
writer.WriteProperty(OpenApiConstants.Explode, _explode, Style is ParameterStyle.Form);
152+
writer.WriteProperty(OpenApiConstants.Explode, _explode, Style is ParameterStyle.Form or ParameterStyle.Cookie);
147153

148154
// allowReserved
149155
writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false);
@@ -251,6 +257,12 @@ internal virtual void WriteRequestBodySchemaForV2(IOpenApiWriter writer, Diction
251257
public virtual void SerializeAsV2(IOpenApiWriter writer)
252258
{
253259
Utils.CheckArgumentNull(writer);
260+
261+
// Validate that Cookie style is only used in OpenAPI 3.2 and later
262+
if (Style == ParameterStyle.Cookie)
263+
{
264+
throw new OpenApiException($"Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions. Current version: {OpenApiSpecVersion.OpenApi2_0}");
265+
}
254266

255267
writer.WriteStartObject();
256268

src/Microsoft.OpenApi/Models/ParameterStyle.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ public enum ParameterStyle
4141
/// <summary>
4242
/// Provides a simple way of rendering nested objects using form parameters.
4343
/// </summary>
44-
[Display("deepObject")] DeepObject
44+
[Display("deepObject")] DeepObject,
45+
46+
/// <summary>
47+
/// Cookie style parameters. Introduced in OpenAPI 3.2.
48+
/// </summary>
49+
[Display("cookie")] Cookie
4550
}
4651
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "sessionId",
3+
"in": "cookie",
4+
"description": "Session identifier stored in cookie",
5+
"style": "cookie",
6+
"schema": {
7+
"type": "string"
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"sessionId","in":"cookie","description":"Session identifier stored in cookie","style":"cookie","schema":{"type":"string"}}
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Globalization;
6+
using System.IO;
7+
using System.Text.Json.Nodes;
8+
using System.Threading.Tasks;
9+
using Xunit;
10+
using VerifyXunit;
11+
12+
namespace Microsoft.OpenApi.Tests.Models
13+
{
14+
[Collection("DefaultSettings")]
15+
public class OpenApiParameterCookieStyleTests
16+
{
17+
private static OpenApiParameter CookieParameter => new()
18+
{
19+
Name = "sessionId",
20+
In = ParameterLocation.Cookie,
21+
Style = ParameterStyle.Cookie,
22+
Description = "Session identifier stored in cookie",
23+
Schema = new OpenApiSchema()
24+
{
25+
Type = JsonSchemaType.String
26+
}
27+
};
28+
29+
private static OpenApiParameter CookieParameterWithDefault => new()
30+
{
31+
Name = "preferences",
32+
In = ParameterLocation.Cookie,
33+
Description = "User preferences stored in cookie",
34+
Schema = new OpenApiSchema()
35+
{
36+
Type = JsonSchemaType.String
37+
}
38+
};
39+
40+
[Fact]
41+
public void CookieParameterStyleIsAvailable()
42+
{
43+
// Arrange & Act
44+
var parameter = CookieParameter;
45+
46+
// Assert
47+
Assert.Equal(ParameterStyle.Cookie, parameter.Style);
48+
Assert.Equal(ParameterLocation.Cookie, parameter.In);
49+
}
50+
51+
[Fact]
52+
public void CookieParameterHasCorrectDefaultStyle()
53+
{
54+
// Arrange & Act
55+
var parameter = CookieParameterWithDefault;
56+
57+
// Assert
58+
Assert.Equal(ParameterStyle.Form, parameter.Style); // Default for cookie location should be Form
59+
}
60+
61+
[Fact]
62+
public void CookieParameterStyleDisplayNameIsCookie()
63+
{
64+
// Arrange & Act
65+
var displayName = ParameterStyle.Cookie.GetDisplayName();
66+
67+
// Assert
68+
Assert.Equal("cookie", displayName);
69+
}
70+
71+
[Fact]
72+
public async Task SerializeCookieParameterAsV32JsonWorks()
73+
{
74+
// Arrange
75+
var expected =
76+
"""
77+
{
78+
"name": "sessionId",
79+
"in": "cookie",
80+
"description": "Session identifier stored in cookie",
81+
"style": "cookie",
82+
"schema": {
83+
"type": "string"
84+
}
85+
}
86+
""";
87+
88+
// Act
89+
var actual = await CookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
90+
91+
// Assert
92+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)));
93+
}
94+
95+
[Fact]
96+
public async Task SerializeCookieParameterWithDefaultStyleAsV32JsonWorks()
97+
{
98+
// Arrange
99+
var expected =
100+
"""
101+
{
102+
"name": "preferences",
103+
"in": "cookie",
104+
"description": "User preferences stored in cookie",
105+
"schema": {
106+
"type": "string"
107+
}
108+
}
109+
""";
110+
111+
// Act
112+
var actual = await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
113+
114+
// Assert
115+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)));
116+
}
117+
118+
[Theory]
119+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
120+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
121+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
122+
public void SerializeCookieParameterStyleThrowsForEarlierVersions(OpenApiSpecVersion version)
123+
{
124+
// Arrange
125+
var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
126+
var writer = new OpenApiJsonWriter(outputStringWriter);
127+
128+
// Act & Assert
129+
var exception = Assert.Throws<OpenApiException>(() =>
130+
{
131+
switch (version)
132+
{
133+
case OpenApiSpecVersion.OpenApi2_0:
134+
CookieParameter.SerializeAsV2(writer);
135+
break;
136+
case OpenApiSpecVersion.OpenApi3_0:
137+
CookieParameter.SerializeAsV3(writer);
138+
break;
139+
case OpenApiSpecVersion.OpenApi3_1:
140+
CookieParameter.SerializeAsV31(writer);
141+
break;
142+
}
143+
});
144+
145+
Assert.Contains("Parameter style 'cookie' is only supported in OpenAPI 3.2 and later versions", exception.Message);
146+
Assert.Contains($"Current version: {version}", exception.Message);
147+
}
148+
149+
[Theory]
150+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
151+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
152+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
153+
public async Task SerializeCookieParameterWithDefaultStyleWorksForEarlierVersions(OpenApiSpecVersion version)
154+
{
155+
// Arrange & Act
156+
string actual = version switch
157+
{
158+
OpenApiSpecVersion.OpenApi2_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0),
159+
OpenApiSpecVersion.OpenApi3_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0),
160+
OpenApiSpecVersion.OpenApi3_1 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1),
161+
_ => throw new ArgumentOutOfRangeException()
162+
};
163+
164+
// Assert - Should not throw because default style (Form) is being used
165+
Assert.NotEmpty(actual);
166+
Assert.DoesNotContain("\"style\"", actual); // Style should not be emitted when it's the default
167+
}
168+
169+
[Theory]
170+
[InlineData(true)]
171+
[InlineData(false)]
172+
public async Task SerializeCookieParameterAsV32JsonWorksAsync(bool produceTerseOutput)
173+
{
174+
// Arrange
175+
var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
176+
var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput });
177+
178+
// Act
179+
CookieParameter.SerializeAsV32(writer);
180+
await writer.FlushAsync();
181+
182+
// Assert
183+
await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput);
184+
}
185+
186+
[Fact]
187+
public void CookieParameterStyleEnumValueExists()
188+
{
189+
// Arrange & Act
190+
var cookieStyleExists = Enum.IsDefined(typeof(ParameterStyle), ParameterStyle.Cookie);
191+
192+
// Assert
193+
Assert.True(cookieStyleExists);
194+
}
195+
196+
[Fact]
197+
public void CookieParameterStyleCanBeDeserialized()
198+
{
199+
// Arrange
200+
var cookieStyleString = "cookie";
201+
202+
// Act
203+
var success = cookieStyleString.TryGetEnumFromDisplayName<ParameterStyle>(out var parameterStyle);
204+
205+
// Assert
206+
Assert.True(success);
207+
Assert.Equal(ParameterStyle.Cookie, parameterStyle);
208+
}
209+
210+
[Fact]
211+
public async Task SerializeCookieParameterAsYamlV32Works()
212+
{
213+
// Arrange
214+
var expected = """
215+
name: sessionId
216+
in: cookie
217+
description: Session identifier stored in cookie
218+
style: cookie
219+
schema:
220+
type: string
221+
""";
222+
223+
// Act
224+
var actual = await CookieParameter.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2);
225+
226+
// Assert
227+
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
228+
}
229+
230+
[Theory]
231+
[InlineData(ParameterStyle.Form, true)]
232+
[InlineData(ParameterStyle.SpaceDelimited, false)]
233+
[InlineData(ParameterStyle.Cookie, true)]
234+
public void WhenStyleIsFormOrCookieTheDefaultValueOfExplodeShouldBeTrueOtherwiseFalse(ParameterStyle? style, bool expectedExplode)
235+
{
236+
// Arrange
237+
var parameter = new OpenApiParameter
238+
{
239+
Name = "name1",
240+
In = ParameterLocation.Query,
241+
Style = style
242+
};
243+
244+
// Act & Assert
245+
Assert.Equal(expectedExplode, parameter.Explode);
246+
}
247+
}
248+
}

test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,8 @@ namespace Microsoft.OpenApi
17531753
PipeDelimited = 5,
17541754
[Microsoft.OpenApi.Display("deepObject")]
17551755
DeepObject = 6,
1756+
[Microsoft.OpenApi.Display("cookie")]
1757+
Cookie = 7,
17561758
}
17571759
public sealed class PathExpression : Microsoft.OpenApi.SourceExpression
17581760
{

0 commit comments

Comments
 (0)