in src/AutoRest.CSharp/LowLevel/Output/ConvenienceMethod.cs [26:234]
internal record ConvenienceMethod(MethodSignature Signature, IReadOnlyList<ProtocolToConvenienceParameterConverter> ProtocolToConvenienceParameterConverters, CSharpType? ResponseType, IReadOnlyList<string>? RequestMediaTypes, IReadOnlyList<string>? ResponseMediaTypes, Diagnostic? Diagnostic, bool IsPageable, bool IsLongRunning, string? Deprecated)
{
private record ConvenienceParameterInfo(Parameter? Convenience, ConvenienceParameterSpread? ConvenienceSpread);
public IEnumerable<MethodBodyStatement> GetConvertStatements(LowLevelClientMethod clientMethod, bool async, FieldDeclaration clientDiagnostics)
{
var protocolMethod = clientMethod.ProtocolMethodSignature;
var parametersDict = ProtocolToConvenienceParameterConverters.ToDictionary(converter => converter.Protocol.Name, converter => new ConvenienceParameterInfo(converter.Convenience, converter.ConvenienceSpread));
var protocolInvocationExpressions = new List<ValueExpression>();
VariableReference? content = null;
bool isMultipartOperation = RequestMediaTypes != null && RequestMediaTypes.Count == 1 && RequestMediaTypes.Contains("multipart/form-data");
foreach (var protocol in protocolMethod.Parameters)
{
var (convenience, convenienceSpread) = parametersDict[protocol.Name];
if (protocol == KnownParameters.RequestContext || protocol == KnownParameters.RequestContextRequired)
{
// convert cancellationToken to request context
// when the protocol needs a request context, we should either convert the cancellationToken to request context, or pass null if required, or do nothing if optional.
if (convenience != null)
{
// meaning we have a cancellationToken, we need to convert it
var context = new VariableReference(protocol.Type, protocol.Name);
// write: RequestContext context = FromCancellationToken(cancellationToken);
yield return Declare(context, new InvokeStaticMethodExpression(null, "FromCancellationToken", new ValueExpression[] { convenience }));
protocolInvocationExpressions.Add(context);
}
else
{
// if convenience parameter is null here, we just use the default value of the protocol parameter as value (we always do this even if the parameter is optional just in case there is an ordering issue)
protocolInvocationExpressions.Add(DefaultOf(protocol.Type));
}
}
else if (protocol == KnownParameters.RequestContent || protocol == KnownParameters.RequestContentNullable)
{
Debug.Assert(convenience is not null);
// convert body to request content
if (convenienceSpread == null)
{
content = new VariableReference(isMultipartOperation ? MultipartFormDataRequestContentProvider.Instance.Type : protocol.Type, protocol.Name);
yield return UsingDeclare(content, convenience.GetConversionToProtocol(protocol.Type, RequestMediaTypes?.FirstOrDefault()));
protocolInvocationExpressions.Add(content);
}
else
{
// write some statements to put the spread back into a local variable
yield return GetSpreadConversion(convenienceSpread, convenience, out var spreadExpression);
if (isMultipartOperation)
{
content = new VariableReference(MultipartFormDataRequestContentProvider.Instance.Type, protocol.Name);
yield return UsingDeclare(content, spreadExpression.ToMultipartRequestContent());
protocolInvocationExpressions.Add(content);
}
else
{
protocolInvocationExpressions.Add(spreadExpression.ToRequestContent());
}
}
}
else
{
// process any other parameter
if (convenience != null)
{
// if convenience parameter is not null here, we convert it to protocol parameter value properly
var expression = convenience.GetConversionToProtocol(protocol.Type, RequestMediaTypes?.FirstOrDefault());
protocolInvocationExpressions.Add(expression);
}
else
{
// if convenience parameter is null here, we just use the default value of the protocol parameter as value (we always do this even if the parameter is optional just in case there is an ordering issue)
if (isMultipartOperation && protocol.Name == "contentType" && content != null)
{
protocolInvocationExpressions.Add(MultipartFormDataRequestContentProvider.Instance.ContentTypeProperty(content));
}
else
{
protocolInvocationExpressions.Add(DefaultOf(protocol.Type));
}
}
}
}
// call the protocol method using the expressions we have for the parameters in protocol method
var invocation = new InvokeInstanceMethodExpression(null, protocolMethod.WithAsync(async), protocolInvocationExpressions);
// handles the response
// TODO -- currently we only need to build response handling for long running method and normal method
if (IsPageable)
throw new NotImplementedException("Statements for handling pageable convenience method has not been implemented yet");
if (IsLongRunning)
{
if (ResponseType == null)
{
// return [await] protocolMethod(parameters...)[.ConfigureAwait(false)];
yield return Return(invocation);
}
else
{
// Operation<BinaryData> response = [await] protocolMethod(parameters...)[.ConfigureAwait(false)];
var response = new VariableReference(protocolMethod.ReturnType!, Configuration.ApiTypes.ResponseParameterName);
yield return Declare(response, invocation);
// return ProtocolOperationHelpers.Convert(response, r => responseType.FromResponse(r), ClientDiagnostics, scopeName);
var diagnostic = Diagnostic ?? clientMethod.ProtocolMethodDiagnostic;
yield return Return(new InvokeStaticMethodExpression(
typeof(ProtocolOperationHelpers),
nameof(ProtocolOperationHelpers.Convert),
new ValueExpression[]
{
response,
GetLongRunningConversionMethod(clientMethod.LongRunningResultRetrievalMethod, ResponseType),
clientDiagnostics,
Literal(diagnostic.ScopeName)
}));
}
}
else
{
var response = new VariableReference(protocolMethod.ReturnType!, Configuration.ApiTypes.ResponseParameterName);
yield return Declare(response, invocation);
// handle normal responses
if (ResponseType is null)
{
yield return Return(response);
}
else if (ResponseType is { IsFrameworkType: false, Implementation: SerializableObjectType { Serialization.Json: { } } serializableObjectType })
{
yield return Return(Extensible.RestOperations.GetTypedResponseFromModel(serializableObjectType, response));
}
else if (ResponseType is { IsFrameworkType: false, Implementation: EnumType enumType })
{
yield return Return(Extensible.RestOperations.GetTypedResponseFromEnum(enumType, response));
}
else if (ResponseType.IsCollection)
{
Debug.Assert(clientMethod.ResponseBodyType is not null);
var serializationFormat = SerializationBuilder.GetSerializationFormat(clientMethod.ResponseBodyType, ResponseType);
var serialization = SerializationBuilder.BuildJsonSerialization(clientMethod.ResponseBodyType, ResponseType, false, serializationFormat);
var value = new VariableReference(ResponseType, "value");
yield return new[]
{
Declare(value, Default),
JsonSerializationMethodsBuilder.BuildDeserializationForMethods(serialization, async, value, Extensible.RestOperations.GetContentStream(response), false, null),
Return(Extensible.RestOperations.GetTypedResponseFromValue(value, response))
};
}
else if (ResponseType is { IsFrameworkType: true })
{
yield return Return(Extensible.RestOperations.GetTypedResponseFromBinaryData(ResponseType.FrameworkType, response, ResponseMediaTypes?.FirstOrDefault()));
}
}
}
private ValueExpression GetLongRunningConversionMethod(LongRunningResultRetrievalMethod? convertMethod, CSharpType responseType)
{
if (convertMethod is null)
{
return new FormattableStringToExpression($"{responseType}.FromResponse");
}
return new FormattableStringToExpression($"{convertMethod.MethodSignature.Name}");
}
private MethodBodyStatement GetSpreadConversion(ConvenienceParameterSpread convenienceSpread, Parameter convenience, out SerializableObjectTypeExpression spread)
{
var backingModel = convenienceSpread.BackingModel;
var spreadVar = new VariableReference(convenienceSpread.BackingModel.Type, convenience.Name);
var arguments = new List<ValueExpression>();
var ctorSignature = backingModel.SerializationConstructor.Signature;
var parametersDict = convenienceSpread.SpreadedParameters.ToDictionary(p => p.Name);
foreach (var ctorParameter in ctorSignature.Parameters)
{
if (parametersDict.TryGetValue(ctorParameter.Name, out var parameter))
{
// we have a value in the spread parameter list for a ctor parameter
ValueExpression expression = new ParameterReference(parameter);
if (parameter.Type.IsList)
{
expression = expression.GetConversion(parameter.Type, ctorParameter.Type);
if (parameter.Validation is not ValidationType.AssertNotNull or ValidationType.AssertNotNullOrEmpty)
{
// we have to add a cast otherwise this will not compile
// this could compile: a?.ToList() as IList<string> ?? new ChangeTrackingList<string>()
// this will not compile: a?.ToList() ?? new ChangeTrackingList<string>()
expression = NullCoalescing(new AsExpression(expression, ctorParameter.Type), New.Instance(parameter.Type.PropertyInitializationType));
}
}
else if (parameter.Type.IsDictionary)
{
if (parameter.Validation is not ValidationType.AssertNotNull or ValidationType.AssertNotNullOrEmpty)
{
expression = NullCoalescing(expression, New.Instance(parameter.Type.PropertyInitializationType));
}
}
// add the argument into the list
arguments.Add(expression);
}
else
{
// we do not have this ctor parameter in the spread parameter list, this should only happen to the raw data field parameter
arguments.Add(ctorParameter.Type.IsValueType ? Default.CastTo(ctorParameter.Type) : Null.CastTo(ctorParameter.Type));
}
}
spread = new SerializableObjectTypeExpression(backingModel, spreadVar);
return Declare(spreadVar, New.Instance(ctorSignature, arguments));
}
}