Skip to content

Commit

Permalink
Fix OData methods with OData primitive return type (#300) (#307)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
MIchaelMainer authored Oct 6, 2020
1 parent 3379cb6 commit 981d48c
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 12 deletions.
30 changes: 30 additions & 0 deletions Templates/CSharp/Base/SharedCSharp.template.tt
Original file line number Diff line number Diff line change
Expand Up @@ -314,4 +314,34 @@ public string GetRequestMethodWithOptionsHeader()
/// <returns>The built request.</returns>";
}

// -------------------------------------------------------------
// Methods used in MethodRequest.cs.tt and IMethodRequest.cs.tt for OData actions and functions.
// -------------------------------------------------------------

/// <summary>
/// Used in MethodRequest.cs.tt and IMethodRequest.cs.tt to get the ODataMethod*Response type
/// defined in Microsoft.Graph.Core. Updates to supported OData primitives for OData methods
/// needs to occur in MethodRequest.cs.tt, IMethodRequest.cs.tt, Microsoft.Graph.Core, and here.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public string GetMethodRequestPrimitiveReturnTypeString(string type)
{
switch (type.ToLowerInvariant())
{
case "string":
return "ODataMethodStringResponse";
case "int32":
return "ODataMethodIntResponse";
case "boolean":
case "bool":
return "ODataMethodBooleanResponse";
case "int64":
return "ODataMethodLongResponse";
default:
return type;
}
}


#>
25 changes: 24 additions & 1 deletion Templates/CSharp/Requests/IMethodRequest.cs.tt
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,42 @@ var requestType = entityName + methodName + "Request";

var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace);
var returnEntityParameter = string.Empty;
if (returnEntityType != null) {returnEntityParameter = returnEntityType.ToLower();}
if (returnEntityType != null)
{
returnEntityParameter = returnEntityType.ToLower();

// Updates to supported OData primitives need to occur here,
// IMethodRequest.cs.tt, Microsoft.Graph.Core, and in
// GetMethodRequestPrimitiveReturnTypeString() in SharedCSharp.
var tempReturnType = GetMethodRequestPrimitiveReturnTypeString(returnEntityType);

// These magic strings represent types in Microsoft.Graph.Core.
// If the return type is a primitive, then make it nullable.
if (tempReturnType == "ODataMethodIntResponse" ||
tempReturnType == "ODataMethodBooleanResponse" ||
tempReturnType == "ODataMethodLongResponse")
{
returnEntityType = returnEntityType + "?";
}
}
var returnTypeObject = method.ReturnType == null ? null : method.ReturnType.AsOdcmClass();



var isCollection = method.IsCollection;

var sendAsyncReturnType = isCollection
? "I" + entityName + methodName + "CollectionPage"
: returnEntityType;



var methodReturnType = sendAsyncReturnType == null
? "System.Threading.Tasks.Task"
: "System.Threading.Tasks.Task<" + sendAsyncReturnType + ">";



bool hasParameters = method.Parameters != null && method.Parameters.Any();
bool includeRequestBody = hasParameters && isAction;
bool returnsStream = string.Equals(sendAsyncReturnType, "Stream");
Expand Down
93 changes: 82 additions & 11 deletions Templates/CSharp/Requests/MethodRequest.cs.tt
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,79 @@ var entityName = method.Class.Name.ToCheckedCase();
var isFunction = method.IsFunction;
var isAction = !isFunction;
var isComposable = method.IsComposable;
var isCollection = method.IsCollection;

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

var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace);

var returnEntityParameter = string.Empty;
if (returnEntityType != null) {returnEntityParameter = returnEntityType.ToLower();}

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

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

var methodReturnType = sendAsyncReturnType == null
? "System.Threading.Tasks.Task"
: "System.Threading.Tasks.Task<" + sendAsyncReturnType + ">";
// Represents the return type of a GetAsync() or PostAsync() call.
var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace);

// Set the SendAsync return type and determine whether we are working with an OData primitive.
if (returnEntityType != null)
{
returnEntityParameter = returnEntityType.ToLower();
if (isCollection)
{
sendAsyncReturnType = "I" + entityName + methodName + "CollectionPage";
}
else
{
// Updates to supported OData primitives need to occur here,
// IMethodRequest.cs.tt, Microsoft.Graph.Core, and in
// GetMethodRequestPrimitiveReturnTypeString() in SharedCSharp.
sendAsyncReturnType = GetMethodRequestPrimitiveReturnTypeString(returnEntityType);

// These magic strings represent types in M.G.C.
if (sendAsyncReturnType == "ODataMethodStringResponse" ||
sendAsyncReturnType == "ODataMethodIntResponse" ||
sendAsyncReturnType == "ODataMethodBooleanResponse" ||
sendAsyncReturnType == "ODataMethodLongResponse")
{
isPrimitiveReturnType = true;
}
}
}
else
{
sendAsyncReturnType = returnEntityType;
}

// Set the return type of the public GetSync() or PostAsync() call.
var methodReturnType = string.Empty;
if (sendAsyncReturnType == null)
{
methodReturnType = "System.Threading.Tasks.Task";
}
else
{
if (isCollection)
{
var collectionPage = "I" + entityName + methodName + "CollectionPage";
methodReturnType = "System.Threading.Tasks.Task<" + collectionPage + ">";
}
else
{
var returnParameter = sendAsyncReturnType == "ODataMethodIntResponse" ||
sendAsyncReturnType == "ODataMethodBooleanResponse" ||
sendAsyncReturnType == "ODataMethodLongResponse" ? returnEntityType + "?"
: returnEntityType;
methodReturnType = "System.Threading.Tasks.Task<" + returnParameter + ">";
}
}

string methodOverloadReturnType = methodReturnType;

if (isCollection)
if (isCollection || isPrimitiveReturnType)
{
methodReturnType = string.Concat("async ", methodReturnType);
}
Expand Down Expand Up @@ -178,9 +229,19 @@ namespace <#=@namespace#>
}
else if (!string.IsNullOrEmpty(sendAsyncReturnType))
{
if (isPrimitiveReturnType)
{
#>
var response = await this.SendAsync<<#=sendAsyncReturnType#>>(<#=methodParameter#>, cancellationToken);
return response.Value;
<#
}
else
{
#>
return this.SendAsync<<#=sendAsyncReturnType#>>(<#=methodParameter#>, cancellationToken);
<#
}
}
else
{
Expand Down Expand Up @@ -278,9 +339,19 @@ namespace <#=@namespace#>
}
else if (!string.IsNullOrEmpty(sendAsyncReturnType))
{
if (isPrimitiveReturnType)
{
#>
var response = await this.SendAsync<<#=sendAsyncReturnType#>>(null, cancellationToken);
return response.Value;
<#
}
else
{
#>
return this.SendAsync<<#=sendAsyncReturnType#>>(null, cancellationToken);
<#
}
}
else
{
Expand Down
37 changes: 37 additions & 0 deletions test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,5 +555,42 @@ public void It_transforms_metadata()
Assert.IsTrue(hasContainsTargetBeenSet, $"The expected ContainsTarget attribute wasn't set in the transformed cleaned metadata.");
Assert.IsFalse(hasCapabilityAnnotations, $"The expected capability annotations weren't removed in the transformed cleaned metadata.");
}

[Test, RunInApplicationDomain]
[TestCase("TestType2FunctionMethodWithStringRequest.cs", "var response = await this.SendAsync<ODataMethodStringResponse>(null, cancellationToken);")]
[TestCase("TestType2FunctionMethodWithBooleanRequest.cs", "var response = await this.SendAsync<ODataMethodBooleanResponse>(null, cancellationToken);")]
[TestCase("TestType2FunctionMethodWithInt32Request.cs", "var response = await this.SendAsync<ODataMethodIntResponse>(null, cancellationToken);")]
[TestCase("TestType3ActionMethodWithInt64Request.cs", "var response = await this.SendAsync<ODataMethodLongResponse>(null, cancellationToken);")]
public void It_creates_method_request_with_OData_return_type(string outputFileName, string testParameter)
{
const string outputDirectory = "output";

Options optionsCSharp = new Options()
{
Output = outputDirectory,
Language = "CSharp",
GenerationMode = GenerationMode.Files
};

Generator.GenerateFiles(testMetadata, optionsCSharp);

FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Requests\" + outputFileName);
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");

IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
bool hasTestParameter = false;

foreach (var line in lines)
{
// We only need to check once.
if (line.Contains(testParameter))
{
hasTestParameter = true;
break;
}
}

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.");
}
}
}
17 changes: 17 additions & 0 deletions test/Typewriter.Test/Resources/dirtyMetadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@
<Parameter Name="Comment" Type="Edm.String" Unicode="false" />
<Parameter Name="TestProperty" Type="Edm.String" Nullable="true"/>
</Action>

<Function Name="FunctionMethodWithString" IsBound="true">
<Parameter Name="bindparameter" Type="microsoft.graph.testType2" />
<ReturnType Type="Edm.String" Unicode="false" />
</Function>
<Function Name="FunctionMethodWithBoolean" IsBound="true">
<Parameter Name="bindparameter" Type="microsoft.graph.testType2" />
<ReturnType Type="Edm.Boolean" Nullable="false" />
</Function>
<Function Name="FunctionMethodWithInt32" IsBound="true">
<Parameter Name="bindparameter" Type="microsoft.graph.testType2" />
<ReturnType Type="Edm.Int32" Nullable="false" />
</Function>
<Action Name="ActionMethodWithInt64" IsBound="true">
<Parameter Name="bindingParameter" Type="microsoft.graph.testType3" />
<ReturnType Type="Edm.Int64" Nullable="false" />
</Action>

<EntityContainer Name="GraphService">
<Singleton Name="testSingleton" Type="microsoft.graph.testSingleton"/>
Expand Down

0 comments on commit 981d48c

Please sign in to comment.