Skip to content

Commit da0612c

Browse files
authored
Merge pull request #457 from ozziepeeps/456
Fixes #456.
2 parents 3c91a1d + d7c6bbf commit da0612c

File tree

2 files changed

+286
-2
lines changed

2 files changed

+286
-2
lines changed

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
252252
// deprecated
253253
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
254254

255+
var extensionsClone = new Dictionary<string, IOpenApiExtension>(Extensions);
256+
255257
// schema
256258
if (this is OpenApiBodyParameter)
257259
{
@@ -283,7 +285,20 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
283285
// uniqueItems
284286
// enum
285287
// multipleOf
286-
Schema?.WriteAsItemsProperties(writer);
288+
if (Schema != null)
289+
{
290+
Schema.WriteAsItemsProperties(writer);
291+
292+
if (Schema.Extensions != null)
293+
{
294+
foreach (var key in Schema.Extensions.Keys)
295+
{
296+
// The extension will already have been serialized as part of the call to WriteAsItemsProperties above,
297+
// so remove it from the cloned collection so we don't write it again.
298+
extensionsClone.Remove(key);
299+
}
300+
}
301+
}
287302

288303
// allowEmptyValue
289304
writer.WriteProperty(OpenApiConstants.AllowEmptyValue, AllowEmptyValue, false);
@@ -307,7 +322,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
307322

308323

309324
// extensions
310-
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);
325+
writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0);
311326

312327
writer.WriteEndObject();
313328
}

test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO;
88
using FluentAssertions;
99
using Microsoft.OpenApi.Extensions;
10+
using Microsoft.OpenApi.Interfaces;
1011
using Microsoft.OpenApi.Models;
1112
using Microsoft.OpenApi.Writers;
1213
using Xunit;
@@ -885,6 +886,95 @@ public class OpenApiDocumentTests
885886
Components = AdvancedComponents
886887
};
887888

889+
public static OpenApiDocument DuplicateExtensions = new OpenApiDocument
890+
{
891+
Info = new OpenApiInfo
892+
{
893+
Version = "1.0.0",
894+
Title = "Swagger Petstore (Simple)",
895+
Description = "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
896+
},
897+
Servers = new List<OpenApiServer>
898+
{
899+
new OpenApiServer
900+
{
901+
Url = "http://petstore.swagger.io/api"
902+
}
903+
},
904+
Paths = new OpenApiPaths
905+
{
906+
["/add/{operand1}/{operand2}"] = new OpenApiPathItem
907+
{
908+
Operations = new Dictionary<OperationType, OpenApiOperation>
909+
{
910+
[OperationType.Get] = new OpenApiOperation
911+
{
912+
OperationId = "addByOperand1AndByOperand2",
913+
Parameters = new List<OpenApiParameter>
914+
{
915+
new OpenApiParameter
916+
{
917+
Name = "operand1",
918+
In = ParameterLocation.Path,
919+
Description = "The first operand",
920+
Required = true,
921+
Schema = new OpenApiSchema
922+
{
923+
Type = "integer",
924+
Extensions = new Dictionary<string, IOpenApiExtension>
925+
{
926+
["my-extension"] = new Any.OpenApiInteger(4),
927+
}
928+
},
929+
Extensions = new Dictionary<string, IOpenApiExtension>
930+
{
931+
["my-extension"] = new Any.OpenApiInteger(4),
932+
}
933+
},
934+
new OpenApiParameter
935+
{
936+
Name = "operand2",
937+
In = ParameterLocation.Path,
938+
Description = "The second operand",
939+
Required = true,
940+
Schema = new OpenApiSchema
941+
{
942+
Type = "integer",
943+
Extensions = new Dictionary<string, IOpenApiExtension>
944+
{
945+
["my-extension"] = new Any.OpenApiInteger(4),
946+
}
947+
},
948+
Extensions = new Dictionary<string, IOpenApiExtension>
949+
{
950+
["my-extension"] = new Any.OpenApiInteger(4),
951+
}
952+
},
953+
},
954+
Responses = new OpenApiResponses
955+
{
956+
["200"] = new OpenApiResponse
957+
{
958+
Description = "pet response",
959+
Content = new Dictionary<string, OpenApiMediaType>
960+
{
961+
["application/json"] = new OpenApiMediaType
962+
{
963+
Schema = new OpenApiSchema
964+
{
965+
Type = "array",
966+
Items = PetSchema
967+
}
968+
},
969+
}
970+
}
971+
}
972+
}
973+
}
974+
}
975+
}
976+
};
977+
888978
private readonly ITestOutputHelper _output;
889979

890980
public OpenApiDocumentTests(ITestOutputHelper output)
@@ -2155,6 +2245,185 @@ public void SerializeAdvancedDocumentAsV2JsonWorks()
21552245
actual.Should().Be(expected);
21562246
}
21572247

2248+
[Fact]
2249+
public void SerializeDuplicateExtensionsAsV3JsonWorks()
2250+
{
2251+
// Arrange
2252+
var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
2253+
var writer = new OpenApiJsonWriter(outputStringWriter);
2254+
var expected = @"{
2255+
""openapi"": ""3.0.1"",
2256+
""info"": {
2257+
""title"": ""Swagger Petstore (Simple)"",
2258+
""description"": ""A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification"",
2259+
""version"": ""1.0.0""
2260+
},
2261+
""servers"": [
2262+
{
2263+
""url"": ""http://petstore.swagger.io/api""
2264+
}
2265+
],
2266+
""paths"": {
2267+
""/add/{operand1}/{operand2}"": {
2268+
""get"": {
2269+
""operationId"": ""addByOperand1AndByOperand2"",
2270+
""parameters"": [
2271+
{
2272+
""name"": ""operand1"",
2273+
""in"": ""path"",
2274+
""description"": ""The first operand"",
2275+
""required"": true,
2276+
""schema"": {
2277+
""type"": ""integer"",
2278+
""my-extension"": 4
2279+
},
2280+
""my-extension"": 4
2281+
},
2282+
{
2283+
""name"": ""operand2"",
2284+
""in"": ""path"",
2285+
""description"": ""The second operand"",
2286+
""required"": true,
2287+
""schema"": {
2288+
""type"": ""integer"",
2289+
""my-extension"": 4
2290+
},
2291+
""my-extension"": 4
2292+
}
2293+
],
2294+
""responses"": {
2295+
""200"": {
2296+
""description"": ""pet response"",
2297+
""content"": {
2298+
""application/json"": {
2299+
""schema"": {
2300+
""type"": ""array"",
2301+
""items"": {
2302+
""required"": [
2303+
""id"",
2304+
""name""
2305+
],
2306+
""type"": ""object"",
2307+
""properties"": {
2308+
""id"": {
2309+
""type"": ""integer"",
2310+
""format"": ""int64""
2311+
},
2312+
""name"": {
2313+
""type"": ""string""
2314+
},
2315+
""tag"": {
2316+
""type"": ""string""
2317+
}
2318+
}
2319+
}
2320+
}
2321+
}
2322+
}
2323+
}
2324+
}
2325+
}
2326+
}
2327+
}
2328+
}";
2329+
2330+
// Act
2331+
DuplicateExtensions.SerializeAsV3(writer);
2332+
writer.Flush();
2333+
var actual = outputStringWriter.GetStringBuilder().ToString();
2334+
2335+
// Assert
2336+
actual = actual.MakeLineBreaksEnvironmentNeutral();
2337+
expected = expected.MakeLineBreaksEnvironmentNeutral();
2338+
actual.Should().Be(expected);
2339+
}
2340+
2341+
[Fact]
2342+
public void SerializeDuplicateExtensionsAsV2JsonWorks()
2343+
{
2344+
// Arrange
2345+
var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
2346+
var writer = new OpenApiJsonWriter(outputStringWriter);
2347+
var expected = @"{
2348+
""swagger"": ""2.0"",
2349+
""info"": {
2350+
""title"": ""Swagger Petstore (Simple)"",
2351+
""description"": ""A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification"",
2352+
""version"": ""1.0.0""
2353+
},
2354+
""host"": ""petstore.swagger.io"",
2355+
""basePath"": ""/api"",
2356+
""schemes"": [
2357+
""http""
2358+
],
2359+
""paths"": {
2360+
""/add/{operand1}/{operand2}"": {
2361+
""get"": {
2362+
""operationId"": ""addByOperand1AndByOperand2"",
2363+
""produces"": [
2364+
""application/json""
2365+
],
2366+
""parameters"": [
2367+
{
2368+
""in"": ""path"",
2369+
""name"": ""operand1"",
2370+
""description"": ""The first operand"",
2371+
""required"": true,
2372+
""type"": ""integer"",
2373+
""my-extension"": 4
2374+
},
2375+
{
2376+
""in"": ""path"",
2377+
""name"": ""operand2"",
2378+
""description"": ""The second operand"",
2379+
""required"": true,
2380+
""type"": ""integer"",
2381+
""my-extension"": 4
2382+
}
2383+
],
2384+
""responses"": {
2385+
""200"": {
2386+
""description"": ""pet response"",
2387+
""schema"": {
2388+
""type"": ""array"",
2389+
""items"": {
2390+
""required"": [
2391+
""id"",
2392+
""name""
2393+
],
2394+
""type"": ""object"",
2395+
""properties"": {
2396+
""id"": {
2397+
""format"": ""int64"",
2398+
""type"": ""integer""
2399+
},
2400+
""name"": {
2401+
""type"": ""string""
2402+
},
2403+
""tag"": {
2404+
""type"": ""string""
2405+
}
2406+
}
2407+
}
2408+
}
2409+
}
2410+
}
2411+
}
2412+
}
2413+
}
2414+
}";
2415+
2416+
// Act
2417+
DuplicateExtensions.SerializeAsV2(writer);
2418+
writer.Flush();
2419+
var actual = outputStringWriter.GetStringBuilder().ToString();
2420+
2421+
// Assert
2422+
actual = actual.MakeLineBreaksEnvironmentNeutral();
2423+
expected = expected.MakeLineBreaksEnvironmentNeutral();
2424+
actual.Should().Be(expected);
2425+
}
2426+
21582427
[Fact]
21592428
public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks()
21602429
{

0 commit comments

Comments
 (0)