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 3 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
1 change: 1 addition & 0 deletions src/AutoRest.CSharp/Common/Input/CodeModelConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public List<InputParameter> CreateOperationParameters(IReadOnlyCollection<Reques
public OperationResponse CreateOperationResponse(ServiceResponse response) => new(
StatusCodes: response.HttpResponse.IntStatusCodes.ToList(),
BodyType: GetResponseBodyType(response),
ResultPath: null,
BodyMediaType: GetBodyFormat(response.HttpResponse.KnownMediaType),
Headers: GetResponseHeaders(response.HttpResponse.Headers),
IsErrorResponse: false
Expand Down
24 changes: 22 additions & 2 deletions src/AutoRest.CSharp/Common/Input/InputTypes/OperationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,30 @@

using System;
using System.Collections.Generic;
using System.Linq;
using AutoRest.CSharp.Utilities;

namespace AutoRest.CSharp.Common.Input;

internal record OperationResponse(IReadOnlyList<int> StatusCodes, InputType? BodyType, BodyMediaType BodyMediaType, IReadOnlyList<OperationResponseHeader> Headers, bool IsErrorResponse)
internal record OperationResponse(IReadOnlyList<int> StatusCodes, InputType? BodyType, BodyMediaType BodyMediaType, IReadOnlyList<OperationResponseHeader> Headers, bool IsErrorResponse, string? ResultPath)
{
public OperationResponse() : this(StatusCodes: Array.Empty<int>(), BodyType: null, BodyMediaType: BodyMediaType.None, Headers: Array.Empty<OperationResponseHeader>(), IsErrorResponse: false) { }
public OperationResponse() : this(StatusCodes: Array.Empty<int>(), BodyType: null, BodyMediaType: BodyMediaType.None, Headers: Array.Empty<OperationResponseHeader>(), IsErrorResponse: false, ResultPath: null) { }
archerzz marked this conversation as resolved.
Show resolved Hide resolved

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

if (ResultPath is null)
return BodyType;

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

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

if (finalResponseType != null)
{
Expand Down
16 changes: 15 additions & 1 deletion src/AutoRest.CSharp/LowLevel/Generation/DpgClientWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,26 @@ 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}, {GetConvertMethodStatement(clientMethod.LongRunning!, responseType)}, {fields.ClientDiagnosticsProperty.Name}, {diagnostic.ScopeName:L});");
}
}
_writer.Line();
}

private string GetConvertMethodStatement(OperationLongRunning longRunning, CSharpType responseType)
archerzz marked this conversation as resolved.
Show resolved Hide resolved
{
if (longRunning.FinalResponse.ResultPath.IsNullOrEmpty())
{
return $"{responseType.Name}.FromResponse";
}

var bodyType = longRunning.FinalResponse.BodyType!;
return @$"r => {{
{bodyType.Name} rawResponse = {bodyType.Name}.FromResponse(r);
archerzz marked this conversation as resolved.
Show resolved Hide resolved
return rawResponse.{longRunning.FinalResponse.ResultPath!.ToCleanName()};
}}";
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ private ReturnTypeChain BuildReturnTypes()
{
if (Operation.LongRunning != null)
{
return Operation.LongRunning.FinalResponse.BodyType;
return Operation.LongRunning.FinalResponse.ReturnType;
}

var operationBodyTypes = Operation.Responses.Where(r => !r.IsErrorResponse).Select(r => r.BodyType).Distinct();
Expand Down
13 changes: 12 additions & 1 deletion 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 @@ -400,6 +404,13 @@ export function loadOperation(
// for now, let assume we don't allow return type
StatusCodes: op.verb === "delete" ? [204] : [200],
BodyType: bodyType,
ResultPath:
metadata.logicalPath ??
// https://github.com/Azure/typespec-azure/issues/3725
(metadata.envelopeResult != metadata.logicalResult &&
op.verb === "post"
? "result" // actually `result` is the only allowed path for now
AlexanderSher marked this conversation as resolved.
Show resolved Hide resolved
: undefined),
BodyMediaType: BodyMediaType.Json
} as OperationResponse
} as OperationLongRunning;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface OperationResponse {
BodyMediaType: BodyMediaType;
Headers: HttpResponseHeader[];
IsErrorResponse: boolean;
ResultPath?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"Name": "Pet",
"Namespace": "LroOperation",
"IsNullable": false,
"Usage": "None",
"Usage": "RoundTrip",
"Properties": [
{
"$id": "3",
Expand Down Expand Up @@ -143,62 +143,55 @@
},
{
"$id": "19",
"Name": "contentType",
"NameInRequest": "Content-Type",
"Description": "This request has a JSON Merge Patch body.",
"Name": "resource",
"NameInRequest": "resource",
"Description": "The resource instance.",
"Type": {
"$id": "20",
"Name": "Literal",
"LiteralValueType": {
"$id": "21",
"Name": "String",
"Kind": "String",
"IsNullable": false
},
"Value": "application/merge-patch+json",
"IsNullable": false
},
"Location": "Header",
"DefaultValue": {
"$id": "22",
"Type": {
"$ref": "20"
},
"Value": "application/merge-patch+json"
"$ref": "2"
},
"Location": "Body",
"IsRequired": true,
"IsApiVersion": false,
"IsResourceParameter": false,
"IsContentType": true,
"IsContentType": false,
"IsEndpoint": false,
"SkipUrlEncoding": false,
"Explode": false,
"Kind": "Constant"
"Kind": "Method"
},
{
"$id": "23",
"Name": "resource",
"NameInRequest": "resource",
"Description": "The resource instance.",
"$id": "20",
"Name": "contentType",
"NameInRequest": "Content-Type",
"Type": {
"$ref": "2"
"$id": "21",
"Name": "String",
"Kind": "String",
"IsNullable": false
},
"Location": "Body",
"IsRequired": true,
"Location": "Header",
"IsApiVersion": false,
"IsResourceParameter": false,
"IsContentType": false,
"IsContentType": true,
"IsRequired": true,
"IsEndpoint": false,
"SkipUrlEncoding": false,
"Explode": false,
"Kind": "Method"
"Kind": "Constant",
"DefaultValue": {
"$id": "22",
"Type": {
"$ref": "21"
},
"Value": "application/json"
}
},
{
"$id": "24",
"$id": "23",
"Name": "accept",
"NameInRequest": "Accept",
"Type": {
"$id": "25",
"$id": "24",
"Name": "String",
"Kind": "String",
"IsNullable": false
Expand All @@ -213,17 +206,17 @@
"Explode": false,
"Kind": "Constant",
"DefaultValue": {
"$id": "26",
"$id": "25",
"Type": {
"$ref": "25"
"$ref": "24"
},
"Value": "application/json"
}
}
],
"Responses": [
{
"$id": "27",
"$id": "26",
"StatusCodes": [
201
],
Expand All @@ -233,12 +226,12 @@
"BodyMediaType": "Json",
"Headers": [
{
"$id": "28",
"$id": "27",
"Name": "Operation-Location",
"NameInResponse": "operationLocation",
"Description": "The location for monitoring the operation state.",
"Type": {
"$id": "29",
"$id": "28",
"Name": "ResourceLocation",
"Kind": "Uri",
"IsNullable": false
Expand All @@ -248,7 +241,7 @@
"IsErrorResponse": false
},
{
"$id": "30",
"$id": "29",
"StatusCodes": [
200
],
Expand All @@ -258,12 +251,12 @@
"BodyMediaType": "Json",
"Headers": [
{
"$id": "31",
"$id": "30",
"Name": "Operation-Location",
"NameInResponse": "operationLocation",
"Description": "The location for monitoring the operation state.",
"Type": {
"$id": "32",
"$id": "31",
"Name": "ResourceLocation",
"Kind": "Uri",
"IsNullable": false
Expand All @@ -273,19 +266,19 @@
"IsErrorResponse": false
}
],
"HttpMethod": "PATCH",
"HttpMethod": "PUT",
"RequestBodyMediaType": "Json",
"Uri": "{Endpoint}/language",
"Path": "/authoring/analyze-text/Pet/{name}",
"RequestMediaTypes": [
"application/merge-patch+json"
"application/json"
],
"BufferResponse": true,
"LongRunning": {
"$id": "33",
"$id": "32",
"FinalStateVia": 3,
"FinalResponse": {
"$id": "34",
"$id": "33",
"StatusCodes": [
200
],
Expand All @@ -296,11 +289,11 @@
}
},
"GenerateProtocolMethod": true,
"GenerateConvenienceMethod": false
"GenerateConvenienceMethod": true
}
],
"Protocol": {
"$id": "35"
"$id": "34"
},
"Creatable": true
}
Expand Down
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
}
5 changes: 4 additions & 1 deletion test/CadlRanchProjects.Tests/lro/standard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ public Task Action() => Test(async (host) =>
{
var operation = await new StandardClient(host, null).ExportAsync(WaitUntil.Completed, "madge", "json");
Assert.IsTrue(operation.HasCompleted);
// TODO: support model properties

var exportedUser = operation.Value;
Assert.AreEqual("madge", exportedUser.Name);
Assert.AreEqual("/users/madge", exportedUser.ResourceUri);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// <auto-generated/>

#nullable disable

using System;
using System.ComponentModel;

namespace _Azure.Lro.Standard.Models
{
/// <summary> Enum describing allowed operation states. </summary>
internal readonly partial struct OperationState : IEquatable<OperationState>
{
private readonly string _value;

/// <summary> Initializes a new instance of <see cref="OperationState"/>. </summary>
/// <exception cref="ArgumentNullException"> <paramref name="value"/> is null. </exception>
public OperationState(string value)
{
_value = value ?? throw new ArgumentNullException(nameof(value));
}

private const string InProgressValue = "InProgress";
private const string SucceededValue = "Succeeded";
private const string FailedValue = "Failed";
private const string CanceledValue = "Canceled";

/// <summary> The operation is in progress. </summary>
public static OperationState InProgress { get; } = new OperationState(InProgressValue);
/// <summary> The operation has completed successfully. </summary>
public static OperationState Succeeded { get; } = new OperationState(SucceededValue);
/// <summary> The operation has failed. </summary>
public static OperationState Failed { get; } = new OperationState(FailedValue);
/// <summary> The operation has been canceled by the user. </summary>
public static OperationState Canceled { get; } = new OperationState(CanceledValue);
/// <summary> Determines if two <see cref="OperationState"/> values are the same. </summary>
public static bool operator ==(OperationState left, OperationState right) => left.Equals(right);
/// <summary> Determines if two <see cref="OperationState"/> values are not the same. </summary>
public static bool operator !=(OperationState left, OperationState right) => !left.Equals(right);
/// <summary> Converts a string to a <see cref="OperationState"/>. </summary>
public static implicit operator OperationState(string value) => new OperationState(value);

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object obj) => obj is OperationState other && Equals(other);
/// <inheritdoc />
public bool Equals(OperationState other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase);

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => _value?.GetHashCode() ?? 0;
/// <inheritdoc />
public override string ToString() => _value;
}
}
Loading
Loading