{"record":{"id":"fa80dd9441c42bd4","repo":"reactiveui/refit","slug":"method-methodinfo-name-is-invalid-all-rest-me","errorCode":null,"errorMessage":"Method \"{methodInfo.Name}\" is invalid. All REST Methods must return either Task<T> or ValueTask<T> or IObservable<T>","messagePattern":"Method \"(.+?)\" is invalid\\. All REST Methods must return either Task<T> or ValueTask<T> or IObservable<T>","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Refit.Reflection/RestMethodInfoInternal.cs","lineNumber":465,"sourceCode":"        if (adapterResultType is not null)\n        {\n            return (returnType, adapterResultType, DetermineDeserializedResultType(adapterResultType));\n        }\n\n        // Allow synchronous return types only for methods that are implemented by generated stubs\n        // (for example explicit/default interface implementations). Public top-level Refit methods must\n        // still use async-compatible return shapes.\n#if NET8_0_OR_GREATER        \n        var isExplicitInterfaceMember = methodInfo.Name.Contains('.');\n#else\n        var isExplicitInterfaceMember = methodInfo.Name.Contains(\".\");\n#endif\n\n        var isNonPublic = !methodInfo.IsPublic;\n\n        if (!isExplicitInterfaceMember && !isNonPublic)\n        {\n            throw new ArgumentException(\n                $\"Method \\\"{methodInfo.Name}\\\" is invalid. All REST Methods must return either Task<T> or ValueTask<T> or IObservable<T>\");\n        }\n\n        return (returnType, returnType, DetermineDeserializedResultType(returnType));\n    }\n\n    /// <summary>Determines the type that response content is deserialized into for the given result type.</summary>\n    /// <param name=\"returnResultType\">The result type wrapped by the return type.</param>\n    /// <returns>The type to deserialize response content into.</returns>\n    internal static Type DetermineDeserializedResultType(Type returnResultType)\n    {\n        if (\n            returnResultType.IsGenericType\n            && (\n                returnResultType.GetGenericTypeDefinition() == typeof(ApiResponse<>)\n                || returnResultType.GetGenericTypeDefinition() == typeof(IApiResponse<>)\n            )\n        )","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/reactiveui/refit/blob/b455f65ecc4c97d092317e349cb775f9cfc6bcdf/src/Refit.Reflection/RestMethodInfoInternal.cs#L447-L483","documentation":"Thrown by DetermineReturnTypeInfo when a public, top-level Refit interface method has a return type that is not an async-compatible shape (Task<T>, ValueTask<T>, IObservable<T>, IAsyncEnumerable<T>, or a non-generic Task) and is not handled by a registered return-type adapter. Refit needs an awaitable/observable return so it can drive the HTTP pipeline; a bare synchronous return (e.g. string, int, void) has no way to surface the async HTTP work.","triggerScenarios":"Declaring a Refit interface method that returns a non-async type, e.g. `string GetUser()` or `void Delete()` or `HttpResponseMessage Get()` (HttpResponseMessage alone is not awaitable). The check is skipped for explicit interface members and non-public methods (generated stubs), so it only hits public declared methods.","commonSituations":"Newcomer mistake of returning the deserialized type directly instead of wrapping it in Task<T>; migrating from HttpClient.GetAwaiter patterns; forgetting ValueTask; a method returning Task (non-generic) when content is expected — note plain `Task` IS allowed and returns void, so the real trigger is a truly synchronous signature.","solutions":["Wrap the return type in Task<T> (or ValueTask<T>), e.g. change `User GetUser()` to `Task<User> GetUser()`.","For streaming/reactive scenarios use IObservable<T> or IAsyncEnumerable<T>.","If you genuinely need the raw response, use Task<HttpResponseMessage> or Task<IApiResponse<T>> (these are Task<>, so they are accepted).","For custom async return shapes, register an IReturnTypeAdapter in RefitSettings so adapterResultType is populated."],"exampleFix":"// before\npublic interface IApi\n{\n    [Get(\"/users/{id}\")]\n    User GetUser(int id);\n}\n\n// after\npublic interface IApi\n{\n    [Get(\"/users/{id}\")]\n    Task<User> GetUser(int id);\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Reject interfaces whose Refit methods lack an async-compatible return.\nstatic bool IsRefitAsyncReturn(Type t) =>\n    t == typeof(Task) ||\n    (t.IsGenericType && t.GetGenericTypeDefinition() is var g &&\n        (g == typeof(Task<>) || g == typeof(ValueTask<>) ||\n         g == typeof(IObservable<>) || g == typeof(IAsyncEnumerable<>)));\n\nforeach (var m in typeof(IMyApi).GetMethods())\n    if (!IsRefitAsyncReturn(m.ReturnType))\n        throw new InvalidOperationException($\"{m.Name} must return Task<T>/ValueTask<T>/IObservable<T>/IAsyncEnumerable<T>\");","tryCatchPattern":null,"preventionTips":["Always wrap Refit method results in Task<T> (or ValueTask<T>) by convention.","Add a Roslyn analyzer/test that asserts every public method on Refit interfaces returns Task/ValueTask/IObservable/IAsyncEnumerable.","Use Task<IApiResponse<T>> or Task<HttpResponseMessage> when you need the raw response."],"tags":["return-type","async","interface-design","reflection-builder"],"backgroundTag":null,"analyzedSha":"b455f65ecc4c97d092317e349cb775f9cfc6bcdf","analyzedAt":"2026-08-13T21:20:57.878Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}