-
-
Notifications
You must be signed in to change notification settings - Fork 784
Expand file tree
/
Copy pathRequestBuilderImplementation.cs
More file actions
704 lines (619 loc) · 35.4 KB
/
Copy pathRequestBuilderImplementation.cs
File metadata and controls
704 lines (619 loc) · 35.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved.
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
#if NET8_0_OR_GREATER
using System.Runtime.InteropServices;
#endif
using ReactiveUI.Primitives.Advanced;
namespace Refit;
/// <summary>Reflection-based request builder that turns Refit interface calls into HTTP requests.</summary>
internal partial class RequestBuilderImplementation : IRequestBuilder
{
/// <summary>Maximum stack-allocated buffer size, in characters, used when building paths and query strings.</summary>
internal const int StackallocThreshold = 512;
/// <summary>The name of the <see cref="IReturnTypeAdapter{TReturn, TResult}.Adapt"/> method, resolved reflectively.</summary>
internal const string AdaptMethodName = "Adapt";
/// <summary>The default query attribute applied when a parameter has none.</summary>
internal static readonly QueryAttribute DefaultQueryAttribute = new();
/// <summary>A placeholder base URI used while building relative request URIs. Its scheme and host are
/// discarded — only the combined path and query are kept (see <c>AssignRequestUri</c>), so it never
/// reaches the network.</summary>
internal static readonly Uri BaseUri = new("https://api");
/// <summary>Caches this type's private delegate-factory methods by name, so resolving one never re-materializes the
/// full <see cref="TypeInfo.DeclaredMethods"/> array — a fresh reflection allocation on every lookup — for the small
/// fixed set of names the builder repeatedly asks for.</summary>
private static readonly ConcurrentDictionary<string, MethodInfo> DeclaredMethodCache = new();
/// <summary>Reuses one delegate for the declared-method resolver so a cache miss never allocates a callback.</summary>
private static readonly Func<string, MethodInfo> DeclaredMethodFactory = ResolveDeclaredMethod;
/// <summary>Lookup of HTTP methods keyed by method name.</summary>
private readonly Dictionary<string, List<RestMethodInfoInternal>> _interfaceHttpMethods;
/// <summary>Cache of closed generic method infos keyed by method and type arguments.</summary>
private readonly ConcurrentDictionary<CloseGenericMethodKey, RestMethodInfoInternal> _interfaceGenericHttpMethods;
/// <summary>The content serializer from the active settings.</summary>
private readonly IHttpContentSerializer _serializer;
/// <summary>The settings controlling request building and serialization.</summary>
private readonly RefitSettings _settings;
/// <summary>The shared route prefix declared by the client interface's <see cref="PathPrefixAttribute"/>, or an empty string when none is present.</summary>
private readonly string _clientPathPrefix;
/// <summary>Initializes a new instance of the <see cref="RequestBuilderImplementation"/> class for the given interface type.</summary>
/// <param name="refitInterfaceType">The Refit interface type to build requests for.</param>
/// <param name="refitSettings">The settings to use, or null for defaults.</param>
/// <exception cref="ArgumentException"><paramref name="refitInterfaceType"/> is null or is not an interface type.</exception>
[RequiresUnreferencedCode("Building requests from reflected interface methods requires interface and request object metadata to be available at runtime.")]
internal RequestBuilderImplementation(
[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.Interfaces
| DynamicallyAccessedMemberTypes.PublicMethods
| DynamicallyAccessedMemberTypes.NonPublicMethods)]
Type refitInterfaceType,
RefitSettings? refitSettings = null)
{
if (refitInterfaceType?.GetTypeInfo().IsInterface != true)
{
throw new ArgumentException("targetInterface must be an Interface");
}
var targetInterfaceInheritedInterfaces = refitInterfaceType.GetInterfaces();
_settings = refitSettings ?? new RefitSettings();
_serializer = _settings.ContentSerializer;
_interfaceGenericHttpMethods =
new();
TargetType = refitInterfaceType;
// The client interface's [PathPrefix] applies to every method it exposes, including methods inherited from
// base interfaces. A base interface's own prefix is ignored here (it applies only when that base is itself the
// client type), so the prefix is read once from the target interface rather than per declaring interface.
_clientPathPrefix = refitInterfaceType.GetCustomAttribute<PathPrefixAttribute>()?.Prefix ?? string.Empty;
var dict = new Dictionary<string, List<RestMethodInfoInternal>>(StringComparer.Ordinal);
AddInterfaceHttpMethods(refitInterfaceType, dict);
foreach (var inheritedInterface in targetInterfaceInheritedInterfaces)
{
AddInterfaceHttpMethods(inheritedInterface, dict);
}
_interfaceHttpMethods = dict;
}
/// <inheritdoc/>
public RefitSettings Settings => _settings;
/// <summary>Gets the Refit interface type this builder targets.</summary>
internal Type TargetType { get; }
/// <inheritdoc/>
[RequiresUnreferencedCode("Building request delegates from reflected method metadata requires generic method metadata to be available at runtime.")]
[RequiresDynamicCode("Building request delegates from reflected method metadata requires runtime generic method instantiation.")]
public Func<HttpClient, object[], object?> BuildRestResultFuncForMethod(
string methodName,
Type[]? parameterTypes = null,
Type[]? genericArgumentTypes = null)
{
var restMethod = FindMatchingRestMethodInfo(
methodName,
parameterTypes,
genericArgumentTypes);
// Task (void)
if (restMethod.ReturnType == typeof(Task))
{
return BuildVoidTaskFuncForMethod(restMethod);
}
// Task<HttpRequestMessage>: build the request and hand it back to the caller without sending it. Runs before the
// Task<T> shape so the request is not dispatched and its response deserialized.
if (IsRequestMessageReturnType(restMethod))
{
return BuildRequestMessageFuncForMethod(restMethod);
}
// Task<T>
if (IsGenericReturnType(restMethod, typeof(Task<>)))
{
return BuildResultFuncForMethod(restMethod, nameof(BuildTaskFuncForMethod));
}
// ValueTask<T>
if (IsGenericReturnType(restMethod, typeof(ValueTask<>)))
{
return BuildResultFuncForMethod(restMethod, nameof(BuildValueTaskFuncForMethod));
}
// IAsyncEnumerable<T>
if (IsGenericReturnType(restMethod, typeof(IAsyncEnumerable<>)))
{
return BuildResultFuncForMethod(restMethod, nameof(BuildAsyncEnumerableFuncForMethod));
}
// IObservable<T>
if (IsGenericReturnType(restMethod, typeof(IObservable<>)))
{
return BuildResultFuncForMethod(restMethod, nameof(BuildRxFuncForMethod));
}
// A registered IReturnTypeAdapter surfaces this return type; this check runs after the built-in shapes so
// registering an adapter never overrides them.
return restMethod.HasReturnTypeAdapter
? BuildAdapterFuncForMethod(restMethod)
: BuildGeneratedSyncFuncForMethod(restMethod);
}
/// <summary>Finds a method declared on this implementation type by name, caching the resolved metadata by name.</summary>
/// <param name="name">The method name.</param>
/// <returns>The declared method.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static MethodInfo FindDeclaredMethod(string name) =>
DeclaredMethodCache.GetOrAdd(name, DeclaredMethodFactory);
/// <summary>Gets the lookup key for a method, stripping any explicit-interface prefix from the name.</summary>
/// <param name="methodInfo">The method to derive a key for.</param>
/// <returns>The simple method name used as a lookup key.</returns>
internal static string GetLookupKeyForMethod(MethodInfo methodInfo)
{
var name = methodInfo.Name;
var lastDot = name.LastIndexOf('.');
return lastDot >= 0 ? name[(lastDot + 1)..] : name;
}
/// <summary>Determines whether the method's return type is a closed generic of the supplied open generic type.</summary>
/// <param name="restMethod">The rest method to inspect.</param>
/// <param name="openGenericType">The open generic type definition to match.</param>
/// <returns><see langword="true"/> if the return type closes <paramref name="openGenericType"/>; otherwise <see langword="false"/>.</returns>
internal static bool IsGenericReturnType(RestMethodInfoInternal restMethod, Type openGenericType) =>
restMethod.ReturnType.GetTypeInfo().IsGenericType
&& restMethod.ReturnType.GetGenericTypeDefinition() == openGenericType;
/// <summary>Determines whether the method returns <see cref="Task{TResult}"/> of <see cref="HttpRequestMessage"/>.</summary>
/// <param name="restMethod">The rest method to inspect.</param>
/// <returns><see langword="true"/> when the method builds and returns its request without sending it.</returns>
internal static bool IsRequestMessageReturnType(RestMethodInfoInternal restMethod) =>
IsGenericReturnType(restMethod, typeof(Task<>))
&& restMethod.ReturnResultType == typeof(HttpRequestMessage);
/// <summary>Filters the candidate methods by parameter count and generic arity.</summary>
/// <param name="httpMethods">The candidate methods.</param>
/// <param name="parameterTypes">The parameter types to match.</param>
/// <param name="genericArgumentTypes">The generic argument types, or null.</param>
/// <returns>The matching candidate methods.</returns>
internal static RestMethodInfoInternal[] FilterPossibleMethods(
List<RestMethodInfoInternal> httpMethods,
Type[] parameterTypes,
Type[]? genericArgumentTypes)
{
var isGeneric = genericArgumentTypes?.Length > 0;
List<RestMethodInfoInternal>? possibleMethods = null;
for (var i = 0; i < httpMethods.Count; i++)
{
var method = httpMethods[i];
if (method.MethodInfo.GetParameters().Length != parameterTypes.Length)
{
continue;
}
if (isGeneric)
{
if (!method.MethodInfo.IsGenericMethod
|| method.MethodInfo.GetGenericArguments().Length != genericArgumentTypes!.Length)
{
continue;
}
}
else if (method.MethodInfo.IsGenericMethod)
{
continue;
}
possibleMethods ??= [];
possibleMethods.Add(method);
}
return possibleMethods is null ? [] : [.. possibleMethods];
}
/// <summary>Runs an asynchronous task factory synchronously and waits for completion.</summary>
/// <param name="taskFactory">The task factory to run.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage(
"Performance",
"PSH1315:A blocking wait on an awaitable that may not be done",
Justification = "Deliberate sync-over-async bridge for synchronous (void/non-Task) interface methods that have no async caller; the work is offloaded via Task.Run to avoid deadlocks.")]
internal static void RunSynchronous(Func<Task> taskFactory) =>
Task.Run(taskFactory).GetAwaiter().GetResult();
/// <summary>Runs an asynchronous task factory synchronously and returns its result.</summary>
/// <typeparam name="T">The result type.</typeparam>
/// <param name="taskFactory">The task factory to run.</param>
/// <returns>The result produced by the task.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage(
"Performance",
"PSH1315:A blocking wait on an awaitable that may not be done",
Justification = "Deliberate sync-over-async bridge for synchronous (non-Task) interface methods that have no async caller; the work is offloaded via Task.Run to avoid deadlocks.")]
internal static T? RunSynchronous<T>(Func<Task<T?>> taskFactory) =>
Task.Run(taskFactory).GetAwaiter().GetResult();
/// <summary>Awaits the request task and disposes the linked cancellation source once it completes.</summary>
/// <typeparam name="T">The result type produced by the request.</typeparam>
/// <param name="task">The in-flight request task.</param>
/// <param name="cts">The linked cancellation source to dispose when the task finishes.</param>
/// <returns>The result produced by <paramref name="task"/>.</returns>
internal static async Task<T?> DisposeWhenDoneAsync<T>(Task<T?> task, CancellationTokenSource cts)
{
try
{
return await task.ConfigureAwait(false);
}
finally
{
cts.Dispose();
}
}
/// <summary>Determines whether reflected parameters exactly match the requested parameter types.</summary>
/// <param name="parameters">The reflected method parameters.</param>
/// <param name="parameterTypes">The requested parameter types.</param>
/// <returns><see langword="true"/> when the parameter types match.</returns>
internal static bool ParametersMatch(ParameterInfo[] parameters, Type[] parameterTypes)
{
for (var i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType != parameterTypes[i])
{
return false;
}
}
return true;
}
/// <summary>Finds the first cancellation token in an argument array.</summary>
/// <param name="paramList">The argument values.</param>
/// <returns>The first cancellation token, or <see cref="CancellationToken.None"/>.</returns>
internal static CancellationToken GetCancellationToken(object[] paramList)
{
for (var i = 0; i < paramList.Length; i++)
{
if (paramList[i] is CancellationToken cancellationToken)
{
return cancellationToken;
}
}
return CancellationToken.None;
}
/// <summary>Discovers the Refit HTTP methods on an interface and adds them to the lookup dictionary.</summary>
/// <param name="interfaceType">The interface to scan for HTTP methods.</param>
/// <param name="methods">The dictionary to populate with discovered methods.</param>
[RequiresUnreferencedCode("Reading reflected interface methods requires interface and request object metadata to be available at runtime.")]
internal void AddInterfaceHttpMethods(
[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.Interfaces
| DynamicallyAccessedMemberTypes.PublicMethods
| DynamicallyAccessedMemberTypes.NonPublicMethods)]
Type interfaceType,
Dictionary<string, List<RestMethodInfoInternal>> methods)
{
foreach (var methodInfo in interfaceType.GetTypeInfo().DeclaredMethods)
{
if (!methodInfo.IsAbstract
|| methodInfo.GetCustomAttribute<HttpMethodAttribute>(true) is null)
{
continue;
}
var key = GetLookupKeyForMethod(methodInfo);
#if NET8_0_OR_GREATER
// Hashes the key once: the lookup and the insert share a single bucket probe.
ref var slot = ref CollectionsMarshal.GetValueRefOrAddDefault(methods, key, out _);
var value = slot ??= [];
#else
if (!methods.TryGetValue(key, out var value))
{
value = [];
methods.Add(key, value);
}
#endif
var restinfo = new RestMethodInfoInternal(interfaceType, methodInfo, _settings, _clientPathPrefix);
value.Add(restinfo);
}
}
/// <summary>Finds the rest method matching the given name, parameter types and generic arguments.</summary>
/// <param name="key">The method lookup key.</param>
/// <param name="parameterTypes">The parameter types to match, or null to match a single overload.</param>
/// <param name="genericArgumentTypes">The generic argument types to close over, or null.</param>
/// <returns>The matching rest method info.</returns>
/// <exception cref="ArgumentException">No method matching <paramref name="key"/> carries an HTTP method attribute,
/// or the name is overloaded and <paramref name="parameterTypes"/> was not supplied to disambiguate it.</exception>
/// <exception cref="InvalidOperationException">None of the overloads accept <paramref name="parameterTypes"/> once closed over <paramref name="genericArgumentTypes"/>.</exception>
[RequiresUnreferencedCode("Resolving generic Refit methods from reflected metadata requires generic method metadata to be available at runtime.")]
[RequiresDynamicCode("Resolving generic Refit methods from reflected metadata requires runtime generic method instantiation.")]
internal RestMethodInfoInternal FindMatchingRestMethodInfo(
string key,
Type[]? parameterTypes,
Type[]? genericArgumentTypes)
{
if (!_interfaceHttpMethods.TryGetValue(key, out var httpMethods))
{
throw new ArgumentException(
"Method must be defined and have an HTTP Method attribute");
}
if (parameterTypes is null)
{
if (httpMethods.Count > 1)
{
throw new ArgumentException(
$"MethodName exists more than once, '{nameof(parameterTypes)}' mut be defined");
}
return CloseGenericMethodIfNeeded(httpMethods[0], genericArgumentTypes);
}
var possibleMethods = FilterPossibleMethods(httpMethods, parameterTypes, genericArgumentTypes);
if (possibleMethods.Length == 1)
{
return CloseGenericMethodIfNeeded(possibleMethods[0], genericArgumentTypes);
}
foreach (var method in possibleMethods)
{
try
{
var closedMethod = CloseGenericMethodIfNeeded(method, genericArgumentTypes);
if (ParametersMatch(closedMethod.MethodInfo.GetParameters(), parameterTypes))
{
return closedMethod;
}
}
catch (Exception exception) when (exception.Message.Contains("violates the constraint", StringComparison.CurrentCultureIgnoreCase))
{
}
}
throw new InvalidOperationException("No suitable Method found...");
}
/// <summary>Closes an open generic rest method over the supplied type arguments, caching the result.</summary>
/// <param name="restMethodInfo">The (possibly generic) rest method.</param>
/// <param name="genericArgumentTypes">The generic argument types, or null if not generic.</param>
/// <returns>The closed rest method info, or the original when no generic arguments are supplied.</returns>
[RequiresUnreferencedCode("Closing generic Refit methods requires generic method metadata to be available at runtime.")]
[RequiresDynamicCode("Closing generic Refit methods requires runtime generic method instantiation.")]
internal RestMethodInfoInternal CloseGenericMethodIfNeeded(
RestMethodInfoInternal restMethodInfo,
Type[]? genericArgumentTypes) =>
genericArgumentTypes is { } genericArguments
? _interfaceGenericHttpMethods.GetOrAdd(
new(restMethodInfo.MethodInfo, genericArguments),
static (_, state) =>
new RestMethodInfoInternal(
state.RestMethod.Type,
state.RestMethod.MethodInfo.MakeGenericMethod(state.GenericArguments),
state.RestMethod.RefitSettings,
state.RestMethod.ClientPathPrefix),
(RestMethod: restMethodInfo, GenericArguments: genericArguments))
: restMethodInfo;
/// <summary>Builds a result delegate for a method by invoking the named generic builder over the result types.</summary>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <param name="builderMethodName">The name of the private generic builder method.</param>
/// <returns>A delegate that invokes the method.</returns>
[RequiresUnreferencedCode("Building generic result delegates requires generic method metadata to be available at runtime.")]
[RequiresDynamicCode("Building generic result delegates requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], object?> BuildResultFuncForMethod(
RestMethodInfoInternal restMethod,
string builderMethodName)
{
var builderMethodInfo = FindDeclaredMethod(builderMethodName);
var resultFunc = (MulticastDelegate?)
builderMethodInfo!.MakeGenericMethod(
restMethod.ReturnResultType,
restMethod.DeserializedResultType)
.Invoke(this, [restMethod]);
// The array is explicit: DynamicInvoke takes 'params object?[]?', and its expanded form
// packs (client, args) into a single argument array rather than forwarding them one-to-one.
return (client, args) => resultFunc!.DynamicInvoke([client, args]);
}
/// <summary>Builds a delegate for a method whose return type a registered <see cref="IReturnTypeAdapter{TReturn, TResult}"/> surfaces.</summary>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that adapts the deferred HTTP call into the surfaced return type.</returns>
[RequiresUnreferencedCode("Building return-type adapter delegates requires generic method metadata to be available at runtime.")]
[RequiresDynamicCode("Building return-type adapter delegates requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], object?> BuildAdapterFuncForMethod(RestMethodInfoInternal restMethod)
{
var builderMethodInfo = FindDeclaredMethod(nameof(BuildAdapterFuncForMethodGeneric));
return (Func<HttpClient, object[], object?>)
builderMethodInfo!.MakeGenericMethod(
restMethod.ReturnResultType,
restMethod.DeserializedResultType)
.Invoke(this, [restMethod])!;
}
/// <summary>Builds an adapter invocation delegate for a method with known result and body types.</summary>
/// <typeparam name="T">The result type the HTTP call materializes (the adapter's <c>TResult</c>).</typeparam>
/// <typeparam name="TBody">The body type used for API responses.</typeparam>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that returns the adapter's surfaced value.</returns>
[SuppressMessage(
"Design",
"SST2307:Generic method type parameters should be inferable from the parameters",
Justification = "Type parameter intentionally specified explicitly by callers.")]
[RequiresUnreferencedCode("Instantiating the adapter and resolving its interface method requires adapter metadata to be available at runtime.")]
[RequiresDynamicCode("Instantiating the adapter and closing its interface method requires runtime generic type instantiation.")]
internal Func<HttpClient, object[], object?> BuildAdapterFuncForMethodGeneric<T, TBody>(
RestMethodInfoInternal restMethod)
{
var taskFunc = BuildCancellableTaskFuncForMethod<T, TBody>(restMethod);
// This runs only when HasReturnTypeAdapter already matched an adapter for this exact return type and adapter
// set, so ResolveClosedAdapterType resolves the same match and never returns null here.
var adapterType = ReturnTypeAdapterResolver.ResolveClosedAdapterType(
restMethod.ReturnType,
restMethod.RefitSettings.ReturnTypeAdapters)!;
var adapter = Activator.CreateInstance(adapterType)!;
// The adapter implements IReturnTypeAdapter<ReturnType, T>; T is the inner result classified for it, so the
// closed interface method matches the strongly typed invoke delegate built below.
var adapterInterface = typeof(IReturnTypeAdapter<,>).MakeGenericType(restMethod.ReturnType, typeof(T));
var adaptMethod = adapterInterface.GetMethod(AdaptMethodName)!;
return (client, paramList) =>
{
var methodCt = restMethod.CancellationToken is not null
? GetCancellationToken(paramList)
: CancellationToken.None;
Func<CancellationToken, Task<T?>> invoke = ct =>
{
// Link the adapter's per-invocation token with the method's token, and keep the linked source alive
// until the request finishes. The reflection builder rebuilds the request on each invoke, so a cold
// adapter can re-subscribe.
var cts = CancellationTokenSource.CreateLinkedTokenSource(methodCt, ct);
var task = taskFunc(client, cts.Token, paramList);
return DisposeWhenDoneAsync(task, cts);
};
return adaptMethod.Invoke(adapter, [invoke]);
};
}
/// <summary>Builds a synchronous invocation delegate for a generated (sync) interface method.</summary>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that invokes the method synchronously.</returns>
[RequiresUnreferencedCode("Building synchronous result delegates requires generic method metadata to be available at runtime.")]
[RequiresDynamicCode("Building synchronous result delegates requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], object?> BuildGeneratedSyncFuncForMethod(
RestMethodInfoInternal restMethod)
{
if (restMethod.ReturnResultType == typeof(void))
{
return (client, paramList) =>
{
RunSynchronous(() =>
ExecuteVoidRequestAsync(
client,
restMethod,
paramList,
paramsContainsCancellationToken: false,
CancellationToken.None));
return null;
};
}
var syncFuncMi = FindDeclaredMethod(nameof(BuildGeneratedSyncFuncForMethodGeneric));
var func =
syncFuncMi!
.MakeGenericMethod(
restMethod.ReturnResultType,
restMethod.DeserializedResultType)
.Invoke(this, [restMethod]);
return (Func<HttpClient, object[], object?>)func!;
}
/// <summary>Builds a synchronous invocation delegate for a generated method with known result and body types.</summary>
/// <typeparam name="T">The deserialized result type.</typeparam>
/// <typeparam name="TBody">The body type used for API responses.</typeparam>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that invokes the method synchronously.</returns>
[SuppressMessage(
"Design",
"SST2307:Generic method type parameters should be inferable from the parameters",
Justification = "Type parameter intentionally specified explicitly by callers.")]
[RequiresDynamicCode("Serializing a body by runtime Type requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], object?> BuildGeneratedSyncFuncForMethodGeneric<T, TBody>(
RestMethodInfoInternal restMethod) =>
(client, paramList) =>
RunSynchronous(() =>
ExecuteRequestAsync<T, TBody>(
client,
restMethod,
paramList,
paramsContainsCancellationToken: false,
CancellationToken.None));
/// <summary>Builds an observable invocation delegate for a method.</summary>
/// <typeparam name="T">The result type returned to the caller.</typeparam>
/// <typeparam name="TBody">The body type used for API responses.</typeparam>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that returns an observable of the result.</returns>
[SuppressMessage(
"Design",
"SST2307:Generic method type parameters should be inferable from the parameters",
Justification = "Type parameter intentionally specified explicitly by callers.")]
[RequiresDynamicCode("Serializing a body by runtime Type requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], IObservable<T?>> BuildRxFuncForMethod<T, TBody>(
RestMethodInfoInternal restMethod)
{
var taskFunc = BuildCancellableTaskFuncForMethod<T, TBody>(restMethod);
return (client, paramList) =>
new FromAsyncSignal<T?>(ct =>
{
var methodCt = CancellationToken.None;
if (restMethod.CancellationToken is not null)
{
methodCt = GetCancellationToken(paramList);
}
// link the two
var cts = CancellationTokenSource.CreateLinkedTokenSource(methodCt, ct);
var task = taskFunc(client, cts.Token, paramList);
// Keep the linked source alive until the request completes, then dispose it.
return DisposeWhenDoneAsync(task, cts);
});
}
/// <summary>Builds a delegate that streams the response of a method returning <see cref="IAsyncEnumerable{T}"/>.</summary>
/// <typeparam name="T">The element type yielded to the caller.</typeparam>
/// <typeparam name="TBody">Unused; present so the delegate factory shares the two-type-parameter shape.</typeparam>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that returns an asynchronous sequence of the result.</returns>
[SuppressMessage(
"Design",
"SST2307:Generic method type parameters should be inferable from the parameters",
Justification = "Type parameter intentionally specified explicitly by callers.")]
[SuppressMessage(
"StyleSharp",
"SST1452:Unused type parameters should be removed",
Justification = "The second type parameter is required so this factory matches the two-type-argument shape invoked reflectively by BuildResultFuncForMethod.")]
[RequiresDynamicCode("Serializing a body by runtime Type requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], IAsyncEnumerable<T?>> BuildAsyncEnumerableFuncForMethod<T, TBody>(
RestMethodInfoInternal restMethod)
{
var builder = this;
return (client, paramList) => builder.ExecuteAsyncEnumerableRequestAsync<T>(client, restMethod, paramList);
}
/// <summary>Builds a task invocation delegate for a method.</summary>
/// <typeparam name="T">The result type returned to the caller.</typeparam>
/// <typeparam name="TBody">The body type used for API responses.</typeparam>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that returns a task of the result.</returns>
[SuppressMessage(
"Design",
"SST2307:Generic method type parameters should be inferable from the parameters",
Justification = "Type parameter intentionally specified explicitly by callers.")]
[RequiresDynamicCode("Serializing a body by runtime Type requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], Task<T?>> BuildTaskFuncForMethod<T, TBody>(
RestMethodInfoInternal restMethod)
{
var ret = BuildCancellableTaskFuncForMethod<T, TBody>(restMethod);
return (client, paramList) =>
restMethod.CancellationToken is not null
? ret(client, GetCancellationToken(paramList), paramList)
: ret(client, CancellationToken.None, paramList);
}
/// <summary>Builds a value-task invocation delegate for a method.</summary>
/// <typeparam name="T">The result type returned to the caller.</typeparam>
/// <typeparam name="TBody">The body type used for API responses.</typeparam>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that returns a value task of the result.</returns>
[SuppressMessage(
"Design",
"SST2307:Generic method type parameters should be inferable from the parameters",
Justification = "Type parameter intentionally specified explicitly by callers.")]
[RequiresDynamicCode("Serializing a body by runtime Type requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], ValueTask<T?>> BuildValueTaskFuncForMethod<T, TBody>(
RestMethodInfoInternal restMethod)
{
var ret = BuildTaskFuncForMethod<T, TBody>(restMethod);
return (client, paramList) => new(ret(client, paramList));
}
/// <summary>Builds a task invocation delegate for a method with no response body.</summary>
/// <param name="restMethod">The rest method to build a delegate for.</param>
/// <returns>A delegate that returns a task with no result.</returns>
[RequiresDynamicCode("Serializing a body by runtime Type requires runtime generic method instantiation.")]
internal Func<HttpClient, object[], Task> BuildVoidTaskFuncForMethod(
RestMethodInfoInternal restMethod) =>
(client, paramList) =>
{
var ct = CancellationToken.None;
if (restMethod.CancellationToken is not null)
{
ct = GetCancellationToken(paramList);
}
return ExecuteVoidRequestAsync(
client,
restMethod,
paramList,
restMethod.CancellationToken is not null,
ct);
};
/// <summary>Resolves a method declared on this implementation type by name, on a declared-method-cache miss.</summary>
/// <param name="name">The method name.</param>
/// <returns>The declared method.</returns>
/// <exception cref="MissingMethodException">No method with the given name is declared on the type. The miss is not
/// cached, so an unknown name keeps throwing rather than poisoning the cache.</exception>
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' may break when trimming",
Justification = "This resolves Refit's own private generic delegate factory methods by known method name.")]
[UnconditionalSuppressMessage(
"Trimming",
"IL2111:Reflection access to methods with DynamicallyAccessedMembersAttribute",
Justification = "This helper filters by known method names and does not invoke methods with dynamic-access requirements.")]
private static MethodInfo ResolveDeclaredMethod(string name)
{
foreach (var method in typeof(RequestBuilderImplementation).GetTypeInfo().DeclaredMethods)
{
if (method.Name == name)
{
return method;
}
}
throw new MissingMethodException(typeof(RequestBuilderImplementation).FullName, name);
}
}