Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dpg): support logicalPath of lro #3839

Merged
merged 14 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/AutoRest.CSharp/Common/Input/CodeModelConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ public List<InputParameter> CreateOperationParameters(IReadOnlyCollection<Reques

return new OperationLongRunning(
FinalStateVia: operation.LongRunningFinalStateVia,
FinalResponse: CreateOperationResponse(operation.LongRunningFinalResponse)
FinalResponse: CreateOperationResponse(operation.LongRunningFinalResponse),
ResultPath: null
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,29 @@
// Licensed under the MIT License.

using Azure.Core;
using System.Linq;

namespace AutoRest.CSharp.Common.Input;

internal record OperationLongRunning(OperationFinalStateVia FinalStateVia, OperationResponse FinalResponse)
internal record OperationLongRunning(OperationFinalStateVia FinalStateVia, OperationResponse FinalResponse, string? ResultPath)
{
public OperationLongRunning() : this(FinalStateVia: OperationFinalStateVia.Location, FinalResponse: new OperationResponse()) { }
public OperationLongRunning() : this(FinalStateVia: OperationFinalStateVia.Location, FinalResponse: new OperationResponse(), null) { }

/// <summary>
/// Meaningful return type of the long running operation.
/// </summary>
public InputType? ReturnType
{
get
{
if (FinalResponse.BodyType is null)
return null;

if (ResultPath is null)
return FinalResponse.BodyType;

var rawResponseType = (InputModelType)FinalResponse.BodyType;
return rawResponseType.Properties.FirstOrDefault(p => p.SerializedName == ResultPath)!.Type;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ protected LongRunningOperation(InputOperation operation, BuildContext context, L
FinalStateVia = operation.LongRunning.FinalStateVia;

var finalResponse = operation.LongRunning.FinalResponse;
var finalResponseType = finalResponse.BodyType;
var returnType = operation.LongRunning.ReturnType;

if (finalResponseType != null)
if (returnType != null)
{
ResultType = TypeFactory.GetOutputType(context.TypeFactory.CreateType(finalResponseType with {IsNullable = false}));
ResultSerialization = SerializationBuilder.Build(finalResponse.BodyMediaType, finalResponseType, ResultType, null);
ResultType = TypeFactory.GetOutputType(context.TypeFactory.CreateType(returnType with {IsNullable = false}));
ResultSerialization = SerializationBuilder.Build(finalResponse.BodyMediaType, returnType, ResultType, null);

var paging = operation.Paging;
if (paging != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using AutoRest.CSharp.Generation.Types;
using AutoRest.CSharp.Output.Models;
using AutoRest.CSharp.Output.Models.Shared;

namespace AutoRest.CSharp.Common.Output.Models
{
/// <summary>
/// Method to fetch return value specified by `logicalPath` from raw response.
/// </summary>
/// <param name="ReturnType">Type of the return value specified by <see cref="ResultPath"/>.</param>
/// <param name="ResponseTypeName">Name of the raw response type.</param>
/// <param name="ResultPath">`logicalPath`.</param>
internal record LongRunningResultRetrievalMethod(CSharpType ReturnType, string ResponseTypeName, string ResultPath)
{
public MethodSignature MethodSignature => new(
Name: $"Fetch{ReturnType.Name}From{ResponseTypeName}",
Summary: null,
Description: null,
Modifiers: MethodSignatureModifiers.Private,
ReturnType: ReturnType,
ReturnDescription: null,
Parameters: new List<Parameter>() { KnownParameters.Response }
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal static class KnownParameters
private static readonly CSharpType RequestContentNullableType = new(Configuration.ApiTypes.RequestContentType, true);
private static readonly CSharpType RequestContextType = new(Configuration.ApiTypes.RequestContextType);
private static readonly CSharpType RequestContextNullableType = new(Configuration.ApiTypes.RequestContextType, true);
private static readonly CSharpType ResponseType = new(Configuration.ApiTypes.ResponseType);

public static readonly Parameter ClientDiagnostics = new("clientDiagnostics", $"The handler for diagnostic messaging in the client.", new CSharpType(Configuration.ApiTypes.ClientDiagnosticsType), null, ValidationType.AssertNotNull, null);
public static readonly Parameter Pipeline = new("pipeline", $"The HTTP pipeline for sending and receiving REST requests and responses", new CSharpType(Configuration.ApiTypes.HttpPipelineType), null, ValidationType.AssertNotNull, null);
Expand All @@ -42,5 +43,7 @@ internal static class KnownParameters

public static readonly Parameter CancellationTokenParameter = new("cancellationToken", $"The cancellation token to use", new CSharpType(typeof(CancellationToken)), Constant.NewInstanceOf(typeof(CancellationToken)), ValidationType.None, null);
public static readonly Parameter EnumeratorCancellationTokenParameter = new("cancellationToken", $"Enumerator cancellation token", typeof(CancellationToken), Constant.NewInstanceOf(typeof(CancellationToken)), ValidationType.None, null) { Attributes = new[] { new CSharpAttribute(typeof(EnumeratorCancellationAttribute)) } };

public static readonly Parameter Response = new("response", $"Response returned from backend service", ResponseType, null, ValidationType.None, null);
}
}
31 changes: 30 additions & 1 deletion src/AutoRest.CSharp/LowLevel/Generation/DpgClientWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using AutoRest.CSharp.Common.Generation.Writers;
using AutoRest.CSharp.Common.Input;
Expand Down Expand Up @@ -97,10 +98,20 @@ public void WriteClient()
WriteCancellationTokenToRequestContextMethod();
}
WriteResponseClassifierMethod(_writer, _client.ResponseClassifierTypes);
WriteLongRunningResultRetrievalMethods();
}
}
}

private void WriteLongRunningResultRetrievalMethods()
{
foreach (var method in _client.ClientMethods.Select(c => c.LongRunningResultRetrievalMethod).WhereNotNull())
{
_writer.Line();
WriteLroResultRetrievalMethod(method);
}
}

public static void WriteProtocolMethods(CodeWriter writer, ClientFields fields, LowLevelClientMethod clientMethod)
{
WriteRequestCreationMethod(writer, clientMethod.RequestMethod, fields);
Expand Down Expand Up @@ -357,12 +368,30 @@ private void WriteConvenienceLroMethod(LowLevelClientMethod clientMethod, Conven
.LineRaw(";");
// return ProtocolOperationHelpers.Convert(response, r => responseType.FromResponse(r), ClientDiagnostics, scopeName);
var diagnostic = convenienceMethod.Diagnostic ?? clientMethod.ProtocolMethodDiagnostic;
_writer.Line($"return {typeof(ProtocolOperationHelpers)}.{nameof(ProtocolOperationHelpers.Convert)}({responseVariable:I}, {responseType}.FromResponse, {fields.ClientDiagnosticsProperty.Name}, {diagnostic.ScopeName:L});");
_writer.Line($"return {typeof(ProtocolOperationHelpers)}.{nameof(ProtocolOperationHelpers.Convert)}({responseVariable:I}, {GetConversionMethodStatement(clientMethod.LongRunningResultRetrievalMethod, responseType)}, {fields.ClientDiagnosticsProperty.Name}, {diagnostic.ScopeName:L});");
}
}
_writer.Line();
}

private FormattableString GetConversionMethodStatement(LongRunningResultRetrievalMethod? convertMethod, CSharpType responseType)
{
if (convertMethod is null)
{
return $"{responseType}.FromResponse";
}
return $"{convertMethod.MethodSignature.Name}";
}

private void WriteLroResultRetrievalMethod(LongRunningResultRetrievalMethod method)
{
using (_writer.WriteMethodDeclaration(method.MethodSignature))
{
_writer.Line($"var resultJsonElement = {typeof(JsonDocument)}.{nameof(JsonDocument.Parse)}(response.{nameof(Response.Content)}).{nameof(JsonDocument.RootElement)}.{nameof(JsonElement.GetProperty)}(\"{method.ResultPath}\");");
_writer.Line($"return {method.ReturnType}.Deserialize{method.ReturnType.Name}(resultJsonElement);");
}
}

private void WriteConveniencePageableMethod(LowLevelClientMethod clientMethod, ConvenienceMethod convenienceMethod, ProtocolMethodPaging pagingInfo, ClientFields fields, bool async)
{
_writer.WritePageable(convenienceMethod, clientMethod.RequestMethod, pagingInfo.NextPageMethod, fields.ClientDiagnosticsProperty, fields.PipelineField, clientMethod.ProtocolMethodDiagnostic.ScopeName, pagingInfo.ItemName, pagingInfo.NextLinkName, async);
Expand Down
4 changes: 3 additions & 1 deletion src/AutoRest.CSharp/LowLevel/Output/LowLevelClientMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ internal record LowLevelClientMethod(
OperationLongRunning? LongRunning,
RequestConditionHeaders ConditionHeaderFlag,
IEnumerable<DpgOperationSample> Samples,
ConvenienceMethodOmittingMessage? ConvenienceMethodOmittingMessage)
ConvenienceMethodOmittingMessage? ConvenienceMethodOmittingMessage,
LongRunningResultRetrievalMethod? LongRunningResultRetrievalMethod) // TODO: move `LongRunningResultRetrievalMethod` under output model of long running, currently we're using
// input model of long running in DPG
{
public bool ShouldGenerateConvenienceMethodRef()
{
Expand Down
12 changes: 10 additions & 2 deletions src/AutoRest.CSharp/LowLevel/Output/OperationMethodChainBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public LowLevelClientMethod BuildOperationMethodChain()
// samples will build below
var samples = new List<DpgOperationSample>();

var method = new LowLevelClientMethod(protocolMethodSignature, convenienceMethod, _restClientMethod, requestBodyType, responseBodyType, diagnostic, _protocolMethodPaging, Operation.LongRunning, _conditionHeaderFlag, samples, convenienceMethodInfo.Message);
var method = new LowLevelClientMethod(protocolMethodSignature, convenienceMethod, _restClientMethod, requestBodyType, responseBodyType, diagnostic, _protocolMethodPaging, Operation.LongRunning, _conditionHeaderFlag, samples, convenienceMethodInfo.Message, GetLongRunningResultRetrievalMethod(Operation.LongRunning));

BuildSamples(method, samples);

Expand Down Expand Up @@ -162,6 +162,14 @@ private void BuildSamples(LowLevelClientMethod method, List<DpgOperationSample>
}
}

private LongRunningResultRetrievalMethod? GetLongRunningResultRetrievalMethod(OperationLongRunning? longRunning)
{
if (longRunning is { ResultPath: not null })
return new(_typeFactory.CreateType(longRunning.ReturnType!), longRunning.FinalResponse.BodyType!.Name, longRunning.ResultPath);

return null;
}

private ConvenienceMethodGenerationInfo ShouldGenerateConvenienceMethod()
{
// we do not generate convenience method if the emitter does not allow it.
Expand Down Expand Up @@ -327,7 +335,7 @@ private ReturnTypeChain BuildReturnTypes()
{
if (Operation.LongRunning != null)
{
return Operation.LongRunning.FinalResponse.BodyType;
return Operation.LongRunning.ReturnType;
}

var operationBodyTypes = Operation.Responses.Where(r => !r.IsErrorResponse).Select(r => r.BodyType).Distinct();
Expand Down
16 changes: 14 additions & 2 deletions src/TypeSpec.Extension/Emitter.Csharp/src/lib/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,11 @@ export function loadOperation(
if (op.verb !== "delete") {
const formattedType = getFormattedType(
program,
metadata.logicalResult
// TODO: we should check `logicalPath` or other ways to determine body type,
// after https://github.com/Azure/typespec-azure/issues/3725 is fixed
op.verb === "post"
archerzz marked this conversation as resolved.
Show resolved Hide resolved
? metadata.envelopeResult
: metadata.logicalResult
);
bodyType = getInputType(context, formattedType, models, enums);
}
Expand All @@ -401,7 +405,15 @@ export function loadOperation(
StatusCodes: op.verb === "delete" ? [204] : [200],
BodyType: bodyType,
BodyMediaType: BodyMediaType.Json
} as OperationResponse
} as OperationResponse,
ResultPath:
metadata.logicalPath ??
// TODO: roll back changes when `logicalPath` can be definitive
// https://github.com/Azure/typespec-azure/issues/3725
archerzz marked this conversation as resolved.
Show resolved Hide resolved
(metadata.envelopeResult != metadata.logicalResult &&
op.verb === "post"
? "result" // actually `result` is the only allowed path for now
: undefined)
} as OperationLongRunning;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ import { OperationResponse } from "./operationResponse.js";
export interface OperationLongRunning {
FinalStateVia: OperationFinalStateVia;
FinalResponse: OperationResponse;
ResultPath?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ model Pet {

interface Projects {
@doc("Creates a new pet or updates an existing one.")
createOrUpdate is LongRunningResourceCreateOrUpdate<Pet>;
createOrUpdate is LongRunningResourceCreateOrReplace<Pet>;
archerzz marked this conversation as resolved.
Show resolved Hide resolved
}
Loading