Skip to content

Commit cab2dcf

Browse files
committed
fix(models): deep copy Servers and Variables in copy constructors
1 parent eb6ec97 commit cab2dcf

6 files changed

Lines changed: 85 additions & 7 deletions

File tree

‎src/Microsoft.OpenApi/Models/OpenApiDocument.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System;
@@ -143,7 +143,7 @@ public OpenApiDocument(OpenApiDocument? document)
143143
Info = document?.Info != null ? new(document.Info) : new OpenApiInfo();
144144
JsonSchemaDialect = document?.JsonSchemaDialect ?? JsonSchemaDialect;
145145
Self = document?.Self ?? Self;
146-
Servers = document?.Servers != null ? [.. document.Servers] : null;
146+
Servers = document?.Servers != null ? [.. document.Servers.Select(static s => new OpenApiServer(s))] : null;
147147
Paths = document?.Paths != null ? new(document.Paths) : [];
148148
Webhooks = document?.Webhooks != null ? new Dictionary<string, IOpenApiPathItem>(document.Webhooks) : null;
149149
Components = document?.Components != null ? new(document?.Components) : null;

‎src/Microsoft.OpenApi/Models/OpenApiOperation.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public OpenApiOperation(OpenApiOperation operation)
151151
Callbacks = operation.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(operation.Callbacks) : null;
152152
Deprecated = operation.Deprecated;
153153
Security = operation.Security != null ? [.. operation.Security] : null;
154-
Servers = operation.Servers != null ? [.. operation.Servers] : null;
154+
Servers = operation.Servers != null ? [.. operation.Servers.Select(static s => new OpenApiServer(s))] : null;
155155
Extensions = operation.Extensions != null ? new Dictionary<string, IOpenApiExtension>(operation.Extensions) : null;
156156
Metadata = operation.Metadata != null ? new Dictionary<string, object>(operation.Metadata) : null;
157157
}

‎src/Microsoft.OpenApi/Models/OpenApiServer.cs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
67

78
namespace Microsoft.OpenApi
89
{
@@ -52,7 +53,7 @@ public OpenApiServer(OpenApiServer server)
5253
Description = server?.Description ?? Description;
5354
Name = server?.Name ?? Name;
5455
Url = server?.Url ?? Url;
55-
Variables = server?.Variables != null ? new Dictionary<string, OpenApiServerVariable>(server.Variables) : null;
56+
Variables = server?.Variables != null ? server.Variables.ToDictionary(static kvp => kvp.Key, static kvp => new OpenApiServerVariable(kvp.Value)) : null;
5657
Extensions = server?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(server.Extensions) : null;
5758
}
5859

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System;
@@ -2753,5 +2753,28 @@ public void GetOperationById_ThrowsOnNullOrEmptyId()
27532753
Assert.Throws<ArgumentNullException>(() => doc.GetOperationById(null!));
27542754
Assert.Throws<ArgumentNullException>(() => doc.GetOperationById(string.Empty));
27552755
}
2756+
2757+
[Fact]
2758+
public void OpenApiDocumentCopyConstructorDeepCopiesServers()
2759+
{
2760+
var document = new OpenApiDocument
2761+
{
2762+
Servers =
2763+
[
2764+
new OpenApiServer
2765+
{
2766+
Url = "https://api.example.com",
2767+
Description = "Production server"
2768+
}
2769+
]
2770+
};
2771+
2772+
var copy = new OpenApiDocument(document);
2773+
Assert.NotNull(copy.Servers);
2774+
Assert.NotSame(document.Servers[0], copy.Servers[0]);
2775+
2776+
copy.Servers[0].Url = "https://staging.example.com";
2777+
Assert.Equal("https://api.example.com", document.Servers[0].Url);
2778+
}
27562779
}
27572780
}

‎test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,5 +995,28 @@ public void OpenApiOperationCopyConstructorPreservesImmutableSortedSet()
995995
var copyNames = copy.Tags.Select(t => t.Name);
996996
Assert.Equal(["tagC", "tagB", "tagA"], copyNames);
997997
}
998+
999+
[Fact]
1000+
public void OpenApiOperationCopyConstructorDeepCopiesServers()
1001+
{
1002+
var operation = new OpenApiOperation
1003+
{
1004+
Servers =
1005+
[
1006+
new OpenApiServer
1007+
{
1008+
Url = "https://api.example.com/v1",
1009+
Description = "Operation server"
1010+
}
1011+
]
1012+
};
1013+
1014+
var copy = new OpenApiOperation(operation);
1015+
Assert.NotNull(copy.Servers);
1016+
Assert.NotSame(operation.Servers[0], copy.Servers[0]);
1017+
1018+
copy.Servers[0].Url = "https://staging.example.com/v1";
1019+
Assert.Equal("https://api.example.com/v1", operation.Servers[0].Url);
1020+
}
9981021
}
9991022
}

‎test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System.Collections.Generic;
@@ -190,5 +190,36 @@ public void CopyConstructorHandlesNameField()
190190
Assert.Equal(original.Description, copy.Description);
191191
Assert.Equal(original.Url, copy.Url);
192192
}
193+
194+
[Fact]
195+
public void CopyConstructorDeepCopiesVariables()
196+
{
197+
// Arrange
198+
var original = new OpenApiServer
199+
{
200+
Url = "https://{username}.example.com:{port}",
201+
Variables = new Dictionary<string, OpenApiServerVariable>
202+
{
203+
["port"] = new()
204+
{
205+
Default = "8443",
206+
Enum = ["443", "8443"]
207+
}
208+
}
209+
};
210+
211+
// Act
212+
var copy = new OpenApiServer(original);
213+
214+
// Assert
215+
Assert.NotNull(copy.Variables);
216+
Assert.NotSame(original.Variables["port"], copy.Variables["port"]);
217+
Assert.NotSame(original.Variables["port"].Enum, copy.Variables["port"].Enum);
218+
219+
copy.Variables["port"].Default = "9443";
220+
copy.Variables["port"].Enum?.Add("9443");
221+
Assert.Equal("8443", original.Variables["port"].Default);
222+
Assert.DoesNotContain("9443", original.Variables["port"].Enum);
223+
}
193224
}
194225
}

0 commit comments

Comments
 (0)