Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
20 changes: 20 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public class OpenApiServer : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public string? Description { get; set; }

/// <summary>
/// An optional string identifying the server. This MUST be unique across servers in the same document.
/// Note: This field is supported in OpenAPI 3.2.0+. For earlier versions, it will be serialized as x-oai-name extension.
/// </summary>
public string? Name { get; set; }

/// <summary>
/// REQUIRED. A URL to the target host. This URL supports Server Variables and MAY be relative,
/// to indicate that the host location is relative to the location where the OpenAPI document is being served.
Expand Down Expand Up @@ -44,6 +50,7 @@ public OpenApiServer() { }
public OpenApiServer(OpenApiServer server)
{
Description = server?.Description ?? Description;
Name = server?.Name ?? Name;
Url = server?.Url ?? Url;
Variables = server?.Variables != null ? new Dictionary<string, OpenApiServerVariable>(server.Variables) : null;
Extensions = server?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(server.Extensions) : null;
Expand Down Expand Up @@ -86,6 +93,19 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
// url
writer.WriteProperty(OpenApiConstants.Url, Url);

// name - serialize as native field for v3.2+ or as extension for earlier versions
if (!string.IsNullOrEmpty(Name))
{
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteProperty(OpenApiConstants.Name, Name);
}
else
{
writer.WriteProperty("x-oai-name", Name);
}
}

// description
writer.WriteProperty(OpenApiConstants.Description, Description);

Expand Down
12 changes: 11 additions & 1 deletion src/Microsoft.OpenApi/Reader/V3/OpenApiServerDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,17 @@ internal static partial class OpenApiV3Deserializer

private static readonly PatternFieldMap<OpenApiServer> _serverPatternFields = new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
{
if (p.Equals("x-oai-name", StringComparison.OrdinalIgnoreCase))
{
o.Name = n.GetScalarValue();
}
else
{
o.AddExtension(p, LoadExtension(p,n));
}
}}
};

public static OpenApiServer LoadServer(ParseNode node, OpenApiDocument hostDocument)
Expand Down
12 changes: 11 additions & 1 deletion src/Microsoft.OpenApi/Reader/V31/OpenApiServerDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,17 @@ internal static partial class OpenApiV31Deserializer

private static readonly PatternFieldMap<OpenApiServer> _serverPatternFields = new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
{
if (p.Equals("x-oai-name", StringComparison.OrdinalIgnoreCase))
{
o.Name = n.GetScalarValue();
}
else
{
o.AddExtension(p, LoadExtension(p,n));
}
}}
};

public static OpenApiServer LoadServer(ParseNode node, OpenApiDocument hostDocument)
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.OpenApi/Reader/V32/OpenApiServerDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ internal static partial class OpenApiV32Deserializer
o.Description = n.GetScalarValue();
}
},
{
"name", (o, n, _) =>
{
o.Name = n.GetScalarValue();
}
},
{
"variables", (o, n, t) =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.IO;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Reader.V31;
using Microsoft.OpenApi.YamlReader;
using SharpYaml.Serialization;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V31Tests
{
public class OpenApiServerTests
{
[Fact]
public void ParseServerWithXOaiNameExtensionShouldSucceed()
{
var input =
"""
url: https://dev.example.com
description: Development server
x-oai-name: dev-server
""";

var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(input));
var yamlNode = yamlStream.Documents[0].RootNode;

var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic);

var asJsonNode = yamlNode.ToJsonNode();
var node = new MapNode(context, asJsonNode);

// Act
var openApiServer = OpenApiV31Deserializer.LoadServer(node, new());

// Assert
Assert.Equal("https://dev.example.com", openApiServer.Url);
Assert.Equal("dev-server", openApiServer.Name);
Assert.Equal("Development server", openApiServer.Description);
// The x-oai-name extension should not be in the extensions collection since it's parsed to Name property
Assert.Null(openApiServer.Extensions);
}

[Fact]
public void ParseServerWithOtherExtensionShouldKeepExtension()
{
var input =
"""
url: https://example.com
description: Sample server
x-custom-extension: custom-value
""";

var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(input));
var yamlNode = yamlStream.Documents[0].RootNode;

var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic);

var asJsonNode = yamlNode.ToJsonNode();
var node = new MapNode(context, asJsonNode);

// Act
var openApiServer = OpenApiV31Deserializer.LoadServer(node, new());

// Assert
Assert.Equal("https://example.com", openApiServer.Url);
Assert.Null(openApiServer.Name);
Assert.Equal("Sample server", openApiServer.Description);
Assert.NotNull(openApiServer.Extensions);
Assert.Single(openApiServer.Extensions);
Assert.True(openApiServer.Extensions.ContainsKey("x-custom-extension"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.IO;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Reader.V32;
using Microsoft.OpenApi.YamlReader;
using SharpYaml.Serialization;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V32Tests
{
public class OpenApiServerTests
{
[Fact]
public void ParseServerWithNameShouldSucceed()
{
var input =
"""
url: https://dev.example.com
name: dev-server
description: Development server
""";

var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(input));
var yamlNode = yamlStream.Documents[0].RootNode;

var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic);

var asJsonNode = yamlNode.ToJsonNode();
var node = new MapNode(context, asJsonNode);

// Act
var openApiServer = OpenApiV32Deserializer.LoadServer(node, new());

// Assert
Assert.Equal("https://dev.example.com", openApiServer.Url);
Assert.Equal("dev-server", openApiServer.Name);
Assert.Equal("Development server", openApiServer.Description);
}

[Fact]
public void ParseServerWithoutNameShouldSucceed()
{
var input =
"""
url: https://example.com
description: Sample server
""";

var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(input));
var yamlNode = yamlStream.Documents[0].RootNode;

var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic);

var asJsonNode = yamlNode.ToJsonNode();
var node = new MapNode(context, asJsonNode);

// Act
var openApiServer = OpenApiV32Deserializer.LoadServer(node, new());

// Assert
Assert.Equal("https://example.com", openApiServer.Url);
Assert.Null(openApiServer.Name);
Assert.Equal("Sample server", openApiServer.Description);
}
}
}
75 changes: 75 additions & 0 deletions test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiServerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.IO;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Reader.V3;
using Microsoft.OpenApi.YamlReader;
using SharpYaml.Serialization;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V3Tests
{
public class OpenApiServerTests
{
[Fact]
public void ParseServerWithXOaiNameExtensionShouldSucceed()
{
var input =
"""
url: https://dev.example.com
description: Development server
x-oai-name: dev-server
""";

var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(input));
var yamlNode = yamlStream.Documents[0].RootNode;

var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic);

var asJsonNode = yamlNode.ToJsonNode();
var node = new MapNode(context, asJsonNode);

// Act
var openApiServer = OpenApiV3Deserializer.LoadServer(node, new());

// Assert
Assert.Equal("https://dev.example.com", openApiServer.Url);
Assert.Equal("dev-server", openApiServer.Name);
Assert.Equal("Development server", openApiServer.Description);
// The x-oai-name extension should not be in the extensions collection since it's parsed to Name property
Assert.Null(openApiServer.Extensions);
}

[Fact]
public void ParseServerWithOtherExtensionShouldKeepExtension()
{
var input =
"""
url: https://example.com
description: Sample server
x-custom-extension: custom-value
""";

var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(input));
var yamlNode = yamlStream.Documents[0].RootNode;

var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic);

var asJsonNode = yamlNode.ToJsonNode();
var node = new MapNode(context, asJsonNode);

// Act
var openApiServer = OpenApiV3Deserializer.LoadServer(node, new());

// Assert
Assert.Equal("https://example.com", openApiServer.Url);
Assert.Null(openApiServer.Name);
Assert.Equal("Sample server", openApiServer.Description);
Assert.NotNull(openApiServer.Extensions);
Assert.Single(openApiServer.Extensions);
Assert.True(openApiServer.Extensions.ContainsKey("x-custom-extension"));
}
}
}
Loading
Loading