Skip to content

Commit a2be0f0

Browse files
Support string next link (Azure#50766)
* Support string next link * regen * Fix merge * Fix visitor bug * fix * test * Add test
1 parent 9e77618 commit a2be0f0

27 files changed

+2363
-551
lines changed

eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/CollectionResultDefinition.cs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ internal class CollectionResultDefinition : TypeProvider
4949
new("pageSizeHint", $"The number of items per page.", new CSharpType(typeof(int?)));
5050

5151
private readonly bool _isProtocol;
52+
private readonly PropertyProvider? _nextPageProperty;
5253

5354
public CollectionResultDefinition(ClientProvider client, InputPagingServiceMethod serviceMethod, CSharpType? itemModelType, bool isAsync)
5455
{
@@ -116,9 +117,9 @@ public CollectionResultDefinition(ClientProvider client, InputPagingServiceMetho
116117
// Use the canonical view in case the property was customized.
117118
if (_nextPageLocation == InputResponseLocation.Body)
118119
{
119-
_nextPagePropertyName =
120-
responseModel.CanonicalView.Properties.FirstOrDefault(
121-
p => p.WireInfo?.SerializedName == nextPagePropertyName)?.Name;
120+
_nextPageProperty = responseModel.CanonicalView.Properties.FirstOrDefault(
121+
p => p.WireInfo?.SerializedName == nextPagePropertyName);
122+
_nextPagePropertyName = _nextPageProperty?.Name;
122123
}
123124
else if (_nextPageLocation == InputResponseLocation.Header)
124125
{
@@ -202,15 +203,15 @@ private MethodBodyStatement[] BuildAsPagesMethodBody()
202203
doWhileStatement.Add(ConvertItemsToListOfBinaryData(responseWithTypeVariable, out var itemsVariable));
203204

204205
// Extract next page
205-
doWhileStatement.Add(nextPageVariable.Assign(_nextPagePropertyName is null ? Null : BuildGetNextPage(responseWithTypeVariable, responseVariable)).Terminate());
206+
doWhileStatement.Add(nextPageVariable.Assign(BuildGetNextPage(responseWithTypeVariable, responseVariable)).Terminate());
206207

207208
// Create and yield the page
208209
doWhileStatement.Add(YieldReturn(Static(new CSharpType(typeof(Page<>), [_itemModelType])).Invoke("FromValues", [itemsVariable, nextPageExpression, responseVariable])));
209210
}
210211
else
211212
{
212213
// Extract next page
213-
doWhileStatement.Add(nextPageVariable.Assign(_nextPagePropertyName is null ? Null : BuildGetNextPage(responseWithTypeVariable, responseVariable)).Terminate());
214+
doWhileStatement.Add(nextPageVariable.Assign(BuildGetNextPage(responseWithTypeVariable, responseVariable)).Terminate());
214215

215216
// Create and yield the page
216217
doWhileStatement.Add(YieldReturn(Static(new CSharpType(typeof(Page<>), [_itemModelType])).Invoke("FromValues", [responseWithTypeVariable.Property(_itemsPropertyName).CastTo(new CSharpType(typeof(IReadOnlyList<>), _itemModelType)), nextPageExpression, responseVariable])));
@@ -263,18 +264,37 @@ private ValueExpression BuildGetNextPage(VariableExpression responseWithTypeVari
263264
return Null;
264265
}
265266

266-
switch (_nextPageLocation)
267+
return _nextPageLocation switch
267268
{
268-
case InputResponseLocation.Body:
269-
return responseWithTypeVariable.Property(_nextPagePropertyName);
270-
case InputResponseLocation.Header:
271-
return new TernaryConditionalExpression(
272-
responseVariable.Property("Headers").Invoke("TryGetValue", Literal(_nextPagePropertyName), new DeclarationExpression(typeof(string), "value", out var nextLinkHeader, isOut: true)).As<bool>(),
273-
nextLinkHeader,
274-
Null);
275-
default:
276-
// Invalid location is logged by the emitter.
277-
return Null;
269+
InputResponseLocation.Body =>NeedsConversionToUri() ? New.Instance<Uri>(responseWithTypeVariable.Property(_nextPagePropertyName)) : responseWithTypeVariable.Property(_nextPagePropertyName),
270+
InputResponseLocation.Header => new TernaryConditionalExpression(
271+
responseVariable.Property("Headers")
272+
.Invoke("TryGetValue", Literal(_nextPagePropertyName),
273+
new DeclarationExpression(typeof(string), "value", out var nextLinkHeader, isOut: true))
274+
.As<bool>(),
275+
NeedsConversionToUri() ? New.Instance<Uri>(nextLinkHeader) : nextLinkHeader,
276+
Null),
277+
_ => Null
278+
};
279+
280+
bool NeedsConversionToUri()
281+
{
282+
if (_paging.NextLink == null)
283+
{
284+
return false;
285+
}
286+
if (_nextPageLocation == InputResponseLocation.Body &&
287+
_nextPageProperty?.Type.Equals(typeof(string)) == true)
288+
{
289+
return true;
290+
}
291+
292+
if (_nextPageLocation == InputResponseLocation.Header)
293+
{
294+
return true;
295+
}
296+
297+
return false;
278298
}
279299
}
280300

eng/packages/http-client-csharp/generator/Azure.Generator/src/Visitors/SpecialHeadersVisitor.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,29 @@ namespace Azure.Generator.Visitors
2020
/// </summary>
2121
internal class SpecialHeadersVisitor : ScmLibraryVisitor
2222
{
23+
private const string ClientRequestIdParameterName = "client-request-id";
24+
private const string ReturnClientRequestIdParameterName = "return-client-request-id";
25+
private const string XMsClientRequestIdParameterName = "x-ms-client-request-id";
2326
protected override ScmMethodProviderCollection? Visit(
2427
InputServiceMethod serviceMethod,
2528
ClientProvider client,
2629
ScmMethodProviderCollection? methods)
2730
{
2831
var clientRequestIdParameter =
29-
serviceMethod.Parameters.FirstOrDefault(p => p.NameInRequest == "client-request-id");
32+
serviceMethod.Parameters.FirstOrDefault(p => p.NameInRequest == ClientRequestIdParameterName);
3033
var returnClientRequestIdParameter =
31-
serviceMethod.Parameters.FirstOrDefault(p => p.NameInRequest == "return-client-request-id");
34+
serviceMethod.Parameters.FirstOrDefault(p => p.NameInRequest == ReturnClientRequestIdParameterName);
3235
var xMsClientRequestIdParameter =
33-
serviceMethod.Parameters.FirstOrDefault(p => p.NameInRequest == "x-ms-client-request-id");
36+
serviceMethod.Parameters.FirstOrDefault(p => p.NameInRequest == XMsClientRequestIdParameterName);
3437

3538
if (clientRequestIdParameter != null || returnClientRequestIdParameter != null || xMsClientRequestIdParameter != null)
3639
{
3740
serviceMethod.Update(parameters: serviceMethod.Parameters
38-
.Where(p => p != clientRequestIdParameter && p != returnClientRequestIdParameter && p != xMsClientRequestIdParameter)
41+
.Where(p => p.NameInRequest != ClientRequestIdParameterName && p.NameInRequest != ReturnClientRequestIdParameterName && p.NameInRequest != XMsClientRequestIdParameterName)
42+
.ToList());
43+
serviceMethod.Operation.Update(parameters: serviceMethod.Operation.Parameters
44+
.Where(p => p.NameInRequest != ClientRequestIdParameterName && p.NameInRequest != ReturnClientRequestIdParameterName && p.NameInRequest != XMsClientRequestIdParameterName)
3945
.ToList());
40-
serviceMethod.Operation.Update(parameters: serviceMethod.Parameters);
4146

4247
// Create a new method collection with the updated service method
4348
methods = new ScmMethodProviderCollection(serviceMethod, client);

eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/CollectionResultDefinitions/NextLinkTests.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,34 @@ public void NextLinkInBodyMultipleClients()
154154
Assert.IsNotNull(felineClientCollectionResult);
155155
}
156156

157+
[Test]
158+
public void NextLinkInBodyOfTWithStringProperty()
159+
{
160+
CreatePagingOperation(InputResponseLocation.Body, useStringProperty: true);
161+
162+
var collectionResultDefinition = AzureClientGenerator.Instance.OutputLibrary.TypeProviders.FirstOrDefault(
163+
t => t is CollectionResultDefinition && t.Name == "CatClientGetCatsCollectionResultOfT");
164+
Assert.IsNotNull(collectionResultDefinition);
165+
166+
var writer = new TypeProviderWriter(collectionResultDefinition!);
167+
var file = writer.Write();
168+
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
169+
}
170+
171+
[Test]
172+
public void NextLinkInBodyWithStringProperty()
173+
{
174+
CreatePagingOperation(InputResponseLocation.Body, useStringProperty: true);
175+
176+
var collectionResultDefinition = AzureClientGenerator.Instance.OutputLibrary.TypeProviders.FirstOrDefault(
177+
t => t is CollectionResultDefinition && t.Name == "CatClientGetCatsCollectionResult");
178+
Assert.IsNotNull(collectionResultDefinition);
179+
180+
var writer = new TypeProviderWriter(collectionResultDefinition!);
181+
var file = writer.Write();
182+
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
183+
}
184+
157185
[Test]
158186
public void UsesValidFieldIdentifierNames()
159187
{
@@ -190,7 +218,7 @@ public void UsesValidFieldIdentifierNames()
190218
Assert.IsTrue(fields.Any(f => f.Name == "_foo"));
191219
}
192220

193-
private static void CreatePagingOperation(InputResponseLocation responseLocation)
221+
private static void CreatePagingOperation(InputResponseLocation responseLocation, bool useStringProperty = false)
194222
{
195223
var inputModel = InputFactory.Model("cat", properties:
196224
[
@@ -201,7 +229,10 @@ private static void CreatePagingOperation(InputResponseLocation responseLocation
201229
[200],
202230
InputFactory.Model(
203231
"page",
204-
properties: [InputFactory.Property("cats", InputFactory.Array(inputModel)), InputFactory.Property("nextCat", InputPrimitiveType.Url)]));
232+
properties: [
233+
InputFactory.Property("cats", InputFactory.Array(inputModel)),
234+
InputFactory.Property("nextCat", useStringProperty ? InputPrimitiveType.String : InputPrimitiveType.Url)
235+
]));
205236
var operation = InputFactory.Operation("getCats", responses: [response]);
206237
var inputServiceMethod = InputFactory.PagingServiceMethod("getCats", operation, pagingMetadata: pagingMetadata);
207238
var client = InputFactory.Client("catClient", methods: [inputServiceMethod]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
// <auto-generated/>
5+
6+
#nullable disable
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using Azure;
11+
using Azure.Core;
12+
using Azure.Core.Pipeline;
13+
using Samples.Models;
14+
15+
namespace Samples
16+
{
17+
internal partial class CatClientGetCatsCollectionResultOfT : global::Azure.Pageable<global::Samples.Models.Cat>
18+
{
19+
private readonly global::Samples.CatClient _client;
20+
private readonly global::Azure.RequestContext _context;
21+
22+
/// <summary> Initializes a new instance of CatClientGetCatsCollectionResultOfT, which is used to iterate over the pages of a collection. </summary>
23+
/// <param name="client"> The CatClient client used to send requests. </param>
24+
/// <param name="context"> The request options, which can override default behaviors of the client pipeline on a per-call basis. </param>
25+
public CatClientGetCatsCollectionResultOfT(global::Samples.CatClient client, global::Azure.RequestContext context) : base((context?.CancellationToken ?? default))
26+
{
27+
_client = client;
28+
_context = context;
29+
}
30+
31+
/// <summary> Gets the pages of CatClientGetCatsCollectionResultOfT as an enumerable collection. </summary>
32+
/// <param name="continuationToken"> A continuation token indicating where to resume paging. </param>
33+
/// <param name="pageSizeHint"> The number of items per page. </param>
34+
/// <returns> The pages of CatClientGetCatsCollectionResultOfT as an enumerable collection. </returns>
35+
public override global::System.Collections.Generic.IEnumerable<global::Azure.Page<global::Samples.Models.Cat>> AsPages(string continuationToken, int? pageSizeHint)
36+
{
37+
global::System.Uri nextPage = (continuationToken != null) ? new global::System.Uri(continuationToken) : null;
38+
do
39+
{
40+
global::Azure.Response response = this.GetNextResponse(pageSizeHint, nextPage);
41+
if ((response is null))
42+
{
43+
yield break;
44+
}
45+
global::Samples.Models.Page responseWithType = ((global::Samples.Models.Page)response);
46+
nextPage = new global::System.Uri(responseWithType.NextCat);
47+
yield return global::Azure.Page<global::Samples.Models.Cat>.FromValues(((global::System.Collections.Generic.IReadOnlyList<global::Samples.Models.Cat>)responseWithType.Cats), nextPage?.AbsoluteUri, response);
48+
}
49+
while ((nextPage != null));
50+
}
51+
52+
/// <summary> Get next page. </summary>
53+
/// <param name="pageSizeHint"> The number of items per page. </param>
54+
/// <param name="nextLink"> The next link to use for the next page of results. </param>
55+
private global::Azure.Response GetNextResponse(int? pageSizeHint, global::System.Uri nextLink)
56+
{
57+
global::Azure.Core.HttpMessage message = (nextLink != null) ? _client.CreateNextGetCatsRequest(nextLink, _context) : _client.CreateGetCatsRequest(_context);
58+
using global::Azure.Core.Pipeline.DiagnosticScope scope = _client.ClientDiagnostics.CreateScope("CatClient.GetCats");
59+
scope.Start();
60+
try
61+
{
62+
return _client.Pipeline.ProcessMessage(message, _context);
63+
}
64+
catch (global::System.Exception e)
65+
{
66+
scope.Failed(e);
67+
throw;
68+
}
69+
}
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
// <auto-generated/>
5+
6+
#nullable disable
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using Azure;
11+
using Azure.Core;
12+
using Azure.Core.Pipeline;
13+
using Samples.Models;
14+
15+
namespace Samples
16+
{
17+
internal partial class CatClientGetCatsCollectionResult : global::Azure.Pageable<global::System.BinaryData>
18+
{
19+
private readonly global::Samples.CatClient _client;
20+
private readonly global::Azure.RequestContext _context;
21+
22+
/// <summary> Initializes a new instance of CatClientGetCatsCollectionResult, which is used to iterate over the pages of a collection. </summary>
23+
/// <param name="client"> The CatClient client used to send requests. </param>
24+
/// <param name="context"> The request options, which can override default behaviors of the client pipeline on a per-call basis. </param>
25+
public CatClientGetCatsCollectionResult(global::Samples.CatClient client, global::Azure.RequestContext context) : base((context?.CancellationToken ?? default))
26+
{
27+
_client = client;
28+
_context = context;
29+
}
30+
31+
/// <summary> Gets the pages of CatClientGetCatsCollectionResult as an enumerable collection. </summary>
32+
/// <param name="continuationToken"> A continuation token indicating where to resume paging. </param>
33+
/// <param name="pageSizeHint"> The number of items per page. </param>
34+
/// <returns> The pages of CatClientGetCatsCollectionResult as an enumerable collection. </returns>
35+
public override global::System.Collections.Generic.IEnumerable<global::Azure.Page<global::System.BinaryData>> AsPages(string continuationToken, int? pageSizeHint)
36+
{
37+
global::System.Uri nextPage = (continuationToken != null) ? new global::System.Uri(continuationToken) : null;
38+
do
39+
{
40+
global::Azure.Response response = this.GetNextResponse(pageSizeHint, nextPage);
41+
if ((response is null))
42+
{
43+
yield break;
44+
}
45+
global::Samples.Models.Page responseWithType = ((global::Samples.Models.Page)response);
46+
global::System.Collections.Generic.List<global::System.BinaryData> items = new global::System.Collections.Generic.List<global::System.BinaryData>();
47+
foreach (var item in responseWithType.Cats)
48+
{
49+
items.Add(global::System.BinaryData.FromObjectAsJson(item));
50+
}
51+
nextPage = new global::System.Uri(responseWithType.NextCat);
52+
yield return global::Azure.Page<global::System.BinaryData>.FromValues(items, nextPage?.AbsoluteUri, response);
53+
}
54+
while ((nextPage != null));
55+
}
56+
57+
/// <summary> Get next page. </summary>
58+
/// <param name="pageSizeHint"> The number of items per page. </param>
59+
/// <param name="nextLink"> The next link to use for the next page of results. </param>
60+
private global::Azure.Response GetNextResponse(int? pageSizeHint, global::System.Uri nextLink)
61+
{
62+
global::Azure.Core.HttpMessage message = (nextLink != null) ? _client.CreateNextGetCatsRequest(nextLink, _context) : _client.CreateGetCatsRequest(_context);
63+
using global::Azure.Core.Pipeline.DiagnosticScope scope = _client.ClientDiagnostics.CreateScope("CatClient.GetCats");
64+
scope.Start();
65+
try
66+
{
67+
return _client.Pipeline.ProcessMessage(message, _context);
68+
}
69+
catch (global::System.Exception e)
70+
{
71+
scope.Failed(e);
72+
throw;
73+
}
74+
}
75+
}
76+
}

eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/CollectionResultDefinitions/TestData/NextLinkTests/NextLinkInHeader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public CatClientGetCatsCollectionResult(global::Samples.CatClient client, global
4848
{
4949
items.Add(global::System.BinaryData.FromObjectAsJson(item));
5050
}
51-
nextPage = response.Headers.TryGetValue("nextCat", out string value) ? value : null;
51+
nextPage = response.Headers.TryGetValue("nextCat", out string value) ? new global::System.Uri(value) : null;
5252
yield return global::Azure.Page<global::System.BinaryData>.FromValues(items, nextPage?.AbsoluteUri, response);
5353
}
5454
while ((nextPage != null));

eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/CollectionResultDefinitions/TestData/NextLinkTests/NextLinkInHeaderAsync.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public CatClientGetCatsAsyncCollectionResult(global::Samples.CatClient client, g
4949
{
5050
items.Add(global::System.BinaryData.FromObjectAsJson(item));
5151
}
52-
nextPage = response.Headers.TryGetValue("nextCat", out string value) ? value : null;
52+
nextPage = response.Headers.TryGetValue("nextCat", out string value) ? new global::System.Uri(value) : null;
5353
yield return global::Azure.Page<global::System.BinaryData>.FromValues(items, nextPage?.AbsoluteUri, response);
5454
}
5555
while ((nextPage != null));

eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/CollectionResultDefinitions/TestData/NextLinkTests/NextLinkInHeaderOfT.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public CatClientGetCatsCollectionResultOfT(global::Samples.CatClient client, glo
4343
yield break;
4444
}
4545
global::Samples.Models.Page responseWithType = ((global::Samples.Models.Page)response);
46-
nextPage = response.Headers.TryGetValue("nextCat", out string value) ? value : null;
46+
nextPage = response.Headers.TryGetValue("nextCat", out string value) ? new global::System.Uri(value) : null;
4747
yield return global::Azure.Page<global::Samples.Models.Cat>.FromValues(((global::System.Collections.Generic.IReadOnlyList<global::Samples.Models.Cat>)responseWithType.Cats), nextPage?.AbsoluteUri, response);
4848
}
4949
while ((nextPage != null));

0 commit comments

Comments
 (0)