Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# CodeGen: Add support for non-exploded array-types with explicit style in query parameters #2961

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
164 changes: 163 additions & 1 deletion src/NSwag.CodeGeneration.CSharp.Tests/QueryParameterTests.cs
@@ -1,4 +1,7 @@
using Xunit;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Xunit;

namespace NSwag.CodeGeneration.CSharp.Tests
{
Expand Down Expand Up @@ -78,5 +81,164 @@ public void When_query_parameter_is_set_to_explode_and_style_is_form_object_para
"urlBuilder_.Append(System.Uri.EscapeDataString(\"limit\") + \"=\").Append(System.Uri.EscapeDataString(ConvertToString(paging.Limit, System.Globalization.CultureInfo.InvariantCulture))).Append(\"&\");",
code);
}

[Theory]
[InlineData("form", ",")]
[InlineData("spaceDelimited", "%20")]
[InlineData("pipeDelimited", "|")]
public void When_query_parameter_is_set_to_not_explode_style_should_be_respected_and_array_parameters_delimited(string style, string delimiter)
{
var spec = $@"{{
""openapi"": ""3.0.0"",
""info"": {{
""version"": ""1.0.0"",
""title"": ""Query params tests""
}},
""servers"": [
{{
""url"": ""http://localhost:8080""
}}
],
""paths"": {{
""/settings"": {{
""get"": {{
""summary"": ""List all settings"",
""operationId"": ""listSettings"",
""parameters"": [
{{
""name"": ""paging"",
""in"": ""query"",
""description"": ""list setting filter"",
""required"": false,
""style"": ""{style}"",
""explode"": false,
""type"": ""array"",
""items"": {{
""type"": ""integer"",
""format"": ""int32""
}}
}}
],
""responses"": {{
""200"": {{
""description"": ""A paged array of settings""
}}
}}
}}
}}
}}
}}
";



var document = OpenApiDocument.FromJsonAsync(spec).Result;

//// Act
var generator = new CSharpClientGenerator(document, new CSharpClientGeneratorSettings());
var code = generator.GenerateFile();

//// Assert

var expected =
$@"{{
bool first_ = true;
foreach (var item_ in paging)
{{
if (first_)
{{
urlBuilder_.Append(System.Uri.EscapeDataString(""paging"")).Append('=');
first_ = false;
}}
else
urlBuilder_.Append({(delimiter.Length == 1 ? $"'{delimiter}'" : $"\"{delimiter}\"")});

urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture)));
}}
if (!first_)
urlBuilder_.Append('&');
}}";

AssertCodeContains(expected, code);
}

[Theory]
[InlineData("form")]
[InlineData("spaceDelimited")]
[InlineData("pipeDelimited")]
public void When_query_parameter_is_set_to_explode_style_is_ignored_and_array_parameters_are_exploded(string style)
{
var spec = $@"{{
""openapi"": ""3.0.0"",
""info"": {{
""version"": ""1.0.0"",
""title"": ""Query params tests""
}},
""servers"": [
{{
""url"": ""http://localhost:8080""
}}
],
""paths"": {{
""/settings"": {{
""get"": {{
""summary"": ""List all settings"",
""operationId"": ""listSettings"",
""parameters"": [
{{
""name"": ""paging"",
""in"": ""query"",
""description"": ""list setting filter"",
""required"": false,
""style"": ""{style}"",
""explode"": true,
""type"": ""array"",
""items"": {{
""type"": ""integer"",
""format"": ""int32""
}}
}}
],
""responses"": {{
""200"": {{
""description"": ""A paged array of settings""
}}
}}
}}
}}
}}
}}
";



var document = OpenApiDocument.FromJsonAsync(spec).Result;

//// Act
var generator = new CSharpClientGenerator(document, new CSharpClientGeneratorSettings());
var code = generator.GenerateFile();

//// Assert

var expected = "foreach (var item_ in paging) { urlBuilder_.Append(System.Uri.EscapeDataString(\"paging\") + \"=\").Append((item_ == null) ? \"\" : System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture))).Append(\"&\"); }";

AssertCodeContains(expected, code);
}

private static void AssertCodeContains(string expected, string actual)
{
var reader = new StringReader(expected);

const string regexLinePrefix = @"^\s+";
const string regexLineSuffix = @"\r?\n";

StringBuilder regexPattern = new StringBuilder();
while (reader.ReadLine() is string line)
regexPattern.AppendLine(regexLinePrefix + Regex.Escape(line) + regexLineSuffix);

Regex regex = new Regex(regexPattern.ToString(), RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);

Assert.True(regex.IsMatch(actual), $"Unable to find:\r\n{expected}\r\n in:\r\n{actual}");
}
}
}
Expand Up @@ -7,7 +7,63 @@ urlBuilder_.Append(System.Uri.EscapeDataString("{{ parameter.Name }}") + "=").Ap
{% elseif parameter.IsDate -%}
urlBuilder_.Append(System.Uri.EscapeDataString("{{ parameter.Name }}") + "=").Append(System.Uri.EscapeDataString({% if parameter.IsNullable and parameter.IsRequired %}{{ parameter.VariableName }} != null ? {% endif %}{{ parameter.VariableName }}{% if parameter.IsSystemNullable %}.Value{% endif %}.ToString("{{ ParameterDateFormat }}", System.Globalization.CultureInfo.InvariantCulture){% if parameter.IsNullable and parameter.IsRequired %} : "{{ QueryNullValue }}"{% endif %})).Append("&");
{% elseif parameter.IsArray -%}
{% if parameter.Explode -%}
foreach (var item_ in {{ parameter.VariableName }}) { urlBuilder_.Append(System.Uri.EscapeDataString("{{ parameter.Name }}") + "=").Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); }
{% elseif parameter.IsForm -%}
{
bool first_ = true;
foreach (var item_ in {{ parameter.VariableName }})
{
if (first_)
{
urlBuilder_.Append(System.Uri.EscapeDataString("{{ parameter.Name }}")).Append('=');
first_ = false;
}
else
urlBuilder_.Append(',');

urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture)));
}
if (!first_)
urlBuilder_.Append('&');
}
{% elseif parameter.IsSpaceDelimited -%}
{
bool first_ = true;
foreach (var item_ in {{ parameter.VariableName }})
{
if (first_)
{
urlBuilder_.Append(System.Uri.EscapeDataString("{{ parameter.Name }}")).Append('=');
first_ = false;
}
else
urlBuilder_.Append("%20");

urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture)));
}
if (!first_)
urlBuilder_.Append('&');
}
{% elseif parameter.IsPipeDelimited -%}
{
bool first_ = true;
foreach (var item_ in {{ parameter.VariableName }})
{
if (first_)
{
urlBuilder_.Append(System.Uri.EscapeDataString("{{ parameter.Name }}")).Append('=');
first_ = false;
}
else
urlBuilder_.Append('|');

urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture)));
}
if (!first_)
urlBuilder_.Append('&');
}
{% endif -%}
{% elseif parameter.IsDeepObject -%}
{% for property in parameter.PropertyNames -%}
if ({{parameter.Name}}.{{property.Name}} != null)
Expand Down
18 changes: 14 additions & 4 deletions src/NSwag.CodeGeneration/Models/ParameterModelBase.cs
Expand Up @@ -90,16 +90,26 @@ public string Default
public OpenApiParameterKind Kind => _parameter.Kind;

/// <summary>Gets the parameter style.</summary>
public OpenApiParameterStyle Style => _parameter.Style;
public OpenApiParameterStyle Style => _parameter.ActualStyle;

/// <summary>Gets the the value indicating if the parameter values should be exploded when included in the query string.</summary>
public bool Explode => _parameter.Explode;
public bool Explode => _parameter.ActualExplode;

/// <summary>Gets a value indicating whether the parameter is a deep object (OpenAPI 3).</summary>
public bool IsDeepObject => _parameter.Style == OpenApiParameterStyle.DeepObject;
public bool IsDeepObject => _parameter.ActualStyle == OpenApiParameterStyle.DeepObject;

/// <summary>Gets a value indicating whether the parameter has form style.</summary>
public bool IsForm => _parameter.Style == OpenApiParameterStyle.Form;
public bool IsForm => _parameter.ActualStyle == OpenApiParameterStyle.Form;

/// <summary>
/// Gets a value indicating whether this array parameter has space-delimited style (OpenAPI 3).
/// </summary>
public bool IsSpaceDelimited => this._parameter.ActualStyle == OpenApiParameterStyle.SpaceDelimeted;

/// <summary>
/// Gets a value indicating whether this array parameter has pipe-delimited style (OpenAPI 3).
/// </summary>
public bool IsPipeDelimited => this._parameter.ActualStyle == OpenApiParameterStyle.PipeDelimited;

/// <summary>Gets the contained value property names (OpenAPI 3).</summary>
public IEnumerable<PropertyModel> PropertyNames
Expand Down
@@ -0,0 +1,85 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using NJsonSchema;
using Xunit;

namespace NSwag.Core.Tests.Serialization
{
public class QueryParameterSerializationTests
{
[Fact]
public async Task When_style_is_form_then_explode_should_serialize_as_true_by_default()
{
//// Arrange
var parameter = new OpenApiParameter();
parameter.Name = "Foo";
parameter.Kind = OpenApiParameterKind.Query;
parameter.Style = OpenApiParameterStyle.Form;
parameter.Schema = new JsonSchema
{
Type = JsonObjectType.Array,
Item = new JsonSchema
{
Type = JsonObjectType.String
}
};

//// Act
var json = parameter.ToJson(Formatting.Indented);

//// Assert
Assert.Equal(
@"{
""$schema"": ""http://json-schema.org/draft-04/schema#"",
""name"": ""Foo"",
""in"": ""query"",
""style"": ""form"",
""explode"": true,
""schema"": {
""type"": ""array"",
""items"": {
""type"": ""string""
}
}
}".Replace("\r", ""), json.Replace("\r", ""));
}

[Fact]
public async Task When_style_is_form_then_explode_should_serialize_as_false_when_set()
{
//// Arrange
var parameter = new OpenApiParameter();
parameter.Name = "Foo";
parameter.Kind = OpenApiParameterKind.Query;
parameter.Style = OpenApiParameterStyle.Form;
parameter.Explode = false;
parameter.Schema = new JsonSchema
{
Type = JsonObjectType.Array,
Item = new JsonSchema
{
Type = JsonObjectType.String
}
};

//// Act
var json = parameter.ToJson(Formatting.Indented);

//// Assert
Assert.Equal(
@"{
""$schema"": ""http://json-schema.org/draft-04/schema#"",
""name"": ""Foo"",
""in"": ""query"",
""style"": ""form"",
""explode"": false,
""schema"": {
""type"": ""array"",
""items"": {
""type"": ""string""
}
}
}".Replace("\r", ""), json.Replace("\r", ""));
}
}
}