Skip to content

Commit 3219c92

Browse files
Fix OData methods with OData primitive return type (#300)
ODate primitives method return types are returned in an object. We weren't handling the object which resulted in serialization exceptions. modified: Templates/CSharp/Base/SharedCSharp.template.tt modified: Templates/CSharp/Requests/MethodRequest.cs.tt modified: Templates/CSharp/Requests/IMethodRequest.cs.tt
1 parent 31fe6ac commit 3219c92

File tree

5 files changed

+190
-12
lines changed

5 files changed

+190
-12
lines changed

Templates/CSharp/Base/SharedCSharp.template.tt

+30
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,34 @@ public string GetRequestMethodWithOptionsHeader()
314314
/// <returns>The built request.</returns>";
315315
}
316316

317+
// -------------------------------------------------------------
318+
// Methods used in MethodRequest.cs.tt and IMethodRequest.cs.tt for OData actions and functions.
319+
// -------------------------------------------------------------
320+
321+
/// <summary>
322+
/// Used in MethodRequest.cs.tt and IMethodRequest.cs.tt to get the ODataMethod*Response type
323+
/// defined in Microsoft.Graph.Core. Updates to supported OData primitives for OData methods
324+
/// needs to occur in MethodRequest.cs.tt, IMethodRequest.cs.tt, Microsoft.Graph.Core, and here.
325+
/// </summary>
326+
/// <param name="type"></param>
327+
/// <returns></returns>
328+
public string GetMethodRequestPrimitiveReturnTypeString(string type)
329+
{
330+
switch (type.ToLowerInvariant())
331+
{
332+
case "string":
333+
return "ODataMethodStringResponse";
334+
case "int32":
335+
return "ODataMethodIntResponse";
336+
case "boolean":
337+
case "bool":
338+
return "ODataMethodBooleanResponse";
339+
case "int64":
340+
return "ODataMethodLongResponse";
341+
default:
342+
return type;
343+
}
344+
}
345+
346+
317347
#>

Templates/CSharp/Requests/IMethodRequest.cs.tt

+24-1
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,42 @@ var requestType = entityName + methodName + "Request";
1818

1919
var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace);
2020
var returnEntityParameter = string.Empty;
21-
if (returnEntityType != null) {returnEntityParameter = returnEntityType.ToLower();}
21+
if (returnEntityType != null)
22+
{
23+
returnEntityParameter = returnEntityType.ToLower();
24+
25+
// Updates to supported OData primitives need to occur here,
26+
// IMethodRequest.cs.tt, Microsoft.Graph.Core, and in
27+
// GetMethodRequestPrimitiveReturnTypeString() in SharedCSharp.
28+
var tempReturnType = GetMethodRequestPrimitiveReturnTypeString(returnEntityType);
29+
30+
// These magic strings represent types in Microsoft.Graph.Core.
31+
// If the return type is a primitive, then make it nullable.
32+
if (tempReturnType == "ODataMethodIntResponse" ||
33+
tempReturnType == "ODataMethodBooleanResponse" ||
34+
tempReturnType == "ODataMethodLongResponse")
35+
{
36+
returnEntityType = returnEntityType + "?";
37+
}
38+
}
2239
var returnTypeObject = method.ReturnType == null ? null : method.ReturnType.AsOdcmClass();
2340

41+
42+
2443
var isCollection = method.IsCollection;
2544

2645
var sendAsyncReturnType = isCollection
2746
? "I" + entityName + methodName + "CollectionPage"
2847
: returnEntityType;
2948

49+
50+
3051
var methodReturnType = sendAsyncReturnType == null
3152
? "System.Threading.Tasks.Task"
3253
: "System.Threading.Tasks.Task<" + sendAsyncReturnType + ">";
3354

55+
56+
3457
bool hasParameters = method.Parameters != null && method.Parameters.Any();
3558
bool includeRequestBody = hasParameters && isAction;
3659
bool returnsStream = string.Equals(sendAsyncReturnType, "Stream");

Templates/CSharp/Requests/MethodRequest.cs.tt

+82-11
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,79 @@ var entityName = method.Class.Name.ToCheckedCase();
1616
var isFunction = method.IsFunction;
1717
var isAction = !isFunction;
1818
var isComposable = method.IsComposable;
19+
var isCollection = method.IsCollection;
1920

2021
var methodName = method.Name.Substring(method.Name.IndexOf('.') + 1).ToCheckedCase();
2122
var requestType = entityName + methodName + "Request";
2223

23-
var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace);
24-
2524
var returnEntityParameter = string.Empty;
26-
if (returnEntityType != null) {returnEntityParameter = returnEntityType.ToLower();}
2725

28-
var isCollection = method.IsCollection;
26+
// Represents the return of the SendAsync call within a public GetSync() or PostAsync() call.
27+
var sendAsyncReturnType = string.Empty;
2928

30-
var sendAsyncReturnType = isCollection
31-
? "I" + entityName + methodName + "CollectionPage"
32-
: returnEntityType;
29+
// Indicates whether the OData method returns an OData primitive (non-collection).
30+
// Collections of OData primitives is already supported.
31+
var isPrimitiveReturnType = false;
3332

34-
var methodReturnType = sendAsyncReturnType == null
35-
? "System.Threading.Tasks.Task"
36-
: "System.Threading.Tasks.Task<" + sendAsyncReturnType + ">";
33+
// Represents the return type of a GetAsync() or PostAsync() call.
34+
var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace);
35+
36+
// Set the SendAsync return type and determine whether we are working with an OData primitive.
37+
if (returnEntityType != null)
38+
{
39+
returnEntityParameter = returnEntityType.ToLower();
40+
if (isCollection)
41+
{
42+
sendAsyncReturnType = "I" + entityName + methodName + "CollectionPage";
43+
}
44+
else
45+
{
46+
// Updates to supported OData primitives need to occur here,
47+
// IMethodRequest.cs.tt, Microsoft.Graph.Core, and in
48+
// GetMethodRequestPrimitiveReturnTypeString() in SharedCSharp.
49+
sendAsyncReturnType = GetMethodRequestPrimitiveReturnTypeString(returnEntityType);
50+
51+
// These magic strings represent types in M.G.C.
52+
if (sendAsyncReturnType == "ODataMethodStringResponse" ||
53+
sendAsyncReturnType == "ODataMethodIntResponse" ||
54+
sendAsyncReturnType == "ODataMethodBooleanResponse" ||
55+
sendAsyncReturnType == "ODataMethodLongResponse")
56+
{
57+
isPrimitiveReturnType = true;
58+
}
59+
}
60+
}
61+
else
62+
{
63+
sendAsyncReturnType = returnEntityType;
64+
}
65+
66+
// Set the return type of the public GetSync() or PostAsync() call.
67+
var methodReturnType = string.Empty;
68+
if (sendAsyncReturnType == null)
69+
{
70+
methodReturnType = "System.Threading.Tasks.Task";
71+
}
72+
else
73+
{
74+
if (isCollection)
75+
{
76+
var collectionPage = "I" + entityName + methodName + "CollectionPage";
77+
methodReturnType = "System.Threading.Tasks.Task<" + collectionPage + ">";
78+
}
79+
else
80+
{
81+
var returnParameter = sendAsyncReturnType == "ODataMethodIntResponse" ||
82+
sendAsyncReturnType == "ODataMethodBooleanResponse" ||
83+
sendAsyncReturnType == "ODataMethodLongResponse" ? returnEntityType + "?"
84+
: returnEntityType;
85+
methodReturnType = "System.Threading.Tasks.Task<" + returnParameter + ">";
86+
}
87+
}
3788

3889
string methodOverloadReturnType = methodReturnType;
3990

40-
if (isCollection)
91+
if (isCollection || isPrimitiveReturnType)
4192
{
4293
methodReturnType = string.Concat("async ", methodReturnType);
4394
}
@@ -178,9 +229,19 @@ namespace <#=@namespace#>
178229
}
179230
else if (!string.IsNullOrEmpty(sendAsyncReturnType))
180231
{
232+
if (isPrimitiveReturnType)
233+
{
234+
#>
235+
var response = await this.SendAsync<<#=sendAsyncReturnType#>>(<#=methodParameter#>, cancellationToken);
236+
return response.Value;
237+
<#
238+
}
239+
else
240+
{
181241
#>
182242
return this.SendAsync<<#=sendAsyncReturnType#>>(<#=methodParameter#>, cancellationToken);
183243
<#
244+
}
184245
}
185246
else
186247
{
@@ -278,9 +339,19 @@ namespace <#=@namespace#>
278339
}
279340
else if (!string.IsNullOrEmpty(sendAsyncReturnType))
280341
{
342+
if (isPrimitiveReturnType)
343+
{
344+
#>
345+
var response = await this.SendAsync<<#=sendAsyncReturnType#>>(null, cancellationToken);
346+
return response.Value;
347+
<#
348+
}
349+
else
350+
{
281351
#>
282352
return this.SendAsync<<#=sendAsyncReturnType#>>(null, cancellationToken);
283353
<#
354+
}
284355
}
285356
else
286357
{

test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs

+37
Original file line numberDiff line numberDiff line change
@@ -555,5 +555,42 @@ public void It_transforms_metadata()
555555
Assert.IsTrue(hasContainsTargetBeenSet, $"The expected ContainsTarget attribute wasn't set in the transformed cleaned metadata.");
556556
Assert.IsFalse(hasCapabilityAnnotations, $"The expected capability annotations weren't removed in the transformed cleaned metadata.");
557557
}
558+
559+
[Test, RunInApplicationDomain]
560+
[TestCase("TestType2FunctionMethodWithStringRequest.cs", "var response = await this.SendAsync<ODataMethodStringResponse>(null, cancellationToken);")]
561+
[TestCase("TestType2FunctionMethodWithBooleanRequest.cs", "var response = await this.SendAsync<ODataMethodBooleanResponse>(null, cancellationToken);")]
562+
[TestCase("TestType2FunctionMethodWithInt32Request.cs", "var response = await this.SendAsync<ODataMethodIntResponse>(null, cancellationToken);")]
563+
[TestCase("TestType3ActionMethodWithInt64Request.cs", "var response = await this.SendAsync<ODataMethodLongResponse>(null, cancellationToken);")]
564+
public void It_creates_method_request_with_OData_return_type(string outputFileName, string testParameter)
565+
{
566+
const string outputDirectory = "output";
567+
568+
Options optionsCSharp = new Options()
569+
{
570+
Output = outputDirectory,
571+
Language = "CSharp",
572+
GenerationMode = GenerationMode.Files
573+
};
574+
575+
Generator.GenerateFiles(testMetadata, optionsCSharp);
576+
577+
FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Requests\" + outputFileName);
578+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
579+
580+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
581+
bool hasTestParameter = false;
582+
583+
foreach (var line in lines)
584+
{
585+
// We only need to check once.
586+
if (line.Contains(testParameter))
587+
{
588+
hasTestParameter = true;
589+
break;
590+
}
591+
}
592+
593+
Assert.IsTrue(hasTestParameter, $"The expected test token string, '{testParameter}', was not set in the generated test file. We didn't properly generate the SendAsync method.");
594+
}
558595
}
559596
}

test/Typewriter.Test/Resources/dirtyMetadata.xml

+17
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@
9292
<Parameter Name="Comment" Type="Edm.String" Unicode="false" />
9393
<Parameter Name="TestProperty" Type="Edm.String" Nullable="true"/>
9494
</Action>
95+
96+
<Function Name="FunctionMethodWithString" IsBound="true">
97+
<Parameter Name="bindparameter" Type="microsoft.graph.testType2" />
98+
<ReturnType Type="Edm.String" Unicode="false" />
99+
</Function>
100+
<Function Name="FunctionMethodWithBoolean" IsBound="true">
101+
<Parameter Name="bindparameter" Type="microsoft.graph.testType2" />
102+
<ReturnType Type="Edm.Boolean" Nullable="false" />
103+
</Function>
104+
<Function Name="FunctionMethodWithInt32" IsBound="true">
105+
<Parameter Name="bindparameter" Type="microsoft.graph.testType2" />
106+
<ReturnType Type="Edm.Int32" Nullable="false" />
107+
</Function>
108+
<Action Name="ActionMethodWithInt64" IsBound="true">
109+
<Parameter Name="bindingParameter" Type="microsoft.graph.testType3" />
110+
<ReturnType Type="Edm.Int64" Nullable="false" />
111+
</Action>
95112

96113
<EntityContainer Name="GraphService">
97114
<Singleton Name="testSingleton" Type="microsoft.graph.testSingleton"/>

0 commit comments

Comments
 (0)