Skip to content

Commit 04c9345

Browse files
Disambiguate complex type, better method overload support (#227)
* Update ComplexType.cs.tt to disambiguate Related to updates to EntityType.cs.tt in 9850b83 and 29d268b * Disambiguate parameters for actions * Enable overloads with uniqueness checks Fixes a hack that was used to restrict generation of a function overload where the overload was based on the binding element type. We now check the overloads for uniqueness based on the parameter list. We also now don't emit duplicate parameters in the CreateReqyest method. * Structural property type needs to use the same disambiguation scheme as complextype template
1 parent 225cacf commit 04c9345

File tree

7 files changed

+276
-19
lines changed

7 files changed

+276
-19
lines changed

Templates/CSharp/Base/IRequestBuilder.Base.template.tt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ public string GetMethodProperties(OdcmClass entity, bool isCollection)
117117
var paramVariableName = param.Name.GetSanitizedParameterName();
118118
var paramTypeString = param.Type.GetTypeString();
119119

120+
// Adds support for classes ending in "Request" that have been dismabiguated.
121+
if (paramTypeString.EndsWith("Request"))
122+
{
123+
paramTypeString = String.Concat(paramTypeString, "Object");
124+
}
125+
120126
if (param.IsCollection)
121127
{
122128
paramTypeString = string.Format("IEnumerable<{0}>", paramTypeString);

Templates/CSharp/Base/RequestBuilder.Base.template.tt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ public string GetMethodProperties(OdcmClass entity, bool isCollection)
163163
var paramVariableName = param.Name.GetSanitizedParameterName();
164164
var paramTypeString = param.Type.GetTypeString();
165165

166+
// Adds support for classes ending in "Request" that have been dismabiguated.
167+
if (paramTypeString.EndsWith("Request"))
168+
{
169+
paramTypeString = String.Concat(paramTypeString, "Object");
170+
}
171+
166172
if (param.IsCollection)
167173
{
168174
paramTypeString = string.Format("IEnumerable<{0}>", paramTypeString);

Templates/CSharp/Model/ComplexType.cs.tt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,31 @@ OdcmClass complex = host.CurrentType.AsOdcmClass();
77
var complexTypeName = complex.Name.ToCheckedCase();
88
var typeDeclaration = complexTypeName;
99

10+
// In the case a complex type name ends with "Request", we will have a collision with the *Request objects
11+
// when we generate. For example, there is a complex type named SearchRequest and an entity named Search.
12+
// The SearchRequest complex type results in a SearchResult class in our models collection. The Search entity
13+
// results in a SearchRequest request object causing conflicts.
14+
15+
if (typeDeclaration.EndsWith("Request"))
16+
{
17+
typeDeclaration = String.Concat(typeDeclaration, "Object");
18+
}
19+
20+
// Capture the cstor type declaration as the typedeclaration may get appended with a base type.
21+
string cstorTypeDeclaration = typeDeclaration;
22+
1023
if (complex.Base != null)
1124
{
12-
typeDeclaration = string.Format("{0} : {1}", typeDeclaration, complex.Base.Name.ToCheckedCase());
25+
// Disambiguate the base type declaration for the same reason we did this
26+
// for typeDeclaration.
27+
var baseTypeDeclaration = complex.Base.Name.ToCheckedCase();
28+
29+
if (baseTypeDeclaration.EndsWith("Request"))
30+
{
31+
baseTypeDeclaration = String.Concat(baseTypeDeclaration, "Object");
32+
}
33+
34+
typeDeclaration = string.Format("{0} : {1}", typeDeclaration, baseTypeDeclaration);
1335
}
1436

1537
var isMethodResponse = complex.LongDescriptionContains("methodResponse");
@@ -47,9 +69,9 @@ namespace <#=complex.Namespace.GetNamespaceName()#>
4769
if (!complex.IsAbstract)
4870
{
4971
#> /// <summary>
50-
/// Initializes a new instance of the <see cref="<#=complexTypeName#>"/> class.
72+
/// Initializes a new instance of the <see cref="<#=cstorTypeDeclaration#>"/> class.
5173
/// </summary>
52-
public <#=complexTypeName#>()
74+
public <#=cstorTypeDeclaration#>()
5375
{
5476
this.ODataType = "<#=complex.FullName#>";
5577
}

Templates/CSharp/Model/EntityType.cs.tt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ namespace <#=entity.Namespace.GetNamespaceName()#>
9393
var propertyType = property.IsTypeNullable() || property.IsCollection()
9494
? property.GetTypeString()
9595
: property.GetTypeString() + "?";
96+
97+
// We want to disambiguate structural property return types in case there is a naming
98+
// collision with a request builder, like what we had for the Search entity, and the SearchRequest
99+
// complex type.
100+
if (propertyType.EndsWith("Request") && property.IsNavigation() == false)
101+
{
102+
propertyType = String.Concat(propertyType, "Object");
103+
}
96104

97105
var propertyName = property.Name.ToCheckedCase();
98106
var propertyCollectionPage = property.IsReference() ? string.Concat(entityName, propertyName, "CollectionWithReferencesPage") : string.Concat(entityName, propertyName, "CollectionPage");

Templates/CSharp/Requests/MethodRequestBuilder.cs.tt

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,12 @@ var requestBuilderType = requestType + "Builder";
1212
var isPost = method.IsAction() && method.Parameters != null && method.Parameters.Any();
1313
var methodType = method.IsFunction ? "Function" : "Action";
1414

15+
// Overloads are determined based on the name of method and the type of the binding parameter.
16+
// There is no differentiation between an entity, and a collection of the same entities.
17+
// This may cause issues in the future that would require changes.
1518
var overloads = new List<OdcmMethod>();
1619
overloads.Add(method);
17-
18-
// So far, it appears that the overloads on the functions are working as expected.
19-
// The overloads on actions should be skipped. If we see more issues here, we may need to
20-
// revisit how VIPR determines overloaded methods.
21-
if (method.IsFunction)
22-
{
23-
overloads.AddRange(method.Overloads);
24-
}
25-
26-
20+
overloads.AddRange(method.Overloads);
2721

2822
var methods = overloads
2923
.Select(m =>
@@ -36,6 +30,11 @@ var methods = overloads
3630
var parameterName = p.Name.GetSanitizedParameterName();
3731
var propertyName = p.Name.ToCheckedCase();
3832

33+
// Adds support for classes ending in "Request" that have been dismabiguated.
34+
if (type.EndsWith("Request"))
35+
{
36+
type = String.Concat(type, "Object");
37+
}
3938
if (p.IsCollection)
4039
{
4140
type = string.Format("IEnumerable<{0}>", type);
@@ -52,6 +51,8 @@ var methods = overloads
5251
var paramStrings = parameters.Select(p => string.Format(",\n {0} {1}", p.Type, p.ParameterName));
5352
var paramComments = parameters.Select(p => string.Format("\n /// <param name=\"{0}\">A {0} parameter for the OData method call.</param>", p.ParameterName));
5453

54+
55+
5556
return new
5657
{
5758
Parameters = parameters,
@@ -74,10 +75,14 @@ namespace <#=method.Namespace.GetNamespaceName()#>
7475
public partial class <#=requestBuilderType#> : Base<#=methodType#>MethodRequestBuilder<I<#=requestType#>>, I<#=requestBuilderType#>
7576
{
7677
<#
78+
// We only want to generate unique method signatures.
79+
// We'll use the ParametersAsArguments to define the uniqueness of a method.
80+
HashSet<string> uniqueMethods = new HashSet<string>();
7781
foreach (var m in methods)
78-
7982
{
80-
83+
// Only generate a unique method.
84+
if (uniqueMethods.Add(m.ParametersAsArguments) == true)
85+
{
8186
#>
8287
/// <summary>
8388
/// Constructs a new <see cref="<#=requestBuilderType#>"/>.
@@ -90,16 +95,17 @@ foreach (var m in methods)
9095
: base(requestUrl, client)
9196
{
9297
<#
93-
foreach (var p in m.Parameters)
94-
{
98+
foreach (var p in m.Parameters)
99+
{
95100
#>
96101
this.SetParameter("<#=p.Name#>", <#=p.ParameterName#>, <#=p.IsNullable.ToString().ToLowerInvariant()#>);
97102
<#
98-
}
103+
}
99104
#>
100105
}
101106

102107
<#
108+
}
103109
}
104110
#>
105111
/// <summary>
@@ -115,17 +121,23 @@ foreach (var m in methods)
115121
<#
116122
if (isPost)
117123
{
124+
HashSet<string> uniqueParameters = new HashSet<string>();
125+
118126
foreach (var m in methods)
119127
{
120128
foreach (var p in m.Parameters)
121129
{
130+
// We only want to add unique parameters to CreateRequest.
131+
if (uniqueParameters.Add(p.Name) == true)
132+
{
122133
#>
123134
if (this.HasParameter("<#=p.Name#>"))
124135
{
125136
request.RequestBody.<#=p.PropertyName#> = this.GetParameter<<#=p.Type#>>("<#=p.Name#>");
126137
}
127138

128139
<#
140+
}
129141
}
130142
}
131143
}

test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,193 @@ public void It_doesnt_generate_odatatype_initialization_for_abstract_entitytypes
280280
Assert.IsTrue(hasTestString, $"The expected test token string, '{testString}', was not set in the generated test file. We didn't properly generate the cstor code.");
281281
Assert.IsFalse(hasTestODataInitString, $"The unexpected test token string, '{testODataInitString}', was set in the generated test file. We didn't properly generate the cstor code.");
282282
}
283+
284+
[TestMethod]
285+
public void It_creates_disambiguated_abstract_base_complextype_models()
286+
{
287+
const string outputDirectory = "output";
288+
289+
Options options = new Options()
290+
{
291+
Output = outputDirectory,
292+
Language = "CSharp",
293+
GenerationMode = GenerationMode.Files
294+
};
295+
296+
Generator.GenerateFiles(testMetadata, options);
297+
298+
FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Model\EmptyBaseComplexTypeRequest.cs");
299+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
300+
301+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
302+
bool hasTestString = false;
303+
string testString = "public abstract partial class EmptyBaseComplexTypeRequestObject";
304+
305+
foreach (var line in lines)
306+
{
307+
if (line.Contains(testString))
308+
{
309+
hasTestString = true;
310+
break;
311+
}
312+
}
313+
314+
Assert.IsTrue(hasTestString, $"The expected test token string, '{testString}', was not set in the generated test file. We didn't properly generate the type declaration code.");
315+
}
316+
317+
[TestMethod]
318+
public void It_creates_disambiguated_complextype_models()
319+
{
320+
const string outputDirectory = "output";
321+
322+
Options options = new Options()
323+
{
324+
Output = outputDirectory,
325+
Language = "CSharp",
326+
GenerationMode = GenerationMode.Files
327+
};
328+
329+
Generator.GenerateFiles(testMetadata, options);
330+
331+
FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Model\DerivedComplexTypeRequest.cs");
332+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
333+
334+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
335+
bool hasTestTypeDeclaration = false;
336+
string testTypeDeclaration = "public partial class DerivedComplexTypeRequestObject : EmptyBaseComplexTypeRequestObject";
337+
bool hasTestTypeCstor = false;
338+
string testTypeCstor = "public DerivedComplexTypeRequestObject";
339+
bool hasTestOdataType = false;
340+
string testOdataType = "this.ODataType = \"microsoft.graph.derivedComplexTypeRequest\"";
341+
342+
foreach (var line in lines)
343+
{
344+
// We only need to check once.
345+
if (line.Contains(testTypeDeclaration) && !hasTestTypeDeclaration)
346+
{
347+
hasTestTypeDeclaration = true;
348+
continue;
349+
}
350+
if (line.Contains(testTypeCstor) && !hasTestTypeCstor)
351+
{
352+
hasTestTypeCstor = true;
353+
continue;
354+
}
355+
if (line.Contains(testOdataType) && !hasTestOdataType)
356+
{
357+
hasTestOdataType = true;
358+
break; // This is the last expected line.
359+
}
360+
}
361+
362+
Assert.IsTrue(hasTestTypeDeclaration, $"The expected test token string, '{testTypeDeclaration}', was not set in the generated test file. We didn't properly generate the type declaration code.");
363+
Assert.IsTrue(hasTestTypeCstor, $"The expected test token string, '{testTypeCstor}', was not set in the generated test file. We didn't properly generate the cstor code.");
364+
Assert.IsTrue(hasTestOdataType, $"The expected test token string, '{testOdataType}', was not set in the generated test file. We didn't properly generate the initialized odata.type code.");
365+
}
366+
367+
[TestMethod]
368+
public void It_creates_disambiguated_MethodRequestBuilder_parameters()
369+
{
370+
const string outputDirectory = "output";
371+
372+
Options options = new Options()
373+
{
374+
Output = outputDirectory,
375+
Language = "CSharp",
376+
GenerationMode = GenerationMode.Files
377+
};
378+
379+
Generator.GenerateFiles(testMetadata, options);
380+
381+
FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Requests\TestTypeQueryRequestBuilder.cs");
382+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
383+
384+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
385+
bool hasTestParameter = false;
386+
string testParameter = "IEnumerable<DerivedComplexTypeRequestObject> requests)";
387+
388+
389+
foreach (var line in lines)
390+
{
391+
// We only need to check once.
392+
if (line.Contains(testParameter))
393+
{
394+
hasTestParameter = true;
395+
break;
396+
}
397+
}
398+
399+
Assert.IsTrue(hasTestParameter, $"The expected test token string, '{testParameter}', was not set in the generated test file. We didn't properly generate the parameter.");
400+
}
401+
402+
[TestMethod]
403+
public void It_creates_disambiguated_EntityRequestBuilder_parameters()
404+
{
405+
const string outputDirectory = "output";
406+
407+
Options options = new Options()
408+
{
409+
Output = outputDirectory,
410+
Language = "CSharp",
411+
GenerationMode = GenerationMode.Files
412+
};
413+
414+
Generator.GenerateFiles(testMetadata, options);
415+
416+
FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Requests\TestTypeRequestBuilder.cs");
417+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
418+
419+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
420+
421+
bool hasTestParameter = false;
422+
string testParameter = "IEnumerable<DerivedComplexTypeRequestObject> requests)";
423+
424+
foreach (var line in lines)
425+
{
426+
// We only need to check once.
427+
if (line.Contains(testParameter))
428+
{
429+
hasTestParameter = true;
430+
break;
431+
}
432+
}
433+
434+
Assert.IsTrue(hasTestParameter, $"The expected test token string, '{testParameter}', was not set in the generated test file. We didn't properly generate the parameter.");
435+
}
436+
437+
[TestMethod]
438+
public void It_creates_disambiguated_IEntityRequestBuilder_parameters()
439+
{
440+
const string outputDirectory = "output";
441+
442+
Options options = new Options()
443+
{
444+
Output = outputDirectory,
445+
Language = "CSharp",
446+
GenerationMode = GenerationMode.Files
447+
};
448+
449+
Generator.GenerateFiles(testMetadata, options);
450+
451+
FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Requests\ITestTypeRequestBuilder.cs");
452+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
453+
454+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
455+
456+
bool hasTestParameter = false;
457+
string testParameter = "IEnumerable<DerivedComplexTypeRequestObject> requests)";
458+
459+
foreach (var line in lines)
460+
{
461+
// We only need to check once.
462+
if (line.Contains(testParameter))
463+
{
464+
hasTestParameter = true;
465+
break;
466+
}
467+
}
468+
469+
Assert.IsTrue(hasTestParameter, $"The expected test token string, '{testParameter}', was not set in the generated test file. We didn't properly generate the parameter.");
470+
}
283471
}
284472
}

0 commit comments

Comments
 (0)