Skip to content

Commit b339855

Browse files
authored
Merge pull request #143 from microsoft/bugfix/missing-quotes-templates
bugfix/missing quotes templates
2 parents fa2fee8 + c0998b5 commit b339855

21 files changed

+158
-99
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// ------------------------------------------------------------
5+
6+
using Microsoft.OData.Edm;
7+
8+
namespace Microsoft.OpenApi.OData.Edm
9+
{
10+
/// <summary>
11+
/// Extension methods for <see cref="IEdmType"/>
12+
/// </summary>
13+
public static class EdmTypeExtensions
14+
{
15+
/// <summary>
16+
/// Determines wether a path parameter should be wrapped in quotes based on the type of the parameter.
17+
/// </summary>
18+
/// <param name="edmType">The type of the parameter.</param>
19+
/// <param name="settings">The conversion settings.</param>
20+
/// <returns>True if the parameter should be wrapped in quotes, false otherwise.</returns>
21+
public static bool ShouldPathParameterBeQuoted(this IEdmType edmType, OpenApiConvertSettings settings)
22+
{
23+
if (edmType == null || settings == null || !settings.AddSingleQuotesForStringParameters)
24+
{
25+
return false;
26+
}
27+
28+
return edmType.TypeKind switch
29+
{
30+
EdmTypeKind.Enum => true,
31+
EdmTypeKind.Primitive when edmType.IsString() || edmType.IsTemporal() => true,
32+
_ => false,
33+
};
34+
}
35+
}
36+
37+
}

src/Microsoft.OpenApi.OData.Reader/Edm/ODataKeySegment.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,7 @@ public override string GetPathItemName(OpenApiConvertSettings settings, HashSet<
7777
}
7878
else
7979
{
80-
IList<string> keyStrings = new List<string>();
81-
foreach (var key in KeyMappings)
82-
{
83-
keyStrings.Add(key.Key + "={" + key.Value + "}");
84-
}
85-
86-
return String.Join(",", keyStrings);
80+
return string.Join(",", KeyMappings.Select(x => x.Key + "='{" + x.Value + "}'"));
8781
}
8882
}
8983

@@ -109,7 +103,8 @@ public override string GetPathItemName(OpenApiConvertSettings settings, HashSet<
109103
foreach (var keyProperty in keys)
110104
{
111105
string name = Utils.GetUniqueName(keyProperty.Name, parameters);
112-
keyStrings.Add(keyProperty.Name + "={" + name + "}");
106+
var quote = keyProperty.Type.Definition.ShouldPathParameterBeQuoted(settings) ? "'" : string.Empty;
107+
keyStrings.Add($"{keyProperty.Name}={quote}{{{name}}}{quote}");
113108
}
114109

115110
return String.Join(",", keyStrings);

src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationImportSegment.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ private string FunctionImportName(IEdmFunctionImport functionImport, OpenApiConv
8383
}
8484
else
8585
{
86-
return p.Name + "={" + uniqueName + "}";
86+
var quote = p.Type.Definition.ShouldPathParameterBeQuoted(settings) ? "'" : string.Empty;
87+
return $"{p.Name}={quote}{{{uniqueName}}}{quote}";
8788
}
8889
})));
8990

src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ private string FunctionName(IEdmFunction function, OpenApiConvertSettings settin
148148
}
149149
else
150150
{
151-
return p.Name + "={" + uniqueName + "}";
151+
var quote = p.Type.Definition.ShouldPathParameterBeQuoted(settings) ? "'" : string.Empty;
152+
return p.Name + $"={quote}{{{uniqueName}}}{quote}";
152153
}
153154
})));
154155

src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathKind.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public enum ODataPathKind
2626
Singleton,
2727

2828
/// <summary>
29-
/// Represents an operation (function or action) path, for example: ~/users/NS.findRooms(roomId={roomId})
29+
/// Represents an operation (function or action) path, for example: ~/users/NS.findRooms(roomId='{roomId}')
3030
/// </summary>
3131
Operation,
3232

src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
297297
{
298298
// Non-Contained
299299
// Single-Valued: ~/entityset/{key}/single-valued-Nav/$ref
300-
// Collection-valued: ~/entityset/{key}/collection-valued-Nav/$ref?$id ={navKey}
300+
// Collection-valued: ~/entityset/{key}/collection-valued-Nav/$ref?$id='{navKey}'
301301
CreateRefPath(currentPath);
302302

303303
if (targetsMany)

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ public static IList<OpenApiParameter> CreateParameters(this ODataContext context
136136

137137
if (parameterNameMapping != null)
138138
{
139-
parameter.Description = $"Usage: {edmParameter.Name}={{{parameterNameMapping[edmParameter.Name]}}}";
139+
var quote = edmParameter.Type.Definition.ShouldPathParameterBeQuoted(context.Settings) ? "'" : string.Empty;
140+
parameter.Description = $"Usage: {edmParameter.Name}={quote}{{{parameterNameMapping[edmParameter.Name]}}}{quote}";
140141
}
141142

142143
parameters.Add(parameter);
@@ -203,7 +204,8 @@ public static IList<OpenApiParameter> CreateKeyParameters(this ODataContext cont
203204

204205
if (keySegment.KeyMappings != null)
205206
{
206-
parameter.Description = parameter.Description + $", {keyProperty.Name}={{{parameter.Name}}}";
207+
var quote = keyProperty.Type.Definition.ShouldPathParameterBeQuoted(context.Settings) ? "'" : string.Empty;
208+
parameter.Description += $", {keyProperty.Name}={quote}{{{parameter.Name}}}{quote}";
207209
}
208210

209211
parameter.Extensions.Add(Constants.xMsKeyType, new OpenApiString(entityType.Name));

src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ public string PathPrefix
188188
/// </summary>
189189
public bool EnableDollarCountPath { get; set; } = true;
190190

191+
/// <summary>
192+
/// Gets/sets a value indicating whether or not single quotes surrounding string parameters in url templates should be added.
193+
/// </summary>
194+
public bool AddSingleQuotesForStringParameters { get; set; } = false;
195+
191196
internal OpenApiConvertSettings Clone()
192197
{
193198
var newSettings = new OpenApiConvertSettings
@@ -219,6 +224,7 @@ internal OpenApiConvertSettings Clone()
219224
ShowRootPath = this.ShowRootPath,
220225
PathProvider = this.PathProvider,
221226
EnableDollarCountPath = this.EnableDollarCountPath,
227+
AddSingleQuotesForStringParameters = this.AddSingleQuotesForStringParameters,
222228
};
223229

224230
return newSettings;

src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ abstract Microsoft.OpenApi.OData.Edm.ODataSegment.Identifier.get -> string
33
abstract Microsoft.OpenApi.OData.Edm.ODataSegment.Kind.get -> Microsoft.OpenApi.OData.Edm.ODataSegmentKind
44
Microsoft.OpenApi.OData.Common.Utils
55
Microsoft.OpenApi.OData.Edm.EdmModelExtensions
6+
Microsoft.OpenApi.OData.Edm.EdmTypeExtensions
7+
static Microsoft.OpenApi.OData.Edm.EdmTypeExtensions.ShouldPathParameterBeQuoted(this Microsoft.OData.Edm.IEdmType edmType, Microsoft.OpenApi.OData.OpenApiConvertSettings settings) -> bool
68
Microsoft.OpenApi.OData.Edm.IODataPathProvider
79
Microsoft.OpenApi.OData.Edm.IODataPathProvider.CanFilter(Microsoft.OData.Edm.IEdmElement element) -> bool
810
Microsoft.OpenApi.OData.Edm.IODataPathProvider.GetPaths(Microsoft.OData.Edm.IEdmModel model, Microsoft.OpenApi.OData.OpenApiConvertSettings settings) -> System.Collections.Generic.IEnumerable<Microsoft.OpenApi.OData.Edm.ODataPath>
@@ -130,6 +132,8 @@ Microsoft.OpenApi.OData.OpenApiConvertSettings.PathProvider.get -> Microsoft.Ope
130132
Microsoft.OpenApi.OData.OpenApiConvertSettings.PathProvider.set -> void
131133
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDollarCountPath.get -> bool
132134
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDollarCountPath.set -> void
135+
Microsoft.OpenApi.OData.OpenApiConvertSettings.AddSingleQuotesForStringParameters.get -> bool
136+
Microsoft.OpenApi.OData.OpenApiConvertSettings.AddSingleQuotesForStringParameters.set -> void
133137
Microsoft.OpenApi.OData.OpenApiConvertSettings.PrefixEntityTypeNameBeforeKey.get -> bool
134138
Microsoft.OpenApi.OData.OpenApiConvertSettings.PrefixEntityTypeNameBeforeKey.set -> void
135139
Microsoft.OpenApi.OData.OpenApiConvertSettings.RequireDerivedTypesConstraintForBoundOperations.get -> bool

test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataKeySegmentTests.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,22 @@ public void GetPathItemNameReturnsCorrectKeyLiteralForSimpleKey(bool prefix, str
7171
}
7272

7373
[Theory]
74-
[InlineData(true)]
75-
[InlineData(false)]
76-
public void GetPathItemNameReturnsCorrectKeyLiteralForCompositeKey(bool prefix)
74+
[InlineData(true, true, "firstName='{firstName}',lastName='{lastName}'")]
75+
[InlineData(false, true, "firstName='{firstName}',lastName='{lastName}'")]
76+
[InlineData(true, false, "firstName={firstName},lastName={lastName}")]
77+
[InlineData(false, false, "firstName={firstName},lastName={lastName}")]
78+
public void GetPathItemNameReturnsCorrectKeyLiteralForCompositeKey(bool prefix, bool addQuotes, string expected)
7779
{
7880
// Arrange & Act
7981
ODataKeySegment segment = new ODataKeySegment(_customer);
8082
OpenApiConvertSettings settings = new OpenApiConvertSettings
8183
{
82-
PrefixEntityTypeNameBeforeKey = prefix
84+
PrefixEntityTypeNameBeforeKey = prefix,
85+
AddSingleQuotesForStringParameters = addQuotes,
8386
};
8487

8588
// Assert
86-
Assert.Equal("firstName={firstName},lastName={lastName}", segment.GetPathItemName(settings));
89+
Assert.Equal(expected, segment.GetPathItemName(settings));
8790
}
8891
}
8992
}

test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationImportSegmentTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ public void GetPathItemNameReturnsCorrectFunctionImportLiteral()
8080
var segment = new ODataOperationImportSegment(_functionImport);
8181

8282
// Assert
83-
Assert.Equal("MyFunction(firstName={firstName},lastName={lastName})",
84-
segment.GetPathItemName(new OpenApiConvertSettings()));
83+
Assert.Equal("MyFunction(firstName='{firstName}',lastName='{lastName}')",
84+
segment.GetPathItemName(new OpenApiConvertSettings() {
85+
AddSingleQuotesForStringParameters = true
86+
}));
8587
}
8688
}
8789
}

test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ public void GetPathItemNameReturnsCorrectFunctionLiteral(bool unqualifiedCall, b
9898

9999
[Theory]
100100
[InlineData(true, true, "{param}")]
101-
[InlineData(true, false, "NS.MyFunction(param={param})")]
102-
[InlineData(false, true, "NS.MyFunction(param={param})")]
103-
[InlineData(false, false, "NS.MyFunction(param={param})")]
101+
[InlineData(true, false, "NS.MyFunction(param='{param}')")]
102+
[InlineData(false, true, "NS.MyFunction(param='{param}')")]
103+
[InlineData(false, false, "NS.MyFunction(param='{param}')")]
104104
public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedFunction(bool isEscapedFunction, bool enableEscapeFunctionCall, string expected)
105105
{
106106
// Arrange & Act
@@ -112,7 +112,8 @@ public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedFunction(bool
112112
var segment = new ODataOperationSegment(boundFunction, isEscapedFunction);
113113
OpenApiConvertSettings settings = new OpenApiConvertSettings
114114
{
115-
EnableUriEscapeFunctionCall = enableEscapeFunctionCall
115+
EnableUriEscapeFunctionCall = enableEscapeFunctionCall,
116+
AddSingleQuotesForStringParameters = true,
116117
};
117118

118119
// Assert
@@ -121,9 +122,9 @@ public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedFunction(bool
121122

122123
[Theory]
123124
[InlineData(true, true, "{param}:")]
124-
[InlineData(true, false, "NS.MyFunction(param={param})")]
125-
[InlineData(false, true, "NS.MyFunction(param={param})")]
126-
[InlineData(false, false, "NS.MyFunction(param={param})")]
125+
[InlineData(true, false, "NS.MyFunction(param='{param}')")]
126+
[InlineData(false, true, "NS.MyFunction(param='{param}')")]
127+
[InlineData(false, false, "NS.MyFunction(param='{param}')")]
127128
public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedComposableFunction(bool isEscapedFunction, bool enableEscapeFunctionCall, string expected)
128129
{
129130
// Arrange & Act
@@ -135,7 +136,8 @@ public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedComposableFunc
135136
var segment = new ODataOperationSegment(boundFunction, isEscapedFunction);
136137
OpenApiConvertSettings settings = new OpenApiConvertSettings
137138
{
138-
EnableUriEscapeFunctionCall = enableEscapeFunctionCall
139+
EnableUriEscapeFunctionCall = enableEscapeFunctionCall,
140+
AddSingleQuotesForStringParameters = true
139141
};
140142

141143
// Assert

test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathTests.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,10 @@ public void GetPathItemNameReturnsCorrectWithSingleKeySegment(bool keyAsSegment,
249249
}
250250

251251
[Theory]
252-
[InlineData(true, true, "/Customers/FirstName={FirstName},LastName={LastName}")]
253-
[InlineData(true, false, "/Customers/FirstName={FirstName},LastName={LastName}")]
254-
[InlineData(false, true, "/Customers(FirstName={FirstName},LastName={LastName})")]
255-
[InlineData(false, false, "/Customers(FirstName={FirstName},LastName={LastName})")]
252+
[InlineData(true, true, "/Customers/FirstName='{FirstName}',LastName='{LastName}'")]
253+
[InlineData(true, false, "/Customers/FirstName='{FirstName}',LastName='{LastName}'")]
254+
[InlineData(false, true, "/Customers(FirstName='{FirstName}',LastName='{LastName}')")]
255+
[InlineData(false, false, "/Customers(FirstName='{FirstName}',LastName='{LastName}')")]
256256
public void GetPathItemNameReturnsCorrectStringWithMultipleKeySegment(bool keyAsSegment, bool prefix, string expected)
257257
{
258258
// Arrange
@@ -262,7 +262,8 @@ public void GetPathItemNameReturnsCorrectStringWithMultipleKeySegment(bool keyAs
262262
OpenApiConvertSettings settings = new OpenApiConvertSettings
263263
{
264264
EnableKeyAsSegment = keyAsSegment,
265-
PrefixEntityTypeNameBeforeKey = prefix
265+
PrefixEntityTypeNameBeforeKey = prefix,
266+
AddSingleQuotesForStringParameters = true,
266267
};
267268

268269
// Act

test/Microsoft.OpenAPI.OData.Reader.Tests/EdmModelOpenApiExtensionsTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ public void TripServiceMetadataToOpenApiJsonWorks(OpenApiSpecVersion specVersion
208208
Version = new Version(1, 0, 1),
209209
ServiceRoot = new Uri("http://services.odata.org/TrippinRESTierService"),
210210
IEEE754Compatible = true,
211-
OpenApiSpecVersion = specVersion
211+
OpenApiSpecVersion = specVersion,
212+
AddSingleQuotesForStringParameters = true,
212213
};
213214
// Act
214215
string json = WriteEdmModelAsOpenApi(model, OpenApiFormat.Json, settings);
@@ -239,7 +240,8 @@ public void TripServiceMetadataToOpenApiYamlWorks(OpenApiSpecVersion specVersion
239240
Version = new Version(1, 0, 1),
240241
ServiceRoot = new Uri("http://services.odata.org/TrippinRESTierService"),
241242
IEEE754Compatible = true,
242-
OpenApiSpecVersion = specVersion
243+
OpenApiSpecVersion = specVersion,
244+
AddSingleQuotesForStringParameters = true,
243245
};
244246

245247
// Act

test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ public void CreatePathItemsReturnsForBasicModel()
7575
[InlineData(true, true, true, "/Customers({ID}):/{param}:")]
7676
[InlineData(true, true, false, "/Customers({ID}):/{param}")]
7777

78-
[InlineData(true, false, true, "/Customers({ID})/NS.MyFunction(param={param})")]
79-
[InlineData(true, false, false, "/Customers({ID})/NS.MyFunction(param={param})")]
80-
[InlineData(false, true, true, "/Customers({ID})/NS.MyFunction(param={param})")]
81-
[InlineData(false, true, false, "/Customers({ID})/NS.MyFunction(param={param})")]
82-
[InlineData(false, false, true, "/Customers({ID})/NS.MyFunction(param={param})")]
83-
[InlineData(false, false, false, "/Customers({ID})/NS.MyFunction(param={param})")]
78+
[InlineData(true, false, true, "/Customers({ID})/NS.MyFunction(param='{param}')")]
79+
[InlineData(true, false, false, "/Customers({ID})/NS.MyFunction(param='{param}')")]
80+
[InlineData(false, true, true, "/Customers({ID})/NS.MyFunction(param='{param}')")]
81+
[InlineData(false, true, false, "/Customers({ID})/NS.MyFunction(param='{param}')")]
82+
[InlineData(false, false, true, "/Customers({ID})/NS.MyFunction(param='{param}')")]
83+
[InlineData(false, false, false, "/Customers({ID})/NS.MyFunction(param='{param}')")]
8484
public void CreatePathItemsReturnsForEscapeFunctionModel(bool enableEscaped, bool hasEscapedAnnotation, bool isComposable, string expected)
8585
{
8686
// Arrange
@@ -108,7 +108,8 @@ public void CreatePathItemsReturnsForEscapeFunctionModel(bool enableEscaped, boo
108108

109109
OpenApiConvertSettings settings = new OpenApiConvertSettings
110110
{
111-
EnableUriEscapeFunctionCall = enableEscaped
111+
EnableUriEscapeFunctionCall = enableEscaped,
112+
AddSingleQuotesForStringParameters = true,
112113
};
113114
ODataContext context = new ODataContext(model, settings);
114115

test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionImportOperationHandlerTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ public void CreateOperationIdWithSHA5ForOverloadEdmFunctionImport(bool enableOpe
120120

121121
OpenApiConvertSettings settings = new OpenApiConvertSettings
122122
{
123-
EnableOperationId = enableOperationId
123+
EnableOperationId = enableOperationId,
124+
AddSingleQuotesForStringParameters = true,
124125
};
125126
ODataContext context = new ODataContext(model, settings);
126127

@@ -134,7 +135,7 @@ public void CreateOperationIdWithSHA5ForOverloadEdmFunctionImport(bool enableOpe
134135

135136
if (enableOperationId)
136137
{
137-
Assert.Equal("FunctionImport.MyFunction-3e3f", operation.OperationId);
138+
Assert.Equal("FunctionImport.MyFunction-cc1c", operation.OperationId);
138139
}
139140
else
140141
{

test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ public void CreateOperationForOverloadEdmFunctionReturnsCorrectOperationId(bool
213213

214214
OpenApiConvertSettings settings = new OpenApiConvertSettings
215215
{
216-
EnableOperationId = enableOperationId
216+
EnableOperationId = enableOperationId,
217+
AddSingleQuotesForStringParameters = true,
217218
};
218219
ODataContext context = new ODataContext(model, settings);
219220

@@ -229,7 +230,7 @@ public void CreateOperationForOverloadEdmFunctionReturnsCorrectOperationId(bool
229230

230231
if (enableOperationId)
231232
{
232-
Assert.Equal("Customers.Customer.MyFunction-28ae", operation.OperationId);
233+
Assert.Equal("Customers.Customer.MyFunction-df74", operation.OperationId);
233234
}
234235
else
235236
{

0 commit comments

Comments
 (0)