Repository: reactiveui/refit Branch: main Commit: d3b9f6eb2242 Files: 281 Total size: 1.3 MB Directory structure: gitextract_tnsc_ofx/ ├── .devcontainers/ │ ├── Dockerfile │ ├── devcontainer.json │ └── post-create.sh ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ └── feature_request.md │ ├── renovate.json │ └── workflows/ │ ├── ci-build.yml │ ├── lock.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── COPYING ├── CodeCoverage.runsettings ├── Directory.Build.props ├── InterfaceStubGenerator.Roslyn38/ │ └── InterfaceStubGenerator.Roslyn38.csproj ├── InterfaceStubGenerator.Roslyn41/ │ └── InterfaceStubGenerator.Roslyn41.csproj ├── InterfaceStubGenerator.Roslyn50/ │ └── InterfaceStubGenerator.Roslyn50.csproj ├── InterfaceStubGenerator.Shared/ │ ├── DiagnosticDescriptors.cs │ ├── Emitter.cs │ ├── ITypeSymbolExtensions.cs │ ├── ImmutableEquatableArray.cs │ ├── IncrementalValuesProviderExtensions.cs │ ├── InterfaceStubGenerator.Shared.projitems │ ├── InterfaceStubGenerator.Shared.shproj │ ├── InterfaceStubGenerator.cs │ ├── IsExternalInit.cs │ ├── Models/ │ │ ├── ContextGenerationModel.cs │ │ ├── InterfaceModel.cs │ │ ├── MethodModel.cs │ │ ├── ParameterModel.cs │ │ ├── TypeConstraint.cs │ │ └── WellKnownTypes.cs │ ├── Parser.cs │ ├── Polyfills/ │ │ └── IndexRange.cs │ ├── SourceWriter.cs │ └── UniqueNameBuilder.cs ├── LICENSE ├── NuGet.config ├── README.md ├── Refit/ │ ├── AnonymousDisposable.cs │ ├── AotCompatibility.cs │ ├── ApiException.cs │ ├── ApiResponse.cs │ ├── Attributes.cs │ ├── AuthenticatedHttpClientHandler.cs │ ├── Buffers/ │ │ ├── PooledBufferWriter.Stream.NETStandard21.cs │ │ ├── PooledBufferWriter.Stream.cs │ │ ├── PooledBufferWriter.ThrowExceptions.cs │ │ └── PooledBufferWriter.cs │ ├── CachedRequestBuilderImplementation.cs │ ├── CamelCaseUrlParameterKeyFormatter.cs │ ├── CloseGenericMethodKey.cs │ ├── CollectionFormat.cs │ ├── DynamicallyAccessedMembersAttribute.cs │ ├── EnumerableExtensions.cs │ ├── FormValueMultimap.cs │ ├── HttpContentExtensions.cs │ ├── HttpRequestMessageProperties.cs │ ├── JsonContentSerializer.cs │ ├── MultipartItem.cs │ ├── NameValueCollection.cs │ ├── Polyfills.Trimming.cs │ ├── ProblemDetails.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── PushStreamContent.cs │ ├── Refit.csproj │ ├── RefitSettings.cs │ ├── RequestBuilder.cs │ ├── RequestBuilderFactory.cs │ ├── RequestBuilderImplementation.TaskToObservable.cs │ ├── RequestBuilderImplementation.cs │ ├── RestMethodInfo.cs │ ├── RestMethodParameterInfo.cs │ ├── RestService.cs │ ├── SystemTextJsonContentSerializer.cs │ ├── UniqueName.cs │ ├── ValidationApiException.cs │ ├── ValueStringBuilder.cs │ └── targets/ │ ├── refit.props │ └── refit.targets ├── Refit.Benchmarks/ │ ├── Benchmarks/ │ │ ├── net5.0/ │ │ │ ├── Benchmark_AllReturnTypes.bat │ │ │ ├── Benchmark_ObservableHttpResponseMessage.bat │ │ │ ├── Benchmark_Task.bat │ │ │ ├── Benchmark_TaskApiResponseT.bat │ │ │ ├── Benchmark_TaskHttpContent.bat │ │ │ ├── Benchmark_TaskHttpResponseMessage.bat │ │ │ ├── Benchmark_TaskStream.bat │ │ │ ├── Benchmark_TaskString.bat │ │ │ └── Benchmark_TaskT.bat │ │ ├── net6.0/ │ │ │ ├── Benchmark_AllReturnTypes.bat │ │ │ ├── Benchmark_ObservableHttpResponseMessage.bat │ │ │ ├── Benchmark_Task.bat │ │ │ ├── Benchmark_TaskApiResponseT.bat │ │ │ ├── Benchmark_TaskHttpContent.bat │ │ │ ├── Benchmark_TaskHttpResponseMessage.bat │ │ │ ├── Benchmark_TaskStream.bat │ │ │ ├── Benchmark_TaskString.bat │ │ │ └── Benchmark_TaskT.bat │ │ └── netcoreapp3.1/ │ │ ├── Benchmark_AllReturnTypes.bat │ │ ├── Benchmark_ObservableHttpResponseMessage.bat │ │ ├── Benchmark_Task.bat │ │ ├── Benchmark_TaskApiResponseT.bat │ │ ├── Benchmark_TaskHttpContent.bat │ │ ├── Benchmark_TaskHttpResponseMessage.bat │ │ ├── Benchmark_TaskStream.bat │ │ ├── Benchmark_TaskString.bat │ │ └── Benchmark_TaskT.bat │ ├── EndToEndBenchmark.cs │ ├── IGitHubService.cs │ ├── IPerformanceService.cs │ ├── PerformanceBenchmark.cs │ ├── Program.cs │ ├── Refit.Benchmarks.csproj │ ├── SourceGeneratorBenchmark.cs │ ├── SourceGeneratorBenchmarksProjects.cs │ ├── StartupBenchmark.cs │ ├── StaticFileHttpResponseHandler.cs │ ├── StaticValueHttpResponseHandler.cs │ ├── newtonsoft-json-10-users.json │ └── system-text-json-10-users.json ├── Refit.GeneratorTests/ │ ├── Fixture.cs │ ├── GeneratedTest.cs │ ├── Incremental/ │ │ ├── FunctionTest.cs │ │ ├── GenericTest.cs │ │ ├── IncrementalGeneratorRunReasons.cs │ │ ├── IncrementalTest.cs │ │ ├── InheritanceTest.cs │ │ └── TestHelper.cs │ ├── InterfaceTests.cs │ ├── MethodTests.cs │ ├── ModuleInitializer.cs │ ├── ParameterTests.cs │ ├── Refit.GeneratorTests.csproj │ ├── ReturnTypeTests.cs │ └── _snapshots/ │ ├── GeneratedTest.ShouldEmitAllFiles#Generated.g.verified.cs │ ├── GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs │ ├── GeneratedTest.ShouldEmitAllFiles#PreserveAttribute.g.verified.cs │ ├── InterfaceTests.ContainedInterfaceTest#IContainedInterface.g.verified.cs │ ├── InterfaceTests.DefaultInterfaceMethod#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.DerivedDefaultInterfaceMethod#IBaseInterface.g.verified.cs │ ├── InterfaceTests.DerivedDefaultInterfaceMethod#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.DisposableTest#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.GlobalNamespaceTest#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.InterfaceDerivedFromRefitBaseTest#IBaseInterface.g.verified.cs │ ├── InterfaceTests.InterfaceDerivedFromRefitBaseTest#IDerivedInterface.g.verified.cs │ ├── InterfaceTests.InterfaceWithGenericConstraint#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.InterfacesWithDifferentCasing#IApi.g.verified.cs │ ├── InterfaceTests.InterfacesWithDifferentCasing#Iapi1.g.verified.cs │ ├── InterfaceTests.InterfacesWithDifferentSignature#IApi.g.verified.cs │ ├── InterfaceTests.InterfacesWithDifferentSignature#IApi1.g.verified.cs │ ├── InterfaceTests.NestedNamespaceTest#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.NonRefitMethodShouldRaiseDiagnostic#IGeneratedClient.g.verified.cs │ ├── InterfaceTests.NonRefitMethodShouldRaiseDiagnostic.verified.txt │ ├── InterfaceTests.RefitInterfaceDerivedFromBaseTest#IGeneratedInterface.g.verified.cs │ ├── InterfaceTests.RefitInterfaceDerivedFromBaseTest.verified.txt │ ├── InterfaceTests.RefitInterfaceDerivedFromRefitBaseTest#IBaseInterface.g.verified.cs │ ├── InterfaceTests.RefitInterfaceDerivedFromRefitBaseTest#IGeneratedInterface.g.verified.cs │ ├── MethodTests.MethodsWithGenericConstraints#IGeneratedClient.g.verified.cs │ ├── MethodTests.MethodsWithGenericConstraints.verified.txt │ ├── ParameterTests.NullableRouteParameter#IGeneratedClient.g.verified.cs │ ├── ParameterTests.NullableValueTypeRouteParameter#IGeneratedClient.g.verified.cs │ ├── ParameterTests.RouteParameter#IGeneratedClient.g.verified.cs │ ├── ParameterTests.ValueTypeRouteParameter#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.GenericConstraintReturnTask#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.GenericStructConstraintReturnTask#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.GenericTaskShouldWork#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.GenericUnmanagedConstraintReturnTask#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.ReturnIObservable#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.ReturnNullableObject#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.ReturnNullableValueType#IGeneratedClient.g.verified.cs │ ├── ReturnTypeTests.ReturnUnsupportedType#IGeneratedClient.g.verified.cs │ └── ReturnTypeTests.VoidTaskShouldWork#IGeneratedClient.g.verified.cs ├── Refit.HttpClientFactory/ │ ├── HttpClientFactoryCore.cs │ ├── HttpClientFactoryExtensions.cs │ ├── HttpClientFactoryExtensions.tt │ ├── Refit.HttpClientFactory.csproj │ └── SettingsFor.cs ├── Refit.Newtonsoft.Json/ │ ├── NewtonsoftJsonContentSerializer.cs │ └── Refit.Newtonsoft.Json.csproj ├── Refit.Tests/ │ ├── API/ │ │ ├── ApiApprovalTests.Refit.DotNet8_0.verified.txt │ │ ├── ApiApprovalTests.Refit.DotNet9_0.verified.txt │ │ ├── ApiApprovalTests.cs │ │ ├── ApiExtensions.cs │ │ └── _snapshots/ │ │ ├── ApiApprovalTests.Refit.DotNet10_0.verified.txt │ │ ├── ApiApprovalTests.Refit.DotNet8_0.verified.txt │ │ └── ApiApprovalTests.Refit.DotNet9_0.verified.txt │ ├── App.config │ ├── AuthenticatedClientHandlerTests.cs │ ├── CachedRequestBuilder.cs │ ├── CamelCaseUrlParameterKeyFormatter.cs │ ├── DefaultUrlParameterFormatterTest.cs │ ├── DeliminatorSeparatedPropertyNamesContractResolver.cs │ ├── DeserializationExceptionFactoryTests.cs │ ├── ExceptionFactoryTests.cs │ ├── ExplicitInterfaceRefitTests.cs │ ├── FormValueMultimapTests.cs │ ├── GitHubApi.cs │ ├── GlobalSuppressions.cs │ ├── HttpClientFactoryExtensionsTests.cs │ ├── IDefaultInterfaceMethodTests.cs │ ├── IFooWithOtherAttribute.cs │ ├── IInterfaceWithoutRefit.cs │ ├── IServiceWithoutNamespace.cs │ ├── InheritedGenericInterfacesApi.cs │ ├── InheritedInterfacesApi.cs │ ├── InheritedInterfacesInSeparateFileApi.cs │ ├── IntegrationTestHelper.cs │ ├── InterfaceStubGenerator.cs │ ├── MethodOverloads.cs │ ├── ModuleInitializer.cs │ ├── ModuleInitializerAttribute.cs │ ├── MultipartTests.cs │ ├── MyQueryParams.cs │ ├── NamespaceCollisionApi.cs │ ├── NamespaceOverlapApi.cs │ ├── NamespaceWithGlobalAliasApi.cs │ ├── NullableReferenceTypes.cs │ ├── PartialInterfacesApi.First.cs │ ├── PartialInterfacesApi.Second.cs │ ├── ReducedUsingInsideNamespaceApi.cs │ ├── Refit.Tests.csproj │ ├── RefitSettings.cs │ ├── ReflectionTests.cs │ ├── RequestBuilder.cs │ ├── ResponseTests.cs │ ├── RestService.cs │ ├── RestServiceExceptions.cs │ ├── SerializedContentTests.cs │ ├── TypeCollisionApiA.cs │ ├── TypeCollisionApiB.cs │ ├── UniqueNameTests.cs │ ├── UseCultureAttribute.cs │ ├── Verifiers/ │ │ ├── CSharpIncrementalSourceGeneratorVerifier`1+Test.cs │ │ ├── CSharpIncrementalSourceGeneratorVerifier`1.cs │ │ ├── CSharpSourceGeneratorVerifier`1+Test.cs │ │ ├── CSharpSourceGeneratorVerifier`1.cs │ │ └── CSharpVerifierHelper.cs │ ├── XmlContentSerializerTests.cs │ └── _snapshots/ │ ├── InterfaceStubGeneratorTests.FindInterfacesSmokeTest#Generated.g.verified.cs │ ├── InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs │ ├── InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApiDisposable.g.verified.cs │ ├── InterfaceStubGeneratorTests.FindInterfacesSmokeTest#INestedGitHubApi.g.verified.cs │ ├── InterfaceStubGeneratorTests.FindInterfacesSmokeTest#PreserveAttribute.g.verified.cs │ ├── InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#Generated.g.verified.cs │ ├── InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#IServiceWithoutNamespace.g.verified.cs │ └── InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#PreserveAttribute.g.verified.cs ├── Refit.Xml/ │ ├── Refit.Xml.csproj │ └── XmlContentSerializer.cs ├── Refit.sln ├── _config.yml ├── buildtask.snk ├── config/ │ ├── filelist.txt │ └── signclient.json ├── examples/ │ ├── Meow/ │ │ ├── Meow.csproj │ │ └── Program.cs │ ├── Meow.Common/ │ │ ├── Meow.Common.csproj │ │ ├── Middleware/ │ │ │ └── HttpClientDiagnosticsHandler.cs │ │ ├── Responses/ │ │ │ └── SearchResponse.cs │ │ └── Services/ │ │ ├── CatsService.cs │ │ ├── ITheCatsAPI.cs │ │ └── Issue2056And2058Demo.cs │ ├── Meow.sln │ ├── SampleUsingLocalApi/ │ │ ├── ConsoleApplication/ │ │ │ ├── ConsoleSampleUsingLocalApi.csproj │ │ │ └── Program.cs │ │ ├── LibraryWithSDKandRefitService/ │ │ │ ├── IRestService.cs │ │ │ ├── LibraryWithSDKandRefitService.csproj │ │ │ └── ModelForTest.cs │ │ └── RestApiforTest/ │ │ ├── Controllers/ │ │ │ └── ValuesController.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── RestApiForTest.csproj │ │ ├── Startup.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ └── SampleUsingLocalApi.sln ├── stylesheets/ │ ├── pygment_trac.css │ └── styles.css └── version.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainers/Dockerfile ================================================ # [Choice] .NET version: 7.0, 7.0-bullseye-slim, 7.0-jammy, 6.0, 6.0-bullseye-slim, 6.0-jammy, 6.0-focal ARG VARIANT="7.0-jammy" FROM mcr.microsoft.com/dotnet/sdk:${VARIANT} # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends ================================================ FILE: .devcontainers/devcontainer.json ================================================ { "name": "DevContainer for .NET", "build": { "dockerfile": "./Dockerfile", "context": ".", "args": { // version: 7.0, 7.0-bullseye-slim, 7.0-jammy, 6.0, 6.0-bullseye-slim, 6.0-jammy, 6.0-focal "VARIANT": "7.0" } }, "features": { // GitHub CLI "ghcr.io/devcontainers/features/github-cli:1": { "version": "latest" }, // Install common utilities "ghcr.io/devcontainers/features/common-utils:1": { "installZsh": true, "installOhMyZsh": true, "upgradePackages": true, "username": "vscode", "uid": "1000", "gid": "1000" } }, "overrideFeatureInstallOrder": [ "ghcr.io/devcontainers/features/common-utils" ], // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. "vscode": { // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-dotnettools.csharp", "ms-vscode.PowerShell", "VisualStudioExptTeam.vscodeintellicode" ], "settings": { } } }, // Uncomment if you want to use bash in 'postCreateCommand' after the container is created "postCreateCommand": "/bin/bash ./.devcontainer/post-create.sh > ~/post-create.log", // Uncomment if you want to connect other than 'root'. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" } ================================================ FILE: .devcontainers/post-create.sh ================================================ ## Restore .NET packages and build the default solution dotnet restore ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome:http://EditorConfig.org # From https://raw.githubusercontent.com/dotnet/roslyn/master/.editorconfig # top-most EditorConfig file root = true # Don't use tabs for indentation. [*] indent_style = space trim_trailing_whitespace = true # (Please don't specify an indent_size here; that has too many unintended consequences.) # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 insert_final_newline = true charset = utf-8-bom # Xml project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # Yml/Yaml files [*.{yaml,yml}] indent_size = 2 # Powershell files [*.ps1] indent_size = 2 # JSON files [*.json] indent_size = 2 # Shell scripts [*.sh] end_of_line = lf [*.{cmd,bat}] end_of_line = crlf # Dotnet code style settings: [*.{cs,vb}] # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Put a blank line between System.* and Microsoft.* dotnet_separate_import_directive_groups = true # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion # Use language keywords instead of framework type names for type references dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion # Prefer read-only on fields dotnet_style_readonly_field = true:warning # Suggest more modern language features when available dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion dotnet_style_prefer_conditional_expression_over_return = false dotnet_style_prefer_conditional_expression_over_assignment = false dotnet_style_prefer_auto_properties = true:suggestion # Parentheses dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent # Accessibility modifiers dotnet_style_require_accessibility_modifiers = omit_if_default:suggestion # Naming Rules # Interfaces start with an I and are PascalCased dotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.symbols = interface_symbols dotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.style = pascal_case_and_prefix_with_I_style dotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.severity = warning # External members are PascalCased dotnet_naming_rule.externally_visible_members_must_be_pascal_cased.symbols = externally_visible_symbols dotnet_naming_rule.externally_visible_members_must_be_pascal_cased.style = pascal_case_style dotnet_naming_rule.externally_visible_members_must_be_pascal_cased.severity = warning # Parameters are camelCased dotnet_naming_rule.parameters_must_be_camel_cased.symbols = parameter_symbols dotnet_naming_rule.parameters_must_be_camel_cased.style = camel_case_style dotnet_naming_rule.parameters_must_be_camel_cased.severity = warning # Constants are PascalCased dotnet_naming_rule.constants_must_be_pascal_cased.symbols = constant_symbols dotnet_naming_rule.constants_must_be_pascal_cased.style = pascal_case_style dotnet_naming_rule.constants_must_be_pascal_cased.severity = warning # Uncomment this group and comment out the next group if you prefer s_ prefixes for static fields # Private static fields are prefixed with s_ and are camelCased like s_myStatic #dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.symbols = private_static_field_symbols #dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.style = camel_case_and_prefix_with_s_underscore_style #dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.severity = warning # Static readonly fields are PascalCased dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = private_static_readonly_field_symbols dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity = warning # Comment this group and uncomment out the next group if you don't want _ prefixed fields. # Private instance fields are camelCased with an _ like _myField #dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.symbols = private_field_symbols #dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.style = camel_case_and_prefix_with_underscore_style #dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.severity = warning # Private instance fields are camelCased dotnet_naming_rule.private_instance_fields_must_be_camel_cased.symbols = private_field_symbols dotnet_naming_rule.private_instance_fields_must_be_camel_cased.style = camel_case_style dotnet_naming_rule.private_instance_fields_must_be_camel_cased.severity = warning # Symbols dotnet_naming_symbols.externally_visible_symbols.applicable_kinds = class,struct,interface,enum,property,method,field,event,delegate dotnet_naming_symbols.externally_visible_symbols.applicable_accessibilities = public,internal,friend,protected,protected_internal,protected_friend,private_protected dotnet_naming_symbols.interface_symbols.applicable_kinds = interface dotnet_naming_symbols.interface_symbols.applicable_accessibilities = * dotnet_naming_symbols.parameter_symbols.applicable_kinds = parameter dotnet_naming_symbols.parameter_symbols.applicable_accessibilities = * dotnet_naming_symbols.constant_symbols.applicable_kinds = field dotnet_naming_symbols.constant_symbols.required_modifiers = const dotnet_naming_symbols.constant_symbols.applicable_accessibilities = * dotnet_naming_symbols.private_static_field_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_field_symbols.required_modifiers = static,shared dotnet_naming_symbols.private_static_field_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_static_readonly_field_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_readonly_field_symbols.required_modifiers = static,shared,readonly dotnet_naming_symbols.private_static_readonly_field_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_field_symbols.applicable_kinds = field dotnet_naming_symbols.private_field_symbols.applicable_accessibilities = private # Styles dotnet_naming_style.camel_case_style.capitalization = camel_case dotnet_naming_style.pascal_case_style.capitalization = pascal_case dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.required_prefix = s_ dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.capitalization = camel_case dotnet_naming_style.camel_case_and_prefix_with_underscore_style.required_prefix = _ dotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization = camel_case dotnet_naming_style.pascal_case_and_prefix_with_I_style.required_prefix = I dotnet_naming_style.pascal_case_and_prefix_with_I_style.capitalization = pascal_case # CSharp code style settings: [*.cs] # Modifier order csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion # Code block csharp_prefer_braces = false:none # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_switch_labels = true csharp_indent_labels = flush_left # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion # Code style defaults csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true # Prefer method-like constructs to have a block body csharp_style_expression_bodied_methods = false:none csharp_style_expression_bodied_constructors = false:none csharp_style_expression_bodied_operators = false:none # Prefer property-like constructs to have an expression-body csharp_style_expression_bodied_properties = true:none csharp_style_expression_bodied_indexers = true:none csharp_style_expression_bodied_accessors = true:none # Expression csharp_prefer_simple_default_expression = true:suggestion csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion # Pattern matching csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion # Null checking preferences csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = do_not_ignore csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain .idea/ ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: Create a report to help us improve title: "[Bug]: " labels: ["bug"] body: - type: markdown attributes: value: | ## Please note although we can't commit to any timeline, priority will be given to those who are [Contributors](https://github.com/reactiveui/ReactiveUI#contribute ) to the project. - type: textarea id: description attributes: label: Describe the bug 🐞 description: A clear and concise description of what the bug is. value: "A bug happened!" validations: required: true - type: textarea id: reproduce-steps attributes: label: Step to reproduce description: "Steps to reproduce the behavior:" value: | 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error validations: required: true - type: input id: reproduce-repository attributes: label: Reproduction repository description: "Simple repository representing the bug" placeholder: https://github.com/reactiveui/refit value: | https://github.com/reactiveui/refit validations: required: false - type: textarea id: expected-behavior attributes: label: Expected behavior description: A clear and concise description of what you expected to happen. value: This should happen... validations: required: true - type: textarea id: screenshots attributes: label: Screenshots 🖼️ description: If applicable, add screenshots to help explain your problem. validations: required: false - type: dropdown id: ide attributes: label: IDE multiple: true options: - Visual Studio 2022 - Visual Studio 2019 - Visual Studio 2017 - Visual Studio for Mac - Rider Windows - Rider macOS - Visual Studio Code - type: input id: operating-system attributes: label: Operating system description: Windows, Linux, Mac OS... validations: required: false - type: input id: system-version attributes: label: Version description: Version and distribution (if applicable) validations: required: false - type: input id: device attributes: label: Device description: Device e.g. iPhone 6 validations: required: false - type: input id: refit-version attributes: label: Refit Version description: e.g. 15.1.1 validations: required: false - type: textarea id: additional-information attributes: label: Additional information ℹ️ description: Add any other information about the problem here. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Questions url: https://github.com/reactiveui/refit/discussions about: 'For general questions about Refit, ask in the GitHub discussions' - name: Chat url: https://www.reactiveui.net/slack about: 'Our slack chat community invite' ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- **Is your feature request related to a problem? Please describe.** **Describe the solution you'd like** **Describe alternatives you've considered** **Describe suggestions on how to achieve the feature** **Additional context** ================================================ FILE: .github/renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["local>reactiveui/.github:renovate"] } ================================================ FILE: .github/workflows/ci-build.yml ================================================ name: Build on: push: branches: [ main ] pull_request: branches: [ main ] # Needed so the reusable workflow can optionally delete the temp per-OS artifacts it creates. permissions: contents: read actions: write env: productNamespacePrefix: "Refit" jobs: build: uses: reactiveui/actions-common/.github/workflows/workflow-common-setup-and-build.yml@main with: productNamespacePrefix: "Refit" srcFolder: "./" secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .github/workflows/lock.yml ================================================ name: 'Lock Threads' on: schedule: - cron: '0 0 * * *' workflow_dispatch: permissions: issues: write pull-requests: write concurrency: group: lock jobs: action: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v6 with: github-token: ${{ github.token }} issue-inactive-days: '14' pr-inactive-days: '14' issue-comment: > This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. pr-comment: > This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: permissions: contents: write id-token: write jobs: release: uses: reactiveui/actions-common/.github/workflows/workflow-common-release.yml@main with: srcFolder: "./" secrets: ES_USERNAME: ${{ secrets.ES_USERNAME }} ES_PASSWORD: ${{ secrets.ES_PASSWORD }} CREDENTIAL_ID: ${{ secrets.CREDENTIAL_ID }} ES_TOTP_SECRET: ${{ secrets.ES_TOTP_SECRET }} publish-nuget: needs: release runs-on: ubuntu-latest environment: name: release permissions: id-token: write steps: - name: Download signed packages uses: actions/download-artifact@v8 with: name: signed-nuget - name: Setup .NET uses: actions/setup-dotnet@v5 - name: NuGet login (OIDC trusted publishing) id: nuget-login uses: NuGet/login@v1 with: user: ${{ secrets.NUGET_USER }} - name: Push to NuGet shell: bash run: | for pkg in *.nupkg; do dotnet nuget push "$pkg" --source https://api.nuget.org/v3/index.json --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" done create-release: needs: [release, publish-nuget] uses: reactiveui/actions-common/.github/workflows/workflow-common-create-release.yml@main with: version: ${{ needs.release.outputs.semver2 }} ================================================ FILE: .gitignore ================================================ # Windows image file caches Thumbs.db # Folder config file Desktop.ini ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userprefs .vs/ # Build results bin/[Dd]ebug/ bin/[Rr]elease/ *_i.c *_p.c *.ilk *.meta *.obj *.pch bin/**/*.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.vspscc .builds # Idea cache files .idea/ # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.ide/ *.lock.json *.nuget.props *.nuget.targets # Visual Studio profiler *.psess *.vsp # ReSharper is a .NET coding add-in _ReSharper* # Click-Once directory publish # Visual Studio Code .vscode/ # Others [Bb]in [Oo]bj sql TestResults *.Cache ClientBin stylecop.* ~$* *.dbmdl .DS_Store Generated_Code #added for RIA/Silverlight projects # Custom # packages Release/ packages/ *.userprefs Refit-Tests/test-results /InterfaceStubGenerator.App/Properties/launchSettings.json *.binlog /InterfaceStubGenerator.Core/Properties/launchSettings.json *.svclog **/BenchmarkDotNet.Artifacts ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at anais@anaisbetts.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/3/0/ ================================================ FILE: COPYING ================================================ Copyright (c) 2012 GitHub Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: CodeCoverage.runsettings ================================================ cobertura [xunit.*]*,[*Tests]*,[System.*]*,[InterfaceStubGenerator.App]* Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute ================================================ FILE: Directory.Build.props ================================================ 2.12 true $(MSBuildProjectName.Contains('Test')) embedded .NET Foundation and Contributors Copyright (c) .NET Foundation and Contributors MIT https://github.com/reactiveui/refit refit_logo.png README.md en-US The automatic type-safe REST library for Xamarin and .NET true preview true True latest AllEnabledByDefault true true $(MSBuildThisFileDirectory)buildtask.snk net462 net8.0;net9.0;net10.0 $(RefitTargets);netstandard2.0;$(RefitTestTargets) IDE0040;CA1054;CA1510 true <_Parameter1>CommitHash <_Parameter2>$(SourceRevisionId) ================================================ FILE: InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj ================================================  netstandard2.0 InterfaceStubGeneratorV1 Refit.Generator false ..\buildtask.snk true true enable 3.8.0 true $(BuildVersion) $(BuildVersionSimple) ================================================ FILE: InterfaceStubGenerator.Roslyn41/InterfaceStubGenerator.Roslyn41.csproj ================================================  netstandard2.0 InterfaceStubGeneratorV2 Refit.Generator false ..\buildtask.snk true true enable $(DefineConstants);ROSLYN_4 4.1.0 true $(BuildVersion) $(BuildVersionSimple) ================================================ FILE: InterfaceStubGenerator.Roslyn50/InterfaceStubGenerator.Roslyn50.csproj ================================================  netstandard2.0 InterfaceStubGeneratorV3 Refit.Generator false ..\buildtask.snk true true enable $(DefineConstants);ROSLYN_4;ROSLYN_5 5.0.0 true true $(BuildVersion) $(BuildVersionSimple) ================================================ FILE: InterfaceStubGenerator.Shared/DiagnosticDescriptors.cs ================================================ using Microsoft.CodeAnalysis; namespace Refit.Generator; internal static class DiagnosticDescriptors { #pragma warning disable RS2008 // Enable analyzer release tracking public static readonly DiagnosticDescriptor InvalidRefitMember = new( "RF001", "Refit types must have Refit HTTP method attributes", "Method {0}.{1} either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument", "Refit", DiagnosticSeverity.Warning, true ); public static readonly DiagnosticDescriptor RefitNotReferenced = new( "RF002", "Refit must be referenced", "Refit is not referenced. Add a reference to Refit.", "Refit", DiagnosticSeverity.Error, true ); #pragma warning restore RS2008 // Enable analyzer release tracking } ================================================ FILE: InterfaceStubGenerator.Shared/Emitter.cs ================================================ using System.Linq; using System.Text; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator; internal static class Emitter { private const string TypeParameterVariableName = "______typeParameters"; public static void EmitSharedCode( ContextGenerationModel model, Action addSource ) { if (model.Interfaces.Count == 0) return; var attributeText = $$""" #pragma warning disable namespace {{model.RefitInternalNamespace}} { [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.AttributeUsage (global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Enum | global::System.AttributeTargets.Constructor | global::System.AttributeTargets.Method | global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Event | global::System.AttributeTargets.Interface | global::System.AttributeTargets.Delegate)] sealed class PreserveAttribute : global::System.Attribute { // // Fields // public bool AllMembers; public bool Conditional; } } #pragma warning restore """; // add the attribute text addSource("PreserveAttribute.g.cs", SourceText.From(attributeText, Encoding.UTF8)); var generatedClassText = $$""" #pragma warning disable namespace Refit.Implementation { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [{{model.PreserveAttributeDisplayName}}] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] internal static partial class Generated { #if NET5_0_OR_GREATER [System.Runtime.CompilerServices.ModuleInitializer] [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(global::Refit.Implementation.Generated))] public static void Initialize() { } #endif } } #pragma warning restore """; addSource("Generated.g.cs", SourceText.From(generatedClassText, Encoding.UTF8)); } public static SourceText EmitInterface(InterfaceModel model) { var source = new SourceWriter(); // if nullability is supported emit the nullable directive if (model.Nullability != Nullability.None) { source.WriteLine( "#nullable " + (model.Nullability == Nullability.Enabled ? "enable" : "disable") ); } source.WriteLine( $$""" #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [{{model.PreserveAttributeDisplayName}}] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class {{model.Ns}}{{model.ClassDeclaration}} : {{model.InterfaceDisplayName}} """ ); source.Indentation += 2; GenerateConstraints(source, model.Constraints, false); source.Indentation--; source.WriteLine( $$""" { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public {{model.Ns}}{{model.ClassSuffix}}(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } """ ); source.Indentation++; var uniqueNames = new UniqueNameBuilder(); uniqueNames.Reserve(model.MemberNames); // Handle Refit Methods foreach (var method in model.RefitMethods) { WriteRefitMethod(source, method, true, uniqueNames); } foreach (var method in model.DerivedRefitMethods) { WriteRefitMethod(source, method, false, uniqueNames); } // Handle non-refit Methods that aren't static or properties or have a method body foreach (var method in model.NonRefitMethods) { WriteNonRefitMethod(source, method); } // Handle Dispose if (model.DisposeMethod) { WriteDisposableMethod(source); } source.Indentation -= 2; source.WriteLine( """ } } } #pragma warning restore """ ); return source.ToSourceText(); } /// /// Generates the body of the Refit method /// /// /// /// True if directly from the type we're generating for, false for methods found on base interfaces /// Contains the unique member names in the interface scope. private static void WriteRefitMethod( SourceWriter source, MethodModel methodModel, bool isTopLevel, UniqueNameBuilder uniqueNames ) { var parameterTypesExpression = GenerateTypeParameterExpression( source, methodModel, uniqueNames ); var returnType = methodModel.ReturnType; var (isAsync, @return, configureAwait) = methodModel.ReturnTypeMetadata switch { ReturnTypeInfo.AsyncVoid => (true, "await (", ").ConfigureAwait(false)"), ReturnTypeInfo.AsyncResult => (true, "return await (", ").ConfigureAwait(false)"), ReturnTypeInfo.Return => (false, "return ", ""), _ => throw new ArgumentOutOfRangeException( nameof(methodModel.ReturnTypeMetadata), methodModel.ReturnTypeMetadata, "Unsupported value." ), }; var isExplicit = methodModel.IsExplicitInterface || !isTopLevel; WriteMethodOpening(source, methodModel, isExplicit, isExplicit, isAsync); // Build the list of args for the array var argArray = methodModel .Parameters.AsArray() .Select(static param => $"@{param.MetadataName}") .ToArray(); // List of generic arguments var genericArray = methodModel .Constraints.AsArray() .Select(static typeParam => $"typeof({typeParam.DeclaredName})") .ToArray(); var argumentsArrayString = argArray.Length == 0 ? "global::System.Array.Empty()" : $"new object[] {{ {string.Join(", ", argArray)} }}"; var genericString = genericArray.Length > 0 ? $", new global::System.Type[] {{ {string.Join(", ", genericArray)} }}" : string.Empty; // Normalize method lookup key: strip explicit interface prefix if present (e.g. IFoo.Bar -> Bar) var lookupName = methodModel.Name; var lastDotIndex = lookupName.LastIndexOf('.'); if (lastDotIndex >= 0 && lastDotIndex < lookupName.Length - 1) { lookupName = lookupName.Substring(lastDotIndex + 1); } source.WriteLine( $""" var ______arguments = {argumentsArrayString}; var ______func = requestBuilder.BuildRestResultFuncForMethod("{lookupName}", {parameterTypesExpression}{genericString} ); {@return}({returnType})______func(this.Client, ______arguments){configureAwait}; """ ); WriteMethodClosing(source); } private static void WriteNonRefitMethod(SourceWriter source, MethodModel methodModel) { var isExplicit = methodModel.IsExplicitInterface; WriteMethodOpening(source, methodModel, isExplicit, isExplicit); source.WriteLine( @"throw new global::System.NotImplementedException(""Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."");" ); WriteMethodClosing(source); } private static void WriteDisposableMethod(SourceWriter source) { source.WriteLine( """ /// void global::System.IDisposable.Dispose() { Client?.Dispose(); } """ ); } private static string GenerateTypeParameterExpression( SourceWriter source, MethodModel methodModel, UniqueNameBuilder uniqueNames ) { // use Array.Empty if method has no parameters. if (methodModel.Parameters.Count == 0) return "global::System.Array.Empty()"; // if one of the parameters is/contains a type parameter then it cannot be cached as it will change type between calls. if (methodModel.Parameters.Any(x => x.IsGeneric)) { var typeEnumerable = methodModel.Parameters.Select(param => $"typeof({param.Type})"); return $"new global::System.Type[] {{ {string.Join(", ", typeEnumerable)} }}"; } // find a name and generate field declaration. var typeParameterFieldName = uniqueNames.New(TypeParameterVariableName); var types = string.Join(", ", methodModel.Parameters.Select(x => $"typeof({x.Type})")); source.WriteLine( $$""" private static readonly global::System.Type[] {{typeParameterFieldName}} = new global::System.Type[] {{{types}} }; """ ); return typeParameterFieldName; } private static void WriteMethodOpening( SourceWriter source, MethodModel methodModel, bool isDerivedExplicitImpl, bool isExplicitInterface, bool isAsync = false ) { var visibility = !isExplicitInterface ? "public " : string.Empty; var async = isAsync ? "async " : ""; var builder = new StringBuilder(); builder.Append( @$"/// {visibility}{async}{methodModel.ReturnType} " ); if (isExplicitInterface) { var ct = methodModel.ContainingType; if (!ct.StartsWith("global::")) { ct = "global::" + ct; } builder.Append(@$"{ct}."); } builder.Append(@$"{methodModel.DeclaredMethod}("); if (methodModel.Parameters.Count > 0) { var list = new List(); foreach (var param in methodModel.Parameters) { var annotation = param.Annotation; list.Add($@"{param.Type}{(annotation ? '?' : string.Empty)} @{param.MetadataName}"); } builder.Append(string.Join(", ", list)); } builder.Append(")"); source.WriteLine(); source.WriteLine(builder.ToString()); source.Indentation++; GenerateConstraints(source, methodModel.Constraints, isDerivedExplicitImpl || isExplicitInterface); source.Indentation--; source.WriteLine("{"); source.Indentation++; } private static void WriteMethodClosing(SourceWriter source) { source.Indentation--; source.WriteLine("}"); } private static void GenerateConstraints( SourceWriter writer, ImmutableEquatableArray typeParameters, bool isOverrideOrExplicitImplementation ) { // Need to loop over the constraints and create them foreach (var typeParameter in typeParameters) { WriteConstraintsForTypeParameter( writer, typeParameter, isOverrideOrExplicitImplementation ); } } private static void WriteConstraintsForTypeParameter( SourceWriter source, TypeConstraint typeParameter, bool isOverrideOrExplicitImplementation ) { // Explicit interface implementations and overrides can only have class or struct constraints var parameters = new List(); var knownConstraints = typeParameter.KnownTypeConstraint; if (knownConstraints.HasFlag(KnownTypeConstraint.Class)) { parameters.Add("class"); } if ( knownConstraints.HasFlag(KnownTypeConstraint.Unmanaged) && !isOverrideOrExplicitImplementation ) { parameters.Add("unmanaged"); } if (knownConstraints.HasFlag(KnownTypeConstraint.Struct)) { parameters.Add("struct"); } if ( knownConstraints.HasFlag(KnownTypeConstraint.NotNull) && !isOverrideOrExplicitImplementation ) { parameters.Add("notnull"); } if (!isOverrideOrExplicitImplementation) { parameters.AddRange(typeParameter.Constraints); } // new constraint has to be last if ( knownConstraints.HasFlag(KnownTypeConstraint.New) && !isOverrideOrExplicitImplementation ) { parameters.Add("new()"); } if (parameters.Count > 0) { source.WriteLine($"where {typeParameter.TypeName} : {string.Join(", ", parameters)}"); } } } ================================================ FILE: InterfaceStubGenerator.Shared/ITypeSymbolExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; namespace Refit.Generator { static class ITypeSymbolExtensions { public static IEnumerable GetBaseTypesAndThis(this ITypeSymbol? type) { var current = type; while (current != null) { yield return current; current = current.BaseType; } } // Determine if "type" inherits from "baseType", ignoring constructed types, optionally including interfaces, // dealing only with original types. public static bool InheritsFromOrEquals( this ITypeSymbol type, ITypeSymbol baseType, bool includeInterfaces ) { if (!includeInterfaces) { return InheritsFromOrEquals(type, baseType); } return type.GetBaseTypesAndThis() .Concat(type.AllInterfaces) .Any(t => t.Equals(baseType, SymbolEqualityComparer.Default)); } // Determine if "type" inherits from "baseType", ignoring constructed types and interfaces, dealing // only with original types. public static bool InheritsFromOrEquals(this ITypeSymbol type, ITypeSymbol baseType) { return type.GetBaseTypesAndThis() .Any(t => t.Equals(baseType, SymbolEqualityComparer.Default)); } } } ================================================ FILE: InterfaceStubGenerator.Shared/ImmutableEquatableArray.cs ================================================ using System.Collections; namespace Refit.Generator; internal static class ImmutableEquatableArray { public static ImmutableEquatableArray Empty() where T : IEquatable => ImmutableEquatableArray.Empty; public static ImmutableEquatableArray ToImmutableEquatableArray( this IEnumerable? values ) where T : IEquatable => values == null ? Empty() : new(values); } /// /// Provides an immutable list implementation which implements sequence equality. /// internal sealed class ImmutableEquatableArray : IEquatable>, IReadOnlyList where T : IEquatable { public static ImmutableEquatableArray Empty { get; } = new(Array.Empty()); private readonly T[] _values; public T this[int index] => _values[index]; public int Count => _values.Length; public ImmutableEquatableArray(T[] values) => _values = values; public ImmutableEquatableArray(IEnumerable values) => _values = values.ToArray(); public T[] AsArray() => _values; public bool Equals(ImmutableEquatableArray? other) => other != null && ((ReadOnlySpan)_values).SequenceEqual(other._values); public override bool Equals(object? obj) => obj is ImmutableEquatableArray other && Equals(other); public override int GetHashCode() { var hash = 0; foreach (T value in _values) { hash = Combine(hash, value.GetHashCode()); } static int Combine(int h1, int h2) { // RyuJIT optimizes this to use the ROL instruction // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830 uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); return ((int)rol5 + h1) ^ h2; } return hash; } public Enumerator GetEnumerator() => new(_values); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); public record struct Enumerator { private readonly T[] _values; private int _index; internal Enumerator(T[] values) { _values = values; _index = -1; } public bool MoveNext() { var newIndex = _index + 1; if ((uint)newIndex < (uint)_values.Length) { _index = newIndex; return true; } return false; } public readonly T Current => _values[_index]; } } ================================================ FILE: InterfaceStubGenerator.Shared/IncrementalValuesProviderExtensions.cs ================================================ #if ROSLYN_4 using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator; internal static class IncrementalValuesProviderExtensions { /// /// Registers an output node into an to output a diagnostic. /// /// The input instance. /// The input sequence of diagnostics. public static void ReportDiagnostics( this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider diagnostic ) { context.RegisterSourceOutput( diagnostic, static (context, diagnostic) => context.ReportDiagnostic(diagnostic) ); } /// /// Registers an output node into an to output diagnostics. /// /// The input instance. /// The input sequence of diagnostics. public static void ReportDiagnostics( this IncrementalGeneratorInitializationContext context, IncrementalValueProvider> diagnostics ) { context.RegisterSourceOutput( diagnostics, static (context, diagnostics) => { foreach (var diagnostic in diagnostics) { context.ReportDiagnostic(diagnostic); } } ); } /// /// Registers an implementation source output for the provided mappers. /// /// The context, on which the output is registered. /// The interfaces stubs. public static void EmitSource( this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider model ) { context.RegisterImplementationSourceOutput( model, static (spc, model) => { var mapperText = Emitter.EmitInterface(model); spc.AddSource(model.FileName, mapperText); } ); } } #endif ================================================ FILE: InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) true b591423d-f92d-4e00-b0eb-615c9853506c InterfaceStubGenerator.Shared ================================================ FILE: InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.shproj ================================================ b591423d-f92d-4e00-b0eb-615c9853506c 14.0 ================================================ FILE: InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator { /// /// InterfaceStubGenerator. /// [Generator] #if ROSLYN_4 public class InterfaceStubGeneratorV2 : IIncrementalGenerator #else public class InterfaceStubGenerator : ISourceGenerator #endif { private const string TypeParameterVariableName = "______typeParameters"; #if !ROSLYN_4 /// /// Executes the specified context. /// /// The context. public void Execute(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not SyntaxReceiver receiver) return; context.AnalyzerConfigOptions.GlobalOptions.TryGetValue( "build_property.RefitInternalNamespace", out var refitInternalNamespace ); var parseStep = Parser.GenerateInterfaceStubs( (CSharpCompilation)context.Compilation, refitInternalNamespace, receiver.CandidateMethods.ToImmutableArray(), receiver.CandidateInterfaces.ToImmutableArray(), context.CancellationToken ); foreach (var diagnostic in parseStep.diagnostics) { context.ReportDiagnostic(diagnostic); } foreach (var interfaceModel in parseStep.contextGenerationSpec.Interfaces) { var interfaceText = Emitter.EmitInterface(interfaceModel); context.AddSource( interfaceModel.FileName, interfaceText ); } Emitter.EmitSharedCode( parseStep.contextGenerationSpec, (name, code) => context.AddSource(name, code) ); } #endif #if ROSLYN_4 /// public void Initialize(IncrementalGeneratorInitializationContext context) { var candidateMethodsProvider = context.SyntaxProvider.CreateSyntaxProvider( (syntax, cancellationToken) => syntax is MethodDeclarationSyntax { Parent: InterfaceDeclarationSyntax, AttributeLists.Count: > 0 }, (context, cancellationToken) => (MethodDeclarationSyntax)context.Node ); var candidateInterfacesProvider = context.SyntaxProvider.CreateSyntaxProvider( (syntax, cancellationToken) => syntax is InterfaceDeclarationSyntax { BaseList: not null }, (context, cancellationToken) => (InterfaceDeclarationSyntax)context.Node ); var refitInternalNamespace = context.AnalyzerConfigOptionsProvider.Select( (analyzerConfigOptionsProvider, cancellationToken) => analyzerConfigOptionsProvider.GlobalOptions.TryGetValue( "build_property.RefitInternalNamespace", out var refitInternalNamespace ) ? refitInternalNamespace : null ); var inputs = candidateMethodsProvider .Collect() .Combine(candidateInterfacesProvider.Collect()) .Select( (combined, cancellationToken) => (candidateMethods: combined.Left, candidateInterfaces: combined.Right) ) .Combine(refitInternalNamespace) .Combine(context.CompilationProvider) .Select( (combined, cancellationToken) => ( combined.Left.Left.candidateMethods, combined.Left.Left.candidateInterfaces, refitInternalNamespace: combined.Left.Right, compilation: combined.Right ) ); var parseStep = inputs.Select( (collectedValues, cancellationToken) => { return Parser.GenerateInterfaceStubs( (CSharpCompilation)collectedValues.compilation, collectedValues.refitInternalNamespace, collectedValues.candidateMethods, collectedValues.candidateInterfaces, cancellationToken ); } ); var diagnostics = parseStep .Select(static (x, _) => x.diagnostics.ToImmutableEquatableArray()) .WithTrackingName(RefitGeneratorStepName.ReportDiagnostics); context.ReportDiagnostics(diagnostics); var contextModel = parseStep.Select(static (x, _) => x.Item2); var interfaceModels = contextModel .SelectMany(static (x, _) => x.Interfaces) .WithTrackingName(RefitGeneratorStepName.BuildRefit); context.EmitSource(interfaceModels); context.RegisterImplementationSourceOutput( contextModel, static (spc, model) => { Emitter.EmitSharedCode(model, (name, code) => spc.AddSource(name, code)); } ); } #else /// /// Initializes the specified context. /// /// The context. public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); } class SyntaxReceiver : ISyntaxReceiver { public List CandidateMethods { get; } = []; public List CandidateInterfaces { get; } = []; public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if ( syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax && methodDeclarationSyntax.Parent is InterfaceDeclarationSyntax && methodDeclarationSyntax.AttributeLists.Count > 0 ) { CandidateMethods.Add(methodDeclarationSyntax); } if (syntaxNode is InterfaceDeclarationSyntax iface && iface.BaseList is not null) { CandidateInterfaces.Add(iface); } } } #endif } internal static class RefitGeneratorStepName { public const string ReportDiagnostics = "ReportDiagnostics"; public const string BuildRefit = "BuildRefit"; } } ================================================ FILE: InterfaceStubGenerator.Shared/IsExternalInit.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 using System.ComponentModel; // ReSharper disable once CheckNamespace namespace System.Runtime.CompilerServices { /// /// Reserved to be used by the compiler for tracking metadata. /// This class should not be used by developers in source code. /// [EditorBrowsable(EditorBrowsableState.Never)] internal static class IsExternalInit { } } #endif ================================================ FILE: InterfaceStubGenerator.Shared/Models/ContextGenerationModel.cs ================================================ namespace Refit.Generator; internal sealed record ContextGenerationModel( string RefitInternalNamespace, string PreserveAttributeDisplayName, ImmutableEquatableArray Interfaces ); ================================================ FILE: InterfaceStubGenerator.Shared/Models/InterfaceModel.cs ================================================ namespace Refit.Generator; internal sealed record InterfaceModel( string PreserveAttributeDisplayName, string FileName, string ClassName, string Ns, string ClassDeclaration, string InterfaceDisplayName, string ClassSuffix, ImmutableEquatableArray Constraints, ImmutableEquatableArray MemberNames, ImmutableEquatableArray NonRefitMethods, ImmutableEquatableArray RefitMethods, ImmutableEquatableArray DerivedRefitMethods, Nullability Nullability, bool DisposeMethod ); internal enum Nullability : byte { Enabled, Disabled, None } ================================================ FILE: InterfaceStubGenerator.Shared/Models/MethodModel.cs ================================================ using System.Collections.Immutable; namespace Refit.Generator; internal sealed record MethodModel( string Name, string ReturnType, string ContainingType, string DeclaredMethod, ReturnTypeInfo ReturnTypeMetadata, ImmutableEquatableArray Parameters, ImmutableEquatableArray Constraints, bool IsExplicitInterface ); internal enum ReturnTypeInfo : byte { Return, AsyncVoid, AsyncResult } ================================================ FILE: InterfaceStubGenerator.Shared/Models/ParameterModel.cs ================================================ namespace Refit.Generator; internal sealed record ParameterModel( string MetadataName, string Type, bool Annotation, bool IsGeneric ); ================================================ FILE: InterfaceStubGenerator.Shared/Models/TypeConstraint.cs ================================================ namespace Refit.Generator; internal readonly record struct TypeConstraint( string TypeName, string DeclaredName, KnownTypeConstraint KnownTypeConstraint, ImmutableEquatableArray Constraints ); [Flags] internal enum KnownTypeConstraint : byte { None = 0, Class = 1 << 0, Unmanaged = 1 << 1, Struct = 1 << 2, NotNull = 1 << 3, New = 1 << 4 } ================================================ FILE: InterfaceStubGenerator.Shared/Models/WellKnownTypes.cs ================================================ using Microsoft.CodeAnalysis; namespace Refit.Generator; /// /// WellKnownTypes. /// public class WellKnownTypes(Compilation compilation) { readonly Dictionary cachedTypes = []; /// /// Gets this instance. /// /// /// public INamedTypeSymbol Get() => Get(typeof(T)); /// /// Gets the specified type. /// /// The type. /// /// Could not get name of type " + type public INamedTypeSymbol Get(Type type) { if (type is null) { throw new ArgumentNullException(nameof(type)); } return Get(type.FullName ?? throw new InvalidOperationException("Could not get name of type " + type)); } /// /// Tries the get. /// /// Full name of the type. /// public INamedTypeSymbol? TryGet(string typeFullName) { if (cachedTypes.TryGetValue(typeFullName, out var typeSymbol)) { return typeSymbol; } typeSymbol = compilation.GetTypeByMetadataName(typeFullName); cachedTypes.Add(typeFullName, typeSymbol); return typeSymbol; } INamedTypeSymbol Get(string typeFullName) => TryGet(typeFullName) ?? throw new InvalidOperationException("Could not get type " + typeFullName); } ================================================ FILE: InterfaceStubGenerator.Shared/Parser.cs ================================================ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator; internal static class Parser { /// /// Generates the interface stubs. /// /// The compilation. /// The refit internal namespace. /// The candidate methods. /// The candidate interfaces. /// The cancellation token. /// public static ( List diagnostics, ContextGenerationModel contextGenerationSpec ) GenerateInterfaceStubs( CSharpCompilation compilation, string? refitInternalNamespace, ImmutableArray candidateMethods, ImmutableArray candidateInterfaces, CancellationToken cancellationToken ) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); var wellKnownTypes = new WellKnownTypes(compilation); refitInternalNamespace = $"{refitInternalNamespace ?? string.Empty}RefitInternalGenerated"; // Remove - as they are valid in csproj, but invalid in a namespace refitInternalNamespace = refitInternalNamespace.Replace('-', '_').Replace('@', '_'); // we're going to create a new compilation that contains the attribute. // TODO: we should allow source generators to provide source during initialize, so that this step isn't required. var options = (CSharpParseOptions)compilation.SyntaxTrees[0].Options; var disposableInterfaceSymbol = wellKnownTypes.Get(typeof(IDisposable)); var httpMethodBaseAttributeSymbol = wellKnownTypes.TryGet( "Refit.HttpMethodAttribute" ); var diagnostics = new List(); if (httpMethodBaseAttributeSymbol == null) { diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.RefitNotReferenced, null)); return ( diagnostics, new ContextGenerationModel( refitInternalNamespace, string.Empty, ImmutableEquatableArray.Empty() ) ); } // Check the candidates and keep the ones we're actually interested in #pragma warning disable RS1024 // Compare symbols correctly var interfaceToNullableEnabledMap = new Dictionary( SymbolEqualityComparer.Default ); #pragma warning restore RS1024 // Compare symbols correctly var methodSymbols = new List(); foreach (var group in candidateMethods.GroupBy(m => m.SyntaxTree)) { var model = compilation.GetSemanticModel(group.Key); foreach (var method in group) { // Get the symbol being declared by the method var methodSymbol = model.GetDeclaredSymbol( method, cancellationToken: cancellationToken ); if (!IsRefitMethod(methodSymbol, httpMethodBaseAttributeSymbol)) continue; var isAnnotated = compilation.Options.NullableContextOptions == NullableContextOptions.Enable || model.GetNullableContext(method.SpanStart) == NullableContext.Enabled; interfaceToNullableEnabledMap[methodSymbol!.ContainingType] = isAnnotated; methodSymbols.Add(methodSymbol!); } } var interfaces = methodSymbols .GroupBy( m => m.ContainingType, SymbolEqualityComparer.Default ) .ToDictionary< IGrouping, INamedTypeSymbol, List >(g => g.Key, v => [.. v], SymbolEqualityComparer.Default); // Look through the candidate interfaces var interfaceSymbols = new List(); foreach (var group in candidateInterfaces.GroupBy(i => i.SyntaxTree)) { var model = compilation.GetSemanticModel(group.Key); foreach (var iface in group) { // get the symbol belonging to the interface var ifaceSymbol = model.GetDeclaredSymbol( iface, cancellationToken: cancellationToken ); // See if we already know about it, might be a dup if (ifaceSymbol is null || interfaces.ContainsKey(ifaceSymbol)) continue; // The interface has no refit methods, but its base interfaces might var hasDerivedRefit = ifaceSymbol .AllInterfaces.SelectMany(i => i.GetMembers().OfType()) .Any(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol)); if (hasDerivedRefit) { // Add the interface to the generation list with an empty set of methods // The logic already looks for base refit methods interfaces.Add(ifaceSymbol, []); var isAnnotated = model.GetNullableContext(iface.SpanStart) == NullableContext.Enabled; interfaceToNullableEnabledMap[ifaceSymbol] = isAnnotated; } } } cancellationToken.ThrowIfCancellationRequested(); // Bail out if there aren't any interfaces to generate code for. This may be the case with transitives if (interfaces.Count == 0) return ( diagnostics, new ContextGenerationModel( refitInternalNamespace, string.Empty, ImmutableEquatableArray.Empty() ) ); var supportsNullable = options.LanguageVersion >= LanguageVersion.CSharp8; var keyCount = new Dictionary(StringComparer.OrdinalIgnoreCase); var attributeText = @$" #pragma warning disable namespace {refitInternalNamespace} {{ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.AttributeUsage (global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Enum | global::System.AttributeTargets.Constructor | global::System.AttributeTargets.Method | global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Event | global::System.AttributeTargets.Interface | global::System.AttributeTargets.Delegate)] sealed class PreserveAttribute : global::System.Attribute {{ // // Fields // public bool AllMembers; public bool Conditional; }} }} #pragma warning restore "; // TODO: Delete? // Is it necessary to add the attributes to the compilation now, does it affect the users ide experience? // Is it needed in order to get the preserve attribute display name. // Will the compilation ever change this. compilation = compilation.AddSyntaxTrees( CSharpSyntaxTree.ParseText( SourceText.From(attributeText, Encoding.UTF8), options, cancellationToken: cancellationToken ) ); // get the newly bound attribute var preserveAttributeSymbol = compilation.GetTypeByMetadataName( $"{refitInternalNamespace}.PreserveAttribute" )!; var preserveAttributeDisplayName = preserveAttributeSymbol.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); var interfaceModels = new List(); // group the fields by interface and generate the source foreach (var group in interfaces) { cancellationToken.ThrowIfCancellationRequested(); // each group is keyed by the Interface INamedTypeSymbol and contains the members // with a refit attribute on them. Types may contain other members, without the attribute, which we'll // need to check for and error out on var keyName = group.Key.Name; int value; while (keyCount.TryGetValue(keyName, out value)) { keyName = $"{keyName}{++value}"; } keyCount[keyName] = value; var fileName = $"{keyName}.g.cs"; var interfaceModel = ProcessInterface( fileName, diagnostics, group.Key, group.Value, preserveAttributeDisplayName, disposableInterfaceSymbol, httpMethodBaseAttributeSymbol, supportsNullable, interfaceToNullableEnabledMap[group.Key] ); interfaceModels.Add(interfaceModel); } var contextGenerationSpec = new ContextGenerationModel( refitInternalNamespace, preserveAttributeDisplayName, interfaceModels.ToImmutableEquatableArray() ); return (diagnostics, contextGenerationSpec); } static InterfaceModel ProcessInterface( string fileName, List diagnostics, INamedTypeSymbol interfaceSymbol, List refitMethods, string preserveAttributeDisplayName, ISymbol disposableInterfaceSymbol, INamedTypeSymbol httpMethodBaseAttributeSymbol, bool supportsNullable, bool nullableEnabled ) { // Get the class name with the type parameters, then remove the namespace var className = interfaceSymbol.ToDisplayString(); var lastDot = className.LastIndexOf('.'); if (lastDot > 0) { className = className.Substring(lastDot + 1); } var classDeclaration = $"{interfaceSymbol.ContainingType?.Name}{className}"; // Get the class name itself var classSuffix = $"{interfaceSymbol.ContainingType?.Name}{interfaceSymbol.Name}"; var ns = interfaceSymbol.ContainingNamespace?.ToDisplayString(); // if it's the global namespace, our lookup rules say it should be the same as the class name if (interfaceSymbol.ContainingNamespace is { IsGlobalNamespace: true }) { ns = string.Empty; } // Remove dots ns = ns!.Replace(".", ""); var interfaceDisplayName = interfaceSymbol.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); // Get any other methods on the refit interfaces. We'll need to generate something for them and warn var nonRefitMethods = interfaceSymbol .GetMembers() .OfType() .Except(refitMethods, SymbolEqualityComparer.Default) .Cast() .ToArray(); // get methods for all inherited var derivedMethods = interfaceSymbol .AllInterfaces.SelectMany(i => i.GetMembers().OfType()) .ToList(); // Look for disposable var disposeMethod = derivedMethods.Find( m => m.ContainingType?.Equals(disposableInterfaceSymbol, SymbolEqualityComparer.Default) == true ); if (disposeMethod != null) { //remove it from the derived methods list so we don't process it with the rest derivedMethods.Remove(disposeMethod); } // Pull out the refit methods from the derived types var derivedRefitMethods = derivedMethods .Where(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol)) .ToArray(); var derivedNonRefitMethods = derivedMethods .Except(derivedRefitMethods, SymbolEqualityComparer.Default) .Cast() .ToArray(); // Exclude base interface methods that the current interface explicitly implements. // This avoids false positive RF001 diagnostics for cases like: // interface IFoo { int Bar(); } and interface IRemoteFoo : IFoo { [Get] abstract int IFoo.Bar(); } if (derivedNonRefitMethods.Length > 0) { var explicitlyImplementedBaseMethods = new HashSet( SymbolEqualityComparer.Default ); foreach (var member in interfaceSymbol.GetMembers().OfType()) { foreach (var baseMethod in member.ExplicitInterfaceImplementations) { // Use OriginalDefinition for robustness when comparing generic methods explicitlyImplementedBaseMethods.Add( baseMethod.OriginalDefinition ?? baseMethod ); } } if (explicitlyImplementedBaseMethods.Count > 0) { derivedNonRefitMethods = derivedNonRefitMethods .Where(m => !explicitlyImplementedBaseMethods.Contains(m.OriginalDefinition ?? m)) .ToArray(); } } var memberNames = interfaceSymbol .GetMembers() .Select(x => x.Name) .Distinct() .ToImmutableEquatableArray(); // Handle Refit Methods var refitMethodsArray = refitMethods .Select(m => ParseMethod(m, true)) .ToImmutableEquatableArray(); // Only include refit methods discovered on base interfaces here. // Do NOT duplicate the current interface's refit methods. var derivedRefitMethodsArray = derivedRefitMethods .Select(m => ParseMethod(m, false)) .ToImmutableEquatableArray(); // Handle non-refit Methods that aren't static or properties or have a method body var nonRefitMethodModelList = new List(); foreach (var method in nonRefitMethods) { if ( method.IsStatic || method.MethodKind == MethodKind.PropertyGet || method.MethodKind == MethodKind.PropertySet || !method.IsAbstract ) continue; nonRefitMethodModelList.Add(ParseNonRefitMethod(method, diagnostics, isDerived: false)); } foreach (var method in derivedNonRefitMethods) { if ( method.IsStatic || method.MethodKind == MethodKind.PropertyGet || method.MethodKind == MethodKind.PropertySet || !method.IsAbstract ) continue; // Derived non-refit methods should be emitted as explicit interface implementations nonRefitMethodModelList.Add(ParseNonRefitMethod(method, diagnostics, isDerived: true)); } var nonRefitMethodModels = nonRefitMethodModelList.ToImmutableEquatableArray(); var constraints = GenerateConstraints(interfaceSymbol.TypeParameters, false); var hasDispose = disposeMethod != null; var nullability = (supportsNullable, nullableEnabled) switch { (false, _) => Nullability.None, (true, true) => Nullability.Enabled, (true, false) => Nullability.Disabled, }; return new InterfaceModel( preserveAttributeDisplayName, fileName, className, ns, classDeclaration, interfaceDisplayName, classSuffix, constraints, memberNames, nonRefitMethodModels, refitMethodsArray, derivedRefitMethodsArray, nullability, hasDispose ); } private static MethodModel ParseNonRefitMethod( IMethodSymbol methodSymbol, List diagnostics, bool isDerived ) { // report invalid error diagnostic foreach (var location in methodSymbol.Locations) { var diagnostic = Diagnostic.Create( DiagnosticDescriptors.InvalidRefitMember, location, methodSymbol.ContainingType.Name, methodSymbol.Name ); diagnostics.Add(diagnostic); } // Parse like a regular method, but force explicit implementation for derived base-interface methods var explicitImpl = methodSymbol.ExplicitInterfaceImplementations.FirstOrDefault(); var containingTypeSymbol = explicitImpl?.ContainingType ?? methodSymbol.ContainingType; var containingType = containingTypeSymbol.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); // Method name should be simple name only (never include interface qualifier) var declaredBaseName = methodSymbol.Name; var lastDot = declaredBaseName.LastIndexOf('.'); if (lastDot >= 0) { declaredBaseName = declaredBaseName.Substring(lastDot + 1); } if (methodSymbol.TypeParameters.Length > 0) { var typeParams = string.Join( ", ", methodSymbol.TypeParameters.Select( tp => tp.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ) ); declaredBaseName += $"<{typeParams}>"; } var returnType = methodSymbol.ReturnType.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); var returnTypeInfo = methodSymbol.ReturnType.MetadataName switch { "Task" => ReturnTypeInfo.AsyncVoid, "Task`1" or "ValueTask`1" => ReturnTypeInfo.AsyncResult, _ => ReturnTypeInfo.Return, }; var parameters = methodSymbol.Parameters.Select(ParseParameter).ToImmutableEquatableArray(); var isExplicit = isDerived || explicitImpl is not null; var constraints = GenerateConstraints(methodSymbol.TypeParameters, isExplicit); return new MethodModel( methodSymbol.Name, returnType, containingType, declaredBaseName, returnTypeInfo, parameters, constraints, isExplicit ); } private static bool IsRefitMethod( IMethodSymbol? methodSymbol, INamedTypeSymbol httpMethodAttribute ) { return methodSymbol ?.GetAttributes() .Any(ad => ad.AttributeClass?.InheritsFromOrEquals(httpMethodAttribute) == true) == true; } private static ImmutableEquatableArray GenerateConstraints( ImmutableArray typeParameters, bool isOverrideOrExplicitImplementation ) { // Need to loop over the constraints and create them return typeParameters .Select( typeParameter => ParseConstraintsForTypeParameter( typeParameter, isOverrideOrExplicitImplementation ) ) .ToImmutableEquatableArray(); } private static TypeConstraint ParseConstraintsForTypeParameter( ITypeParameterSymbol typeParameter, bool isOverrideOrExplicitImplementation ) { // Explicit interface implementations and overrides can only have class or struct constraints var known = KnownTypeConstraint.None; if (typeParameter.HasReferenceTypeConstraint) { known |= KnownTypeConstraint.Class; } if (typeParameter.HasUnmanagedTypeConstraint && !isOverrideOrExplicitImplementation) { known |= KnownTypeConstraint.Unmanaged; } // unmanaged constraints are both structs and unmanaged so the struct constraint is redundant if (typeParameter.HasValueTypeConstraint && !typeParameter.HasUnmanagedTypeConstraint) { known |= KnownTypeConstraint.Struct; } if (typeParameter.HasNotNullConstraint && !isOverrideOrExplicitImplementation) { known |= KnownTypeConstraint.NotNull; } var constraints = ImmutableEquatableArray.Empty; if (!isOverrideOrExplicitImplementation) { constraints = typeParameter .ConstraintTypes.Select( typeConstraint => typeConstraint.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ) .ToImmutableEquatableArray(); } // new constraint has to be last if (typeParameter.HasConstructorConstraint && !isOverrideOrExplicitImplementation) { known |= KnownTypeConstraint.New; } var declaredName = typeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); return new TypeConstraint(typeParameter.Name, declaredName, known, constraints); } private static ParameterModel ParseParameter(IParameterSymbol param) { var annotation = !param.Type.IsValueType && param.NullableAnnotation == NullableAnnotation.Annotated; var paramType = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var isGeneric = ContainsTypeParameter(param.Type); return new ParameterModel(param.MetadataName, paramType, annotation, isGeneric); } private static bool ContainsTypeParameter(ITypeSymbol symbol) { if (symbol is ITypeParameterSymbol) return true; if (symbol is not INamedTypeSymbol { TypeParameters.Length: > 0 } namedType) return false; foreach (var typeArg in namedType.TypeArguments) { if (ContainsTypeParameter(typeArg)) return true; } return false; } private static MethodModel ParseMethod(IMethodSymbol methodSymbol, bool isImplicitInterface) { var returnType = methodSymbol.ReturnType.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); // For explicit interface implementations, the containing type for the explicit method signature // must be the interface being implemented (e.g. IFoo), not the interface that declares it. var explicitImpl = methodSymbol.ExplicitInterfaceImplementations.FirstOrDefault(); var containingTypeSymbol = explicitImpl?.ContainingType ?? methodSymbol.ContainingType; var containingType = containingTypeSymbol.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); // Simple method name (strip any explicit interface qualifier if present) var declaredBaseName = methodSymbol.Name; var lastDot = declaredBaseName.LastIndexOf('.'); if (lastDot >= 0) { declaredBaseName = declaredBaseName.Substring(lastDot + 1); } if (methodSymbol.TypeParameters.Length > 0) { var typeParams = string.Join( ", ", methodSymbol.TypeParameters.Select( tp => tp.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ) ); declaredBaseName += $"<{typeParams}>"; } var returnTypeInfo = methodSymbol.ReturnType.MetadataName switch { "Task" => ReturnTypeInfo.AsyncVoid, "Task`1" or "ValueTask`1" => ReturnTypeInfo.AsyncResult, _ => ReturnTypeInfo.Return, }; var parameters = methodSymbol.Parameters.Select(ParseParameter).ToImmutableEquatableArray(); var isExplicit = explicitImpl is not null; var constraints = GenerateConstraints(methodSymbol.TypeParameters, isExplicit || !isImplicitInterface); return new MethodModel( methodSymbol.Name, returnType, containingType, declaredBaseName, returnTypeInfo, parameters, constraints, isExplicit ); } } ================================================ FILE: InterfaceStubGenerator.Shared/Polyfills/IndexRange.cs ================================================ #if NETSTANDARD2_0 || NET462 namespace System { /// /// Minimal polyfill for System.Index to support the C# index syntax when targeting /// .NET Standard 2.0 or .NET Framework 4.6.2. This implementation only exposes the members /// required by this codebase and is not a full replacement for the BCL type. /// /// /// This type exists solely to allow the source to compile on older targets where /// System.Index is not available. It should not be used as a general-purpose /// substitute outside of this project. /// public readonly record struct Index { private readonly int _value; private readonly bool _fromEnd; /// /// Creates a new from the start of a sequence. /// /// The zero-based index from the start. public Index(int value) { _value = value; _fromEnd = false; } /// /// Creates a new with the specified origin. /// /// The index position value. /// /// When , the index is calculated from the end of the sequence; otherwise from the start. /// public Index(int value, bool fromEnd) { _value = value; _fromEnd = fromEnd; } /// /// Gets the index value. /// public int Value => _value; /// /// Gets a value indicating whether the index is from the end of the sequence. /// public bool IsFromEnd => _fromEnd; /// /// Gets an that points to the start of a sequence. /// public static Index Start => new(0); /// /// Gets an that points just past the end of a sequence. /// public static Index End => new(0, true); /// /// Implicitly converts an to an from the start. /// /// The zero-based index from the start. public static implicit operator Index(int value) => new(value); } /// /// Minimal polyfill for System.Range to support the C# range syntax when targeting /// .NET Standard 2.0 or .NET Framework 4.6.2. This implementation only exposes the members /// required by this codebase and is not a full replacement for the BCL type. /// /// /// This type exists solely to allow the source to compile on older targets where /// System.Range is not available. It should not be used as a general-purpose /// substitute outside of this project. /// public readonly record struct Range { /// /// Initializes a new instance of the struct. /// /// The inclusive start . /// The exclusive end . public Range(Index start, Index end) { Start = start; End = end; } /// /// Gets the inclusive start of the range. /// public Index Start { get; } /// /// Gets the exclusive end of the range. /// public Index End { get; } /// /// Creates a that starts at the specified index and ends at . /// /// The inclusive start . /// A new . public static Range StartAt(Index start) => new(start, Index.End); /// /// Creates a that starts at and ends at the specified index. /// /// The exclusive end . /// A new . public static Range EndAt(Index end) => new(Index.Start, end); /// /// Gets a that represents the entire sequence. /// public static Range All => new(Index.Start, Index.End); } } #endif ================================================ FILE: InterfaceStubGenerator.Shared/SourceWriter.cs ================================================ using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator; // From https://github.com/dotnet/runtime/blob/233826c88d2100263fb9e9535d96f75824ba0aea/src/libraries/Common/src/SourceGenerators/SourceWriter.cs#L11 internal sealed class SourceWriter { const char IndentationChar = ' '; const int CharsPerIndentation = 4; readonly StringBuilder sb = new(); int indentation; public int Indentation { get => indentation; set { if (value < 0) { Throw(); static void Throw() => throw new ArgumentOutOfRangeException(nameof(value)); } indentation = value; } } public void Append(string text) => sb.Append(text); public void WriteLine(char value) { AddIndentation(); sb.Append(value); sb.AppendLine(); } public void WriteLine(string text) { if (indentation == 0) { sb.AppendLine(text); return; } bool isFinalLine; ReadOnlySpan remainingText = text.AsSpan(); do { ReadOnlySpan nextLine = GetNextLine(ref remainingText, out isFinalLine); if (!nextLine.IsEmpty) { AddIndentation(); } AppendSpan(sb, nextLine); sb.AppendLine(); } while (!isFinalLine); } public void WriteLine() => sb.AppendLine(); public SourceText ToSourceText() { Debug.Assert(indentation == 0 && sb.Length > 0); return SourceText.From(sb.ToString(), Encoding.UTF8); } public void Reset() { sb.Clear(); indentation = 0; } private void AddIndentation() => sb.Append(IndentationChar, CharsPerIndentation * indentation); private static ReadOnlySpan GetNextLine( ref ReadOnlySpan remainingText, out bool isFinalLine ) { if (remainingText.IsEmpty) { isFinalLine = true; return default; } ReadOnlySpan next; ReadOnlySpan rest; int lineLength = remainingText.IndexOf('\n'); if (lineLength == -1) { lineLength = remainingText.Length; isFinalLine = true; rest = default; } else { rest = remainingText.Slice(lineLength + 1); isFinalLine = false; } if ((uint)lineLength > 0 && remainingText[lineLength - 1] == '\r') { lineLength--; } next = remainingText.Slice(0, lineLength); remainingText = rest; return next; } private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan span) { fixed (char* ptr = span) { builder.Append(ptr, span.Length); } } } ================================================ FILE: InterfaceStubGenerator.Shared/UniqueNameBuilder.cs ================================================ namespace Refit.Generator; // // UniqueNameBuilder. // public class UniqueNameBuilder() { private readonly HashSet _usedNames = new(StringComparer.Ordinal); private readonly UniqueNameBuilder? _parentScope; private UniqueNameBuilder(UniqueNameBuilder parentScope) : this() { _parentScope = parentScope; } /// /// Reserve a name. /// /// public void Reserve(string name) => _usedNames.Add(name); /// /// Create a new scope. /// /// Unique Name Builder. public UniqueNameBuilder NewScope() => new(this); /// /// Generate a unique name. /// /// THe name. /// public string New(string name) { var i = 0; var uniqueName = name; while (Contains(uniqueName)) { uniqueName = name + i; i++; } _usedNames.Add(uniqueName); return uniqueName; } /// /// Reserve names. /// /// The name. public void Reserve(IEnumerable names) { if (names == null) { return; } foreach (var name in names) { _usedNames.Add(name); } } private bool Contains(string name) { if (_usedNames.Contains(name)) return true; if (_parentScope != null) return _parentScope.Contains(name); return false; } } ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) ReactiveUI 2012 - 2025 All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: NuGet.config ================================================ ================================================ FILE: README.md ================================================ ![Refit](refit_logo.png) ## Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET [![Build](https://github.com/reactiveui/refit/actions/workflows/ci-build.yml/badge.svg)](https://github.com/reactiveui/refit/actions/workflows/ci-build.yml) [![codecov](https://codecov.io/github/reactiveui/refit/branch/main/graph/badge.svg?token=2guEgHsDU2)](https://codecov.io/github/reactiveui/refit) ||Refit|Refit.HttpClientFactory|Refit.Newtonsoft.Json| |-|-|-|-| |*NuGet*|[![NuGet](https://img.shields.io/nuget/v/Refit.svg)](https://www.nuget.org/packages/Refit/)|[![NuGet](https://img.shields.io/nuget/v/Refit.HttpClientFactory.svg)](https://www.nuget.org/packages/Refit.HttpClientFactory/)|[![NuGet](https://img.shields.io/nuget/v/Refit.Newtonsoft.Json.svg)](https://www.nuget.org/packages/Refit.Newtonsoft.Json/)| Refit is a library heavily inspired by Square's [Retrofit](http://square.github.io/retrofit) library, and it turns your REST API into a live interface: ```csharp public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } ``` The `RestService` class generates an implementation of `IGitHubApi` that uses `HttpClient` to make its calls: ```csharp var gitHubApi = RestService.For("https://api.github.com"); var octocat = await gitHubApi.GetUser("octocat"); ``` .NET Core supports registering via HttpClientFactory ```csharp services .AddRefitClient() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.github.com")); ``` # Table of Contents * [Where does this work?](#where-does-this-work) * [Breaking changes in 6.x](#breaking-changes-in-6x) * [API Attributes](#api-attributes) * [Querystrings](#querystrings) * [Dynamic Querystring Parameters](#dynamic-querystring-parameters) * [Collections as Querystring parameters](#collections-as-querystring-parameters) * [Unescape Querystring parameters](#unescape-querystring-parameters) * [Custom Querystring Parameter formatting](#custom-querystring-parameter-formatting) * [Body content](#body-content) * [Buffering and the Content-Length header](#buffering-and-the-content-length-header) * [JSON content](#json-content) * [XML Content](#xml-content) * [Form posts](#form-posts) * [Setting request headers](#setting-request-headers) * [Static headers](#static-headers) * [Dynamic headers](#dynamic-headers) * [Bearer Authentication](#bearer-authentication) * [Reducing header boilerplate with DelegatingHandlers (Authorization headers worked example)](#reducing-header-boilerplate-with-delegatinghandlers-authorization-headers-worked-example) * [Redefining headers](#redefining-headers) * [Removing headers](#removing-headers) * [Passing state into DelegatingHandlers](#passing-state-into-delegatinghandlers) * [Support for Polly and Polly.Context](#support-for-polly-and-pollycontext) * [Target Interface type](#target-interface-type) * [MethodInfo of the method on the Refit client interface that was invoked](#methodinfo-of-the-method-on-the-refit-client-interface-that-was-invoked) * [Multipart uploads](#multipart-uploads) * [Retrieving the response](#retrieving-the-response) * [Using generic interfaces](#using-generic-interfaces) * [Interface inheritance](#interface-inheritance) * [Headers inheritance](#headers-inheritance) * [Default Interface Methods](#default-interface-methods) * [Using HttpClientFactory](#using-httpclientfactory) * [Providing a custom HttpClient](#providing-a-custom-httpclient) * [Handling exceptions](#handling-exceptions) * [When returning Task<IApiResponse>, Task<IApiResponse<T>>, or Task<ApiResponse<T>>](#when-returning-taskiapiresponse-taskiapiresponset-or-taskapiresponset) * [When returning Task<T>](#when-returning-taskt) * [Providing a custom ExceptionFactory](#providing-a-custom-exceptionfactory) * [ApiException deconstruction with Serilog](#apiexception-deconstruction-with-serilog) ### Where does this work? Refit currently supports the following platforms and any .NET Standard 2.0 target: * UWP * Xamarin.Android * Xamarin.Mac * Xamarin.iOS * Desktop .NET 4.6.1 * .NET 6 / 8 * Blazor * Uno Platform ### SDK Requirements ### Updates in 8.0.x Fixes for some issues experienced, this lead to some breaking changes. See [Releases](https://github.com/reactiveui/refit/releases) for full details. ### V6.x.x Refit 6 requires Visual Studio 16.8 or higher, or the .NET SDK 5.0.100 or higher. It can target any .NET Standard 2.0 platform. Refit 6 does not support the old `packages.config` format for NuGet references (as they do not support analyzers/source generators). You must [migrate to PackageReference](https://devblogs.microsoft.com/nuget/migrate-packages-config-to-package-reference/) to use Refit v6 and later. #### Breaking changes in 6.x Refit 6 makes [System.Text.Json](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-overview) the default JSON serializer. If you'd like to continue to use `Newtonsoft.Json`, add the `Refit.Newtonsoft.Json` NuGet package and set your `ContentSerializer` to `NewtonsoftJsonContentSerializer` on your `RefitSettings` instance. `System.Text.Json` is faster and uses less memory, though not all features are supported. The [migration guide](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0) contains more details. `IContentSerializer` was renamed to `IHttpContentSerializer` to better reflect its purpose. Additionally, two of its methods were renamed, `SerializeAsync` -> `ToHttpContent` and `DeserializeAsync` -> `FromHttpContentAsync`. Any existing implementations of these will need to be updated, though the changes should be minor. ##### Updates in 6.3 Refit 6.3 splits out the XML serialization via `XmlContentSerializer` into a separate package, `Refit.Xml`. This is to reduce the dependency size when using Refit with Web Assembly (WASM) applications. If you require XML, add a reference to `Refit.Xml`. ### API Attributes Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation. ```csharp [Get("/users/list")] ``` You can also specify query parameters in the URL: ```csharp [Get("/users/list?sort=desc")] ``` A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }. If the name of your parameter doesn't match the name in the URL path, use the `AliasAs` attribute. ```csharp [Get("/group/{id}/users")] Task> GroupList([AliasAs("id")] int groupId); ``` A request url can also bind replacement blocks to a custom object ```csharp [Get("/group/{request.groupId}/users/{request.userId}")] Task> GroupList(UserGroupRequest request); class UserGroupRequest{ int groupId { get;set; } int userId { get;set; } } ``` Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified. The comparison between parameter name and URL parameter is *not* case-sensitive, so it will work correctly if you name your parameter `groupId` in the path `/group/{groupid}/show` for example. ```csharp [Get("/group/{groupid}/users")] Task> GroupList(int groupId, [AliasAs("sort")] string sortOrder); GroupList(4, "desc"); >>> "/group/4/users?sort=desc" ``` Round-tripping route parameter syntax: Forward slashes aren't encoded when using a double-asterisk (\*\*) catch-all parameter syntax. During link generation, the routing system encodes the value captured in a double-asterisk (\*\*) catch-all parameter (for example, {**myparametername}) except the forward slashes. The type of round-tripping route parameter must be string. ```csharp [Get("/search/{**page}")] Task> Search(string page); Search("admin/products"); >>> "/search/admin/products" ``` ### Querystrings #### Dynamic Querystring Parameters If you specify an `object` as a query parameter, all public properties which are not null are used as query parameters. This previously only applied to GET requests, but has now been expanded to all HTTP request methods, partly thanks to Twitter's hybrid API that insists on non-GET requests with querystring parameters. Use the `Query` attribute to change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object. ```csharp public class MyQueryParams { [AliasAs("order")] public string SortOrder { get; set; } public int Limit { get; set; } public KindOptions Kind { get; set; } } public enum KindOptions { Foo, [EnumMember(Value = "bar")] Bar } [Get("/group/{id}/users")] Task> GroupList([AliasAs("id")] int groupId, MyQueryParams params); [Get("/group/{id}/users")] Task> GroupListWithAttribute([AliasAs("id")] int groupId, [Query(".","search")] MyQueryParams params); params.SortOrder = "desc"; params.Limit = 10; params.Kind = KindOptions.Bar; GroupList(4, params) >>> "/group/4/users?order=desc&Limit=10&Kind=bar" GroupListWithAttribute(4, params) >>> "/group/4/users?search.order=desc&search.Limit=10&search.Kind=bar" ``` A similar behavior exists if using a Dictionary, but without the advantages of the `AliasAs` attributes and of course no intellisense and/or type safety. You can also specify querystring parameters with [Query] and have them flattened in non-GET requests, similar to: ```csharp [Post("/statuses/update.json")] Task PostTweet([Query]TweetParams params); ``` Where `TweetParams` is a POCO, and properties will also support `[AliasAs]` attributes. If you need to keep internal-only properties on your query DTO, mark them with one of the standard ignore attributes and Refit will skip them when building the query string: - `[IgnoreDataMember]` - `[System.Text.Json.Serialization.JsonIgnore]` - `[Newtonsoft.Json.JsonIgnore]` #### Collections as Querystring parameters Use the `Query` attribute to specify format in which collections should be formatted in query string ```csharp [Get("/users/list")] Task Search([Query(CollectionFormat.Multi)]int[] ages); Search(new [] {10, 20, 30}) >>> "/users/list?ages=10&ages=20&ages=30" [Get("/users/list")] Task Search([Query(CollectionFormat.Csv)]int[] ages); Search(new [] {10, 20, 30}) >>> "/users/list?ages=10%2C20%2C30" ``` You can also specify collection format in `RefitSettings`, that will be used by default, unless explicitly defined in `Query` attribute. ```csharp var gitHubApi = RestService.For("https://api.github.com", new RefitSettings { CollectionFormat = CollectionFormat.Multi }); ``` #### Unescape Querystring parameters Use the `QueryUriFormat` attribute to specify if the query parameters should be url escaped ```csharp [Get("/query")] [QueryUriFormat(UriFormat.Unescaped)] Task Query(string q); Query("Select+Id,Name+From+Account") >>> "/query?q=Select+Id,Name+From+Account" ``` #### Custom Querystring parameter formatting **Formatting Keys** To customize the format of query keys, you have two main options: 1. **Using the `AliasAs` Attribute**: You can use the `AliasAs` attribute to specify a custom key name for a property. This attribute will always take precedence over any key formatter you specify. ```csharp public class MyQueryParams { [AliasAs("order")] public string SortOrder { get; set; } public int Limit { get; set; } } [Get("/group/{id}/users")] Task> GroupList([AliasAs("id")] int groupId, [Query] MyQueryParams params); params.SortOrder = "desc"; params.Limit = 10; GroupList(1, params); ``` This will generate the following request: ``` /group/1/users?order=desc&Limit=10 ``` 2. **Using the `RefitSettings.UrlParameterKeyFormatter` Property**: By default, Refit uses the property name as the query key without any additional formatting. If you want to apply a custom format across all your query keys, you can use the `UrlParameterKeyFormatter` property. Remember that if a property has an `AliasAs` attribute, it will be used regardless of the formatter. The following example uses the built-in `CamelCaseUrlParameterKeyFormatter`: ```csharp public class MyQueryParams { public string SortOrder { get; set; } [AliasAs("queryLimit")] public int Limit { get; set; } } [Get("/group/users")] Task> GroupList([Query] MyQueryParams params); params.SortOrder = "desc"; params.Limit = 10; ``` The request will look like: ``` /group/users?sortOrder=desc&queryLimit=10 ``` **Note**: The `AliasAs` attribute always takes the top priority. If both the attribute and a custom key formatter are present, the `AliasAs` attribute's value will be used. #### Formatting URL Parameter Values with the `UrlParameterFormatter` In Refit, the `UrlParameterFormatter` property within `RefitSettings` allows you to customize how parameter values are formatted in the URL. This can be particularly useful when you need to format dates, numbers, or other types in a specific manner that aligns with your API's expectations. **Using `UrlParameterFormatter`**: Assign a custom formatter that implements the `IUrlParameterFormatter` interface to the `UrlParameterFormatter` property. ```csharp public class CustomDateUrlParameterFormatter : IUrlParameterFormatter { public string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type) { if (value is DateTime dt) { return dt.ToString("yyyyMMdd"); } return value?.ToString(); } } var settings = new RefitSettings { UrlParameterFormatter = new CustomDateUrlParameterFormatter() }; ``` In this example, a custom formatter is created for date values. Whenever a `DateTime` parameter is encountered, it formats the date as `yyyyMMdd`. **Formatting Dictionary Keys**: When dealing with dictionaries, it's important to note that keys are treated as values. If you need custom formatting for dictionary keys, you should use the `UrlParameterFormatter` as well. For instance, if you have a dictionary parameter and you want to format its keys in a specific way, you can handle that in the custom formatter: ```csharp public class CustomDictionaryKeyFormatter : IUrlParameterFormatter { public string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type) { // Handle dictionary keys if (attributeProvider is PropertyInfo prop && prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { // Custom formatting logic for dictionary keys return value?.ToString().ToUpperInvariant(); } return value?.ToString(); } } var settings = new RefitSettings { UrlParameterFormatter = new CustomDictionaryKeyFormatter() }; ``` In the above example, the dictionary keys will be converted to uppercase. ### Body content One of the parameters in your method can be used as the body, by using the Body attribute: ```csharp [Post("/users/new")] Task CreateUser([Body] User user); ``` There are four possibilities for supplying the body data, depending on the type of the parameter: * If the type is `Stream`, the content will be streamed via `StreamContent` * If the type is `string`, the string will be used directly as the content unless `[Body(BodySerializationMethod.Json)]` is set which will send it as a `StringContent` * If the parameter has the attribute `[Body(BodySerializationMethod.UrlEncoded)]`, the content will be URL-encoded (see [form posts](#form-posts) below) * For all other types, the object will be serialized using the content serializer specified in RefitSettings (JSON is the default). #### Buffering and the `Content-Length` header By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no `Content-Length` header is set _on the request_. If your API needs you to send a `Content-Length` header with the request, you can disable this streaming behavior by setting the `buffered` argument of the `[Body]` attribute to `true`: ```csharp Task CreateUser([Body(buffered: true)] User user); ``` #### JSON content JSON requests and responses are serialized/deserialized using an instance of the `IHttpContentSerializer` interface. Refit provides two implementations out of the box: `SystemTextJsonContentSerializer` (which is the default JSON serializer) and `NewtonsoftJsonContentSerializer`. The first uses `System.Text.Json` APIs and is focused on high performance and low memory usage, while the latter uses the known `Newtonsoft.Json` library and is more versatile and customizable. You can read more about the two serializers and the main differences between the two [at this link](https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to). For instance, here is how to create a new `RefitSettings` instance using the `Newtonsoft.Json`-based serializer (you'll also need to add a `PackageReference` to `Refit.Newtonsoft.Json`): ```csharp var settings = new RefitSettings(new NewtonsoftJsonContentSerializer()); ``` If you're using `Newtonsoft.Json` APIs, you can customize their behavior by setting the `Newtonsoft.Json.JsonConvert.DefaultSettings` property: ```csharp JsonConvert.DefaultSettings = () => new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = {new StringEnumConverter()} }; // Serialized as: {"day":"Saturday"} await PostSomeStuff(new { Day = DayOfWeek.Saturday }); ``` As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a `RefitSettings` that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs: ```csharp var gitHubApi = RestService.For("https://api.github.com", new RefitSettings { ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings { ContractResolver = new SnakeCasePropertyNamesContractResolver() } )}); var otherApi = RestService.For("https://api.example.com", new RefitSettings { ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() } )}); ``` Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute: ```csharp public class Foo { // Works like [AliasAs("b")] would in form posts (see below) [JsonProperty(PropertyName="b")] public string Bar { get; set; } } ``` ##### JSON source generator To apply the benefits of the new [JSON source generator](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/) for System.Text.Json added in .NET 6, you can use `SystemTextJsonContentSerializer` with a custom instance of `RefitSettings` and `JsonSerializerOptions`: ```csharp var gitHubApi = RestService.For("https://api.github.com", new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(MyJsonSerializerContext.Default.Options) }); ``` #### XML Content XML requests and responses are serialized/deserialized using _System.Xml.Serialization.XmlSerializer_. By default, Refit will use JSON content serialization, to use XML content configure the ContentSerializer to use the `XmlContentSerializer`: ```csharp var gitHubApi = RestService.For("https://www.w3.org/XML", new RefitSettings { ContentSerializer = new XmlContentSerializer() }); ``` Property serialization/deserialization can be customised using attributes found in the _System.Xml.Serialization_ namespace: ```csharp public class Foo { [XmlElement(Namespace = "https://www.w3.org/XML")] public string Bar { get; set; } } ``` The _System.Xml.Serialization.XmlSerializer_ provides many options for serializing, those options can be set by providing an `XmlContentSerializerSettings` to the `XmlContentSerializer` constructor: ```csharp var gitHubApi = RestService.For("https://www.w3.org/XML", new RefitSettings { ContentSerializer = new XmlContentSerializer( new XmlContentSerializerSettings { XmlReaderWriterSettings = new XmlReaderWriterSettings() { ReaderSettings = new XmlReaderSettings { IgnoreWhitespace = true } } } ) }); ``` #### Form posts For APIs that take form posts (i.e. serialized as `application/x-www-form-urlencoded`), initialize the Body attribute with `BodySerializationMethod.UrlEncoded`. The parameter can be an `IDictionary`: ```csharp public interface IMeasurementProtocolApi { [Post("/collect")] Task Collect([Body(BodySerializationMethod.UrlEncoded)] Dictionary data); } var data = new Dictionary { {"v", 1}, {"tid", "UA-1234-5"}, {"cid", new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")}, {"t", "event"}, }; // Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event await api.Collect(data); ``` Or you can just pass any object and all _public, readable_ properties will be serialized as form fields in the request. This approach allows you to alias property names using `[AliasAs("whatever")]` which can help if the API has cryptic field names: ```csharp public interface IMeasurementProtocolApi { [Post("/collect")] Task Collect([Body(BodySerializationMethod.UrlEncoded)] Measurement measurement); } public class Measurement { // Properties can be read-only and [AliasAs] isn't required public int v { get { return 1; } } [AliasAs("tid")] public string WebPropertyId { get; set; } [AliasAs("cid")] public Guid ClientId { get; set; } [AliasAs("t")] public string Type { get; set; } public object IgnoreMe { private get; set; } } var measurement = new Measurement { WebPropertyId = "UA-1234-5", ClientId = new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"), Type = "event" }; // Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event await api.Collect(measurement); ``` If you have a type that has `[JsonProperty(PropertyName)]` attributes setting property aliases, Refit will use those too (`[AliasAs]` will take precedence where you have both). This means that the following type will serialize as `one=value1&two=value2`: ```csharp public class SomeObject { [JsonProperty(PropertyName = "one")] public string FirstProperty { get; set; } [JsonProperty(PropertyName = "notTwo")] [AliasAs("two")] public string SecondProperty { get; set; } } ``` **NOTE:** This use of `AliasAs` applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use `[JsonProperty("full-property-name")]`. ### Setting request headers #### Static headers You can set one or more static request headers for a request applying a `Headers` attribute to the method: ```csharp [Headers("User-Agent: Awesome Octocat App")] [Get("/users/{user}")] Task GetUser(string user); ``` Static headers can also be added to _every request in the API_ by applying the `Headers` attribute to the interface: ```csharp [Headers("User-Agent: Awesome Octocat App")] public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); [Post("/users/new")] Task CreateUser([Body] User user); } ``` #### Dynamic headers If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a `Header` attribute to a parameter: ```csharp [Get("/users/{user}")] Task GetUser(string user, [Header("Authorization")] string authorization); // Will add the header "Authorization: token OAUTH-TOKEN" to the request var user = await GetUser("octocat", "token OAUTH-TOKEN"); ``` Adding an `Authorization` header is such a common use case that you can add an access token to a request by applying an `Authorize` attribute to a parameter and optionally specifying the scheme: ```csharp [Get("/users/{user}")] Task GetUser(string user, [Authorize("Bearer")] string token); // Will add the header "Authorization: Bearer OAUTH-TOKEN}" to the request var user = await GetUser("octocat", "OAUTH-TOKEN"); //note: the scheme defaults to Bearer if none provided ``` If you need to set multiple headers at runtime, you can add a `IDictionary` and apply a `HeaderCollection` attribute to the parameter and it will inject the headers into the request: [//]: # ({% raw %}) ```csharp [Get("/users/{user}")] Task GetUser(string user, [HeaderCollection] IDictionary headers); var headers = new Dictionary {{"Authorization","Bearer tokenGoesHere"}, {"X-Tenant-Id","123"}}; var user = await GetUser("octocat", headers); ``` [//]: # ({% endraw %}) #### Bearer Authentication Most APIs need some sort of Authentication. The most common is OAuth Bearer authentication. A header is added to each request of the form: `Authorization: Bearer `. Refit makes it easy to insert your logic to get the token however your app needs, so you don't have to pass a token into each method. 1. Add `[Headers("Authorization: Bearer")]` to the interface or methods which need the token. 2. Set `AuthorizationHeaderValueGetter` in the `RefitSettings` instance. Refit will call your delegate each time it needs to obtain the token, so it's a good idea for your mechanism to cache the token value for some period within the token lifetime. `AuthorizationHeaderValueGetter` works whether you create clients with `RestService.For("https://...")` or supply your own `HttpClient` via `RestService.For(httpClient, settings)`. If your API methods accept a `CancellationToken`, that token is propagated to the getter delegate. #### Reducing header boilerplate with DelegatingHandlers (Authorization headers worked example) Although we make provisions for adding dynamic headers at runtime directly in Refit, most use-cases would likely benefit from registering a custom `DelegatingHandler` in order to inject the headers as part of the `HttpClient` middleware pipeline thus removing the need to add lots of `[Header]` or `[HeaderCollection]` attributes. In the example above we are leveraging a `[HeaderCollection]` parameter to inject an `Authorization` and `X-Tenant-Id` header. This is quite a common scenario if you are integrating with a 3rd party that uses OAuth2. While it's ok for the occasional endpoint, it would be quite cumbersome if we had to add that boilerplate to every method in our interface. In this example we will assume our application is a multi-tenant application that is able to pull information about a tenant through some interface `ITenantProvider` and has a data store `IAuthTokenStore` that can be used to retrieve an auth token to attach to the outbound request. ```csharp //Custom delegating handler for adding Auth headers to outbound requests class AuthHeaderHandler : DelegatingHandler { private readonly ITenantProvider tenantProvider; private readonly IAuthTokenStore authTokenStore; public AuthHeaderHandler(ITenantProvider tenantProvider, IAuthTokenStore authTokenStore) { this.tenantProvider = tenantProvider ?? throw new ArgumentNullException(nameof(tenantProvider)); this.authTokenStore = authTokenStore ?? throw new ArgumentNullException(nameof(authTokenStore)); // InnerHandler must be left as null when using DI, but must be assigned a value when // using RestService.For // InnerHandler = new HttpClientHandler(); } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = await authTokenStore.GetToken(); //potentially refresh token here if it has expired etc. request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Headers.Add("X-Tenant-Id", tenantProvider.GetTenantId()); return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } //Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddTransient(); services.AddTransient(); services.AddTransient(); //this will add our refit api implementation with an HttpClient //that is configured to add auth headers to all requests //note: AddRefitClient requires a reference to Refit.HttpClientFactory //note: the order of delegating handlers is important and they run in the order they are added! services.AddRefitClient() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")) .AddHttpMessageHandler(); //you could add Polly here to handle HTTP 429 / HTTP 503 etc } //Your application code public class SomeImportantBusinessLogic { private ISomeThirdPartyApi thirdPartyApi; public SomeImportantBusinessLogic(ISomeThirdPartyApi thirdPartyApi) { this.thirdPartyApi = thirdPartyApi; } public async Task DoStuffWithUser(string username) { var user = await thirdPartyApi.GetUser(username); //do your thing } } ``` If you aren't using dependency injection then you could achieve the same thing by doing something like this: ```csharp var api = RestService.For(new HttpClient(new AuthHeaderHandler(tenantProvider, authTokenStore)) { BaseAddress = new Uri("https://api.example.com") } ); var user = await thirdPartyApi.GetUser(username); //do your thing ``` #### Redefining headers Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — **redefining a header will replace it**, in the following order of precedence: * `Headers` attribute on the interface _(lowest priority)_ * `Headers` attribute on the method * `Header` attribute or `HeaderCollection` attribute on a method parameter _(highest priority)_ ```csharp [Headers("X-Emoji: :rocket:")] public interface IGitHubApi { [Get("/users/list")] Task GetUsers(); [Get("/users/{user}")] [Headers("X-Emoji: :smile_cat:")] Task GetUser(string user); [Post("/users/new")] [Headers("X-Emoji: :metal:")] Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji); } // X-Emoji: :rocket: var users = await GetUsers(); // X-Emoji: :smile_cat: var user = await GetUser("octocat"); // X-Emoji: :trollface: await CreateUser(user, ":trollface:"); ``` **Note:** This redefining behavior only applies to headers _with the same name_. Headers with different names are not replaced. The following code will result in all headers being included: ```csharp [Headers("Header-A: 1")] public interface ISomeApi { [Headers("Header-B: 2")] [Post("/post")] Task PostTheThing([Header("Header-C")] int c); } // Header-A: 1 // Header-B: 2 // Header-C: 3 var user = await api.PostTheThing(3); ``` #### Removing headers Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without `: `) or passing `null` for a dynamic header. _Empty strings will be included as empty headers._ ```csharp [Headers("X-Emoji: :rocket:")] public interface IGitHubApi { [Get("/users/list")] [Headers("X-Emoji")] // Remove the X-Emoji header Task GetUsers(); [Get("/users/{user}")] [Headers("X-Emoji:")] // Redefine the X-Emoji header as empty Task GetUser(string user); [Post("/users/new")] Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji); } // No X-Emoji header var users = await GetUsers(); // X-Emoji: var user = await GetUser("octocat"); // No X-Emoji header await CreateUser(user, null); // X-Emoji: await CreateUser(user, ""); ``` ### Passing state into DelegatingHandlers If there is runtime state that you need to pass to a `DelegatingHandler` you can add a property with a dynamic value to the underlying `HttpRequestMessage.Properties` by applying a `Property` attribute to a parameter: ```csharp public interface IGitHubApi { [Post("/users/new")] Task CreateUser([Body] User user, [Property("SomeKey")] string someValue); [Post("/users/new")] Task CreateUser([Body] User user, [Property] string someOtherKey); } ``` The attribute constructor optionally takes a string which becomes the key in the `HttpRequestMessage.Properties` dictionary. If no key is explicitly defined then the name of the parameter becomes the key. If a key is defined multiple times the value in `HttpRequestMessage.Properties` will be overwritten. The parameter itself can be any `object`. Properties can be accessed inside a `DelegatingHandler` as follows: > ⚠️ **Important for `IHttpClientFactory` users:** `DelegatingHandler` instances are pooled and can live longer than a single request scope. Avoid reading per-request state from services that may be scoped/cached across handler lifetimes (for example a tenant/customer resolver stored on the handler). For per-request values like `CustomerId`, pass the value through `[Property]` so each request carries its own state. ```csharp class RequestPropertyHandler : DelegatingHandler { public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {} protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // See if the request has a the property if(request.Properties.ContainsKey("SomeKey")) { var someProperty = request.Properties["SomeKey"]; //do stuff } if(request.Properties.ContainsKey("someOtherKey")) { var someOtherProperty = request.Properties["someOtherKey"]; //do stuff } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } ``` Note: in .NET 5 `HttpRequestMessage.Properties` has been marked `Obsolete` and Refit will instead populate the value into the new `HttpRequestMessage.Options`. #### Support for Polly and Polly.Context Because Refit supports `HttpClientFactory` it is possible to configure Polly policies on your HttpClient. If your policy makes use of `Polly.Context` this can be passed via Refit by adding `[Property("PolicyExecutionContext")] Polly.Context context` as behind the scenes `Polly.Context` is simply stored in `HttpRequestMessage.Properties` under the key `PolicyExecutionContext` and is of type `Polly.Context`. It's only recommended to pass the `Polly.Context` this way if your use case requires that the `Polly.Context` be initialized with dynamic content only known at runtime. If your `Polly.Context` only requires the same content every time (e.g an `ILogger` that you want to use to log from inside your policies) a cleaner approach is to inject the `Polly.Context` via a `DelegatingHandler` as described in [#801](https://github.com/reactiveui/refit/issues/801#issuecomment-1137318526) #### Target Interface Type and method info There may be times when you want to know what the target interface type is of the Refit instance. An example is where you have a derived interface that implements a common base like this: ```csharp public interface IGetAPI { [Get("/{key}")] Task Get(long key); } public interface IUsersAPI : IGetAPI { } public interface IOrdersAPI : IGetAPI { } ``` You can access the concrete type of the interface for use in a handler, such as to alter the URL of the request: [//]: # ({% raw %}) ```csharp class RequestPropertyHandler : DelegatingHandler { public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {} protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Get the type of the target interface Type interfaceType = (Type)request.Properties[HttpMessageRequestOptions.InterfaceType]; var builder = new UriBuilder(request.RequestUri); // Alter the Path in some way based on the interface or an attribute on it builder.Path = $"/{interfaceType.Name}{builder.Path}"; // Set the new Uri on the outgoing message request.RequestUri = builder.Uri; return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } ``` [//]: # ({% endraw %}) The full method information (`RestMethodInfo`) is also always available in the request options. The `RestMethodInfo` contains more information about the method being called such as the full `MethodInfo` when using reflection is needed: [//]: # ({% raw %}) ```csharp class RequestPropertyHandler : DelegatingHandler { public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {} protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Get the method info if (request.Options.TryGetValue(new HttpRequestOptionsKey(HttpRequestMessageOptions.RestMethodInfo), out RestMethodInfo restMethodInfo)) { var builder = new UriBuilder(request.RequestUri); // Alter the Path in some way based on the method info or an attribute on it builder.Path = $"/{restMethodInfo.MethodInfo.Name}{builder.Path}"; // Set the new Uri on the outgoing message request.RequestUri = builder.Uri; } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } ``` [//]: # ({% endraw %}) Note: in .NET 5 `HttpRequestMessage.Properties` has been marked `Obsolete` and Refit will instead populate the value into the new `HttpRequestMessage.Options`. Refit provides `HttpRequestMessageOptions.InterfaceType` and `HttpRequestMessageOptions.RestMethodInfo` to respectively access the interface type and REST method info from the options. ### Multipart uploads Methods decorated with `Multipart` attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types: - `string` (parameter name will be used as name and string value as value) - byte array - `Stream` - `FileInfo` Name of the field in the multipart data priority precedence: * `multipartItem.Name` if specified and not null (optional); dynamic, allows naming form data part at execution time. * `[AliasAs]` attribute (optional) that decorate the streamPart parameter in the method signature (see below); static, defined in code. * `MultipartItem` parameter name (default) as defined in the method signature; static, defined in code. A custom boundary can be specified with an optional string parameter to the `Multipart` attribute. If left empty, this defaults to `----MyGreatBoundary`. To specify the file name and content type for byte array (`byte[]`), `Stream` and `FileInfo` parameters, use of a wrapper class is required. The wrapper classes for these types are `ByteArrayPart`, `StreamPart` and `FileInfoPart`. ```csharp public interface ISomeApi { [Multipart] [Post("/users/{id}/photo")] Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream); } ``` To pass a `Stream` to this method, construct a StreamPart object like so: ```csharp someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg")); ``` Note: The `AttachmentName` attribute that was previously described in this section has been deprecated and its use is not recommended. ### Retrieving the response Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via `Task` or via `IObservable`. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future. Similarly to how body content changes via the parameter type, the return type will determine the content returned. Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded: ```csharp [Post("/users/new")] Task CreateUser([Body] User user); // This will throw if the network call fails await CreateUser(someUser); ``` If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively. ```csharp // Returns the content as a string (i.e. the JSON data) [Get("/users/{user}")] Task GetUser(string user); // Returns the raw response, as an IObservable that can be used with the // Reactive Extensions [Get("/users/{user}")] IObservable GetUser(string user); ``` There is also a generic wrapper class called `ApiResponse` that can be used as a return type. Using this class as a return type allows you to retrieve not just the content as an object, but also any metadata associated with the request/response. This includes information such as response headers, the http status code and reason phrase (e.g. 404 Not Found), the response version, the original request message that was sent and in the case of an error, an `ApiException` object containing details of the error. Following are some examples of how you can retrieve the response metadata. ```csharp //Returns the content within a wrapper class containing metadata about the request/response [Get("/users/{user}")] Task> GetUser(string user); //Calling the API var response = await gitHubApi.GetUser("octocat"); //Getting the status code (returns a value from the System.Net.HttpStatusCode enumeration) var httpStatus = response.StatusCode; //Determining if a success status code was received and there wasn't any other error //(for example, during content deserialization) if(response.IsSuccessful) { //YAY! Do the thing... } //Retrieving a well-known header value (e.g. "Server" header) var serverHeaderValue = response.Headers.Server != null ? response.Headers.Server.ToString() : string.Empty; //Retrieving a custom header value var customHeaderValue = string.Join(',', response.Headers.GetValues("A-Custom-Header")); //Looping through all the headers foreach(var header in response.Headers) { var headerName = header.Key; var headerValue = string.Join(',', header.Value); } //Finally, retrieving the content in the response body as a strongly-typed object var user = response.Content; ``` ### Using generic interfaces When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type: ```csharp public interface IReallyExcitingCrudApi where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } ``` Which can be used like this: ```csharp // The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type) var api = RestService.For>("http://api.example.com/users"); ``` ### Interface inheritance When multiple services that need to be kept separate share a number of APIs, it is possible to leverage interface inheritance to avoid having to define the same Refit methods multiple times in different services: ```csharp public interface IBaseService { [Get("/resources")] Task GetResource(string id); } public interface IDerivedServiceA : IBaseService { [Delete("/resources")] Task DeleteResource(string id); } public interface IDerivedServiceB : IBaseService { [Post("/resources")] Task AddResource([Body] Resource resource); } ``` In this example, the `IDerivedServiceA` interface will expose both the `GetResource` and `DeleteResource` APIs, while `IDerivedServiceB` will expose `GetResource` and `AddResource`. #### Headers inheritance When using inheritance, existing header attributes will be passed along as well, and the inner-most ones will have precedence: ```csharp [Headers("User-Agent: AAA")] public interface IAmInterfaceA { [Get("/get?result=Ping")] Task Ping(); } [Headers("User-Agent: BBB")] public interface IAmInterfaceB : IAmInterfaceA { [Get("/get?result=Pang")] [Headers("User-Agent: PANG")] Task Pang(); [Get("/get?result=Foo")] Task Foo(); } ``` Here, `IAmInterfaceB.Pang()` will use `PANG` as its user agent, while `IAmInterfaceB.Foo` and `IAmInterfaceB.Ping` will use `BBB`. Note that if `IAmInterfaceB` didn't have a header attribute, `Foo` would then use the `AAA` value inherited from `IAmInterfaceA`. If an interface is inheriting more than one interface, the order of precedence is the same as the one in which the inherited interfaces are declared: ```csharp public interface IAmInterfaceC : IAmInterfaceA, IAmInterfaceB { [Get("/get?result=Foo")] Task Foo(); } ``` Here `IAmInterfaceC.Foo` would use the header attribute inherited from `IAmInterfaceA`, if present, or the one inherited from `IAmInterfaceB`, and so on for all the declared interfaces. ### Default Interface Methods Starting with C# 8.0, default interface methods (a.k.a. DIMs) can be defined on interfaces. Refit interfaces can provide additional logic using DIMs, optionally combined with private and/or static helper methods: ```csharp public interface IApiClient { // implemented by Refit but not exposed publicly [Get("/get")] internal Task GetInternal(); // Publicly available with added logic applied to the result from the API call public async Task Get() => FormatResponse(await GetInternal()); private static String FormatResponse(string response) => $"The response is: {response}"; } ``` The type generated by Refit will implement the method `IApiClient.GetInternal`. If additional logic is required immediately before or after its invocation, it shouldn't be exposed directly and can thus be hidden from consumers by being marked as `internal`. The default interface method `IApiClient.Get` will be inherited by all types implementing `IApiClient`, including - of course - the type generated by Refit. Consumers of the `IApiClient` will call the public `Get` method and profit from the additional logic provided in its implementation (optionally, in this case, with the help of the private static helper `FormatResponse`). To support runtimes without DIM-support (.NET Core 2.x and below or .NET Standard 2.0 and below), two additional types would be required for the same solution. ```csharp internal interface IApiClientInternal { [Get("/get")] Task Get(); } public interface IApiClient { public Task Get(); } internal class ApiClient : IApiClient { private readonly IApiClientInternal client; public ApiClient(IApiClientInternal client) => this.client = client; public async Task Get() => FormatResponse(await client.Get()); private static String FormatResponse(string response) => $"The response is: {response}"; } ``` ### Using HttpClientFactory Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to `Refit.HttpClientFactory` and call the provided extension method in your `ConfigureServices` method to configure your Refit interface: ```csharp services.AddRefitClient() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")); // Add additional IHttpClientBuilder chained methods as required here: // .AddHttpMessageHandler() // .SetHandlerLifetime(TimeSpan.FromMinutes(2)); ``` Optionally, a `RefitSettings` object can be included: ```csharp var settings = new RefitSettings(); // Configure refit settings here services.AddRefitClient(settings) .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")); // Add additional IHttpClientBuilder chained methods as required here: // .AddHttpMessageHandler() // .SetHandlerLifetime(TimeSpan.FromMinutes(2)); // or injected from the container services.AddRefitClient(provider => new RefitSettings() { /* configure settings */ }) .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")); // Add additional IHttpClientBuilder chained methods as required here: // .AddHttpMessageHandler() // .SetHandlerLifetime(TimeSpan.FromMinutes(2)); ``` Note that some of the properties of `RefitSettings` will be ignored because the `HttpClient` and `HttpClientHandlers` will be managed by the `HttpClientFactory` instead of Refit. You can then get the api interface using constructor injection: ```csharp public class HomeController : Controller { public HomeController(IWebApi webApi) { _webApi = webApi; } private readonly IWebApi _webApi; public async Task Index(CancellationToken cancellationToken) { var thing = await _webApi.GetSomethingWeNeed(cancellationToken); return View(thing); } } ``` ### Providing a custom HttpClient You can supply a custom `HttpClient` instance by simply passing it as a parameter to the `RestService.For` method: ```csharp RestService.For(new HttpClient() { BaseAddress = new Uri("https://www.someapi.com/api/") }); ``` However, when supplying a custom `HttpClient` instance, `HttpMessageHandlerFactory` will not be used because you already control the handler pipeline. `AuthorizationHeaderValueGetter` does still work with `RestService.For(httpClient, settings)` when the request includes an `Authorization` header placeholder (for example `[Headers("Authorization: Bearer")]`). If you still want to be able to configure the `HtttpClient` instance that `Refit` provides while still making use of the above settings, simply expose the `HttpClient` on the API interface: ```csharp interface ISomeApi { // This will automagically be populated by Refit if the property exists HttpClient Client { get; } [Headers("Authorization: Bearer")] [Get("/endpoint")] Task SomeApiEndpoint(); } ``` Then, after creating the REST service, you can set any `HttpClient` property you want, e.g. `Timeout`: ```csharp SomeApi = RestService.For("https://www.someapi.com/api/", new RefitSettings() { AuthorizationHeaderValueGetter = (rq, ct) => GetTokenAsync() }); SomeApi.Client.Timeout = timeout; ``` ### Native AoT / trimming guidance Refit's recommended **source-generator-first** setup for Native AoT and trimmed applications is: 1. Use normal Refit interfaces so the Refit source generator produces the client implementation at build time. 2. Prefer `RestService.For(...)` over reflection-heavy manual patterns around `Type` where possible. 3. Supply source-generated `System.Text.Json` metadata for your DTOs. For the default `SystemTextJsonContentSerializer` on .NET 8+, Refit prefers `JsonTypeInfo` metadata from your configured `JsonSerializerOptions` when it is available. That means Native AoT apps can improve compatibility by supplying source-generated metadata through a `JsonSerializerContext` or `TypeInfoResolver` on the serializer options they pass into `SystemTextJsonContentSerializer`. ```csharp [JsonSerializable(typeof(Todo))] public partial class TodoJsonContext : JsonSerializerContext { } var settings = new RefitSettings( new SystemTextJsonContentSerializer( new JsonSerializerOptions(JsonSerializerDefaults.Web) { TypeInfoResolver = TodoJsonContext.Default } ) ); var api = RestService.For("https://api.example.com", settings); ``` If a generated Refit client cannot be found at runtime, Refit now explicitly points you back to the source generator/build output and recommends generated clients plus source-generated `System.Text.Json` metadata for Native AoT scenarios. Refit also ships analyzers for newer Roslyn toolchains, including a Roslyn 5.0 build for newer Visual Studio versions. ### Handling exceptions Refit has different exception handling behavior depending on if your Refit interface methods return `Task` or if they return `Task`, `Task>`, or `Task>`. #### When returning `Task`, `Task>`, or `Task>` Refit traps any `ApiException` raised by the `ExceptionFactory` when processing the response, and any errors that occur when attempting to deserialize the response to `ApiResponse`, and populates the exception into the `Error` property on `ApiResponse` without throwing the exception. You can then decide what to do like so: ```csharp var response = await _myRefitClient.GetSomeStuff(); if(response.IsSuccessful) { //do your thing } else { _logger.LogError(response.Error, response.Error.Content); } ``` > [!NOTE] > The `IsSuccessful` property checks whether the response status code is in the range 200-299 and there wasn't any other error (for example, during content deserialization). If you just want to check the HTTP response status code, you can use the `IsSuccessStatusCode` property. #### When returning `Task` Refit throws any `ApiException` raised by the `ExceptionFactory` when processing the response and any errors that occur when attempting to deserialize the response to `Task`. ```csharp // ... try { var result = await awesomeApi.GetFooAsync("bar"); } catch (ApiException exception) { //exception handling } // ... ``` Refit can also throw `ValidationApiException` instead which in addition to the information present on `ApiException` also contains `ProblemDetails` when the service implements the [RFC 7807](https://tools.ietf.org/html/rfc7807) specification for problem details and the response content type is `application/problem+json` For specific information on the problem details of the validation exception, simply catch `ValidationApiException`: ```csharp // ... try { var result = await awesomeApi.GetFooAsync("bar"); } catch (ValidationApiException validationException) { // handle validation here by using validationException.Content, // which is type of ProblemDetails according to RFC 7807 // If the response contains additional properties on the problem details, // they will be added to the validationException.Content.Extensions collection. } catch (ApiException exception) { // other exception handling } // ... ``` #### Providing a custom `ExceptionFactory` You can also override default exceptions behavior that are raised by the `ExceptionFactory` when processing the result by providing a custom exception factory in `RefitSettings`. For example, you can suppress all exceptions with the following: ```csharp var nullTask = Task.FromResult(null); var gitHubApi = RestService.For("https://api.github.com", new RefitSettings { ExceptionFactory = httpResponse => nullTask; }); ``` For exceptions raised when attempting to deserialize the response use DeserializationExceptionFactory described bellow. #### Providing a custom `DeserializationExceptionFactory` You can override default deserialization exceptions behavior that are raised by the `DeserializationExceptionFactory` when processing the result by providing a custom exception factory in `RefitSettings`. For example, you can suppress all deserialization exceptions with the following: ```csharp var nullTask = Task.FromResult(null); var gitHubApi = RestService.For("https://api.github.com", new RefitSettings { DeserializationExceptionFactory = (httpResponse, exception) => nullTask; }); ``` #### `ApiException` deconstruction with Serilog For users of [Serilog](https://serilog.net), you can enrich the logging of `ApiException` using the [Serilog.Exceptions.Refit](https://www.nuget.org/packages/Serilog.Exceptions.Refit) NuGet package. Details of how to integrate this package into your applications can be found [here](https://github.com/RehanSaeed/Serilog.Exceptions#serilogexceptionsrefit). ================================================ FILE: Refit/AnonymousDisposable.cs ================================================ namespace Refit { sealed class AnonymousDisposable(Action block) : IDisposable { public void Dispose() { block(); } } } ================================================ FILE: Refit/AotCompatibility.cs ================================================ using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; #if NET7_0_OR_GREATER using System.Runtime.InteropServices; #endif namespace Refit { internal static class AotCompatibility { // Intentionally left blank to avoid changing public API surface (e.g., assembly-level attributes) // while keeping a central place for any future AOT-related initializers if needed. } } ================================================ FILE: Refit/ApiException.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http; using System.Net.Http.Headers; namespace Refit { /// /// Represents an error that occured while sending an API request. /// [Serializable] #pragma warning disable CA1032 // Implement standard exception constructors public class ApiException : Exception #pragma warning restore CA1032 // Implement standard exception constructors { /// /// HTTP response status code. /// public HttpStatusCode StatusCode { get; } /// /// The reason phrase which typically is sent by the server together with the status code. /// public string? ReasonPhrase { get; } /// /// HTTP response headers. /// public HttpResponseHeaders Headers { get; } /// /// The HTTP method used to send the request. /// public HttpMethod HttpMethod { get; } /// /// The used to send the HTTP request. /// public Uri? Uri => RequestMessage.RequestUri; /// /// The HTTP Request message used to send the request. /// public HttpRequestMessage RequestMessage { get; } /// /// HTTP response content headers as defined in RFC 2616. /// public HttpContentHeaders? ContentHeaders { get; private set; } /// /// HTTP Response content as string. /// public string? Content { get; private set; } /// /// Does the response have content? /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(Content))] #endif public bool HasContent => !string.IsNullOrWhiteSpace(Content); /// /// Refit settings used to send the request. /// public RefitSettings RefitSettings { get; } /// /// Initializes a new instance of the class. /// /// The message. /// The HTTP method. /// The content. /// The status code. /// The reason phrase. /// The headers. /// The refit settings. /// The inner exception. protected ApiException( HttpRequestMessage message, HttpMethod httpMethod, string? content, HttpStatusCode statusCode, string? reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings, Exception? innerException = null ) : this( CreateMessage(statusCode, reasonPhrase), message, httpMethod, content, statusCode, reasonPhrase, headers, refitSettings, innerException ) { } /// /// Initializes a new instance of the class. /// /// The exception message. /// The message. /// The HTTP method. /// The content. /// The status code. /// The reason phrase. /// The headers. /// The refit settings. /// The inner exception. protected ApiException( string exceptionMessage, HttpRequestMessage message, HttpMethod httpMethod, string? content, HttpStatusCode statusCode, string? reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings, Exception? innerException = null ) : base(exceptionMessage, innerException) { RequestMessage = message; HttpMethod = httpMethod; StatusCode = statusCode; ReasonPhrase = reasonPhrase; Headers = headers; RefitSettings = refitSettings; Content = content; } /// /// Get the deserialized response content as nullable . /// /// Type to deserialize the content to /// The response content deserialized as public async Task GetContentAsAsync() => HasContent ? await RefitSettings .ContentSerializer.FromHttpContentAsync(new StringContent(Content!)) .ConfigureAwait(false) : default; /// /// Create an instance of . /// /// The HTTP Request message used to send the request. /// The HTTP method used to send the request. /// The HTTP Response message. /// Refit settings used to sent the request. /// Add an inner exception to the . /// A newly created . #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods public static Task Create( HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings, Exception? innerException = null ) #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods { if (response?.IsSuccessStatusCode == true) { throw new ArgumentException("Response is successful, cannot create an ApiException.", nameof(response)); } var exceptionMessage = CreateMessage(response!.StatusCode, response.ReasonPhrase); return Create( exceptionMessage, message, httpMethod, response, refitSettings, innerException ); } /// /// Create an instance of with a custom exception message. /// /// A custom exception message. /// The HTTP Request message used to send the request. /// The HTTP method used to send the request. /// The HTTP Response message. /// Refit settings used to sent the request. /// Add an inner exception to the . /// A newly created . #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods public static async Task Create( string exceptionMessage, HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings, Exception? innerException = null ) #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods { if (response == null) { throw new ArgumentNullException(nameof(response)); } var exception = new ApiException( exceptionMessage, message, httpMethod, null, response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings, innerException ); if (response.Content == null) { return exception; } #pragma warning disable CA1031 // Do not catch general exception types try { exception.ContentHeaders = response.Content.Headers; var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); exception.Content = content; if ( response .Content.Headers?.ContentType ?.MediaType ?.Equals("application/problem+json") ?? false ) { exception = ValidationApiException.Create(exception); } response.Content.Dispose(); } catch { // NB: We're already handling an exception at this point, // so we want to make sure we don't throw another one // that hides the real error. } #pragma warning restore CA1031 // Do not catch general exception types return exception; } static string CreateMessage(HttpStatusCode statusCode, string? reasonPhrase) => $"Response status code does not indicate success: {(int)statusCode} ({reasonPhrase})."; } } ================================================ FILE: Refit/ApiResponse.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http; using System.Net.Http.Headers; namespace Refit { static class ApiResponse { internal static T Create( HttpResponseMessage resp, object? content, RefitSettings settings, ApiException? error = null ) { return (T) Activator.CreateInstance( typeof(ApiResponse), resp, content, settings, error )!; } } /// /// Implementation of that provides additional functionalities. /// /// /// /// Create an instance of with type . /// /// Original HTTP Response message. /// Response content. /// Refit settings used to send the request. /// The ApiException, if the request failed. /// public sealed class ApiResponse( HttpResponseMessage response, T? content, RefitSettings settings, ApiException? error = null ) : IApiResponse { readonly HttpResponseMessage response = response ?? throw new ArgumentNullException(nameof(response)); bool disposed; /// /// Deserialized request content as . /// public T? Content { get; } = content; /// /// Refit settings used to send the request. /// public RefitSettings Settings { get; } = settings; /// /// HTTP response headers. /// public HttpResponseHeaders Headers => response.Headers; /// /// HTTP response content headers as defined in RFC 2616. /// public HttpContentHeaders? ContentHeaders => response.Content?.Headers; /// /// Indicates whether the request was successful. /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif public bool IsSuccessStatusCode => response.IsSuccessStatusCode; /// /// Indicates whether the request was successful and there wasn't any other error (for example, during content deserialization). /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(Content))] [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif public bool IsSuccessful => IsSuccessStatusCode && Error is null; /// /// The reason phrase which typically is sent by the server together with the status code. /// public string? ReasonPhrase => response.ReasonPhrase; /// /// The HTTP Request message which led to this response. /// public HttpRequestMessage? RequestMessage => response.RequestMessage; /// /// HTTP response status code. /// public HttpStatusCode StatusCode => response.StatusCode; /// /// HTTP Message version. /// public Version Version => response.Version; /// /// The object in case of unsuccessful response. /// public ApiException? Error { get; private set; } = error; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// Ensures the request was successful by throwing an exception in case of failure /// /// The current /// public async Task> EnsureSuccessStatusCodeAsync() { if (!IsSuccessStatusCode) { await ThrowsApiExceptionAsync().ConfigureAwait(false); } return this; } /// /// Ensures the request was successful and without any other error by throwing an exception in case of failure /// /// The current /// public async Task> EnsureSuccessfulAsync() { if (!IsSuccessful) { await ThrowsApiExceptionAsync().ConfigureAwait(false); } return this; } void Dispose(bool disposing) { if (!disposing || disposed) return; disposed = true; response.Dispose(); } private async Task ThrowsApiExceptionAsync() { var exception = Error ?? await ApiException .Create( response.RequestMessage!, response.RequestMessage!.Method, response, Settings ) .ConfigureAwait(false); Dispose(); throw exception; } } /// public interface IApiResponse : IApiResponse { #if NET6_0_OR_GREATER /// /// The object in case of unsuccessful response. /// [SuppressMessage( "Naming", "CA1716:Identifiers should not match keywords", Justification = "By Design" )] new ApiException? Error { get; } /// /// HTTP response content headers as defined in RFC 2616. /// new HttpContentHeaders? ContentHeaders { get; } /// /// Indicates whether the request was successful. /// [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] new bool IsSuccessStatusCode { get; } /// /// Indicates whether the request was successful and there wasn't any other error (for example, during content deserialization). /// [MemberNotNullWhen(true, nameof(Content))] [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] new bool IsSuccessful { get; } #endif /// /// Deserialized request content as . /// T? Content { get; } } /// /// Base interface used to represent an API response. /// public interface IApiResponse : IDisposable { /// /// HTTP response headers. /// HttpResponseHeaders Headers { get; } /// /// HTTP response content headers as defined in RFC 2616. /// HttpContentHeaders? ContentHeaders { get; } /// /// Indicates whether the request was successful. /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif bool IsSuccessStatusCode { get; } /// /// Indicates whether the request was successful and there wasn't any other error (for example, during content deserialization). /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif bool IsSuccessful { get; } /// /// HTTP response status code. /// HttpStatusCode StatusCode { get; } /// /// The reason phrase which typically is sent by the server together with the status code. /// string? ReasonPhrase { get; } /// /// The HTTP Request message which led to this response. /// HttpRequestMessage? RequestMessage { get; } /// /// HTTP Message version. /// Version Version { get; } /// /// The object in case of unsuccessful response. /// [SuppressMessage( "Naming", "CA1716:Identifiers should not match keywords", Justification = "By Design" )] ApiException? Error { get; } } } ================================================ FILE: Refit/Attributes.cs ================================================ using System.Net.Http; namespace Refit { /// /// HttpMethodAttribute. /// /// /// /// Initializes a new instance of the class. /// /// The path. public abstract class HttpMethodAttribute(string path) : Attribute { /// /// Gets the method. /// /// /// The method. /// public abstract HttpMethod Method { get; } /// /// Gets or sets the path. /// /// /// The path. /// public virtual string Path { get; protected set; } = path; } /// /// Send the request with HTTP method 'GET'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] #pragma warning disable CA1813 // Avoid unsealed attributes public class GetAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => HttpMethod.Get; } /// /// Send the request with HTTP method 'POST'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] public class PostAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => HttpMethod.Post; } /// /// Send the request with HTTP method 'PUT'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] public class PutAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => HttpMethod.Put; } /// /// Send the request with HTTP method 'DELETE'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] public class DeleteAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => HttpMethod.Delete; } /// /// Send the request with HTTP method 'PATCH'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] public class PatchAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => new("PATCH"); } /// /// Send the request with HTTP method 'OPTION'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] public class OptionsAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => new("OPTIONS"); } /// /// Send the request with HTTP method 'HEAD'. /// /// /// Initializes a new instance of the class. /// /// The path. [AttributeUsage(AttributeTargets.Method)] public class HeadAttribute(string path) : HttpMethodAttribute(path) { /// /// Gets the method. /// /// /// The method. /// public override HttpMethod Method => HttpMethod.Head; } /// /// Send the request as multipart. /// /// /// Currently, multipart methods only support the following parameter types: , array, , . /// /// /// Initializes a new instance of the class. /// /// The boundary text. [AttributeUsage(AttributeTargets.Method)] public class MultipartAttribute(string boundaryText = "----MyGreatBoundary") : Attribute { /// /// Gets the boundary text. /// /// /// The boundary text. /// public string BoundaryText { get; private set; } = boundaryText; } /// /// Defines methods to serialize HTTP requests' bodies. /// public enum BodySerializationMethod { /// /// Encodes everything using the ContentSerializer in RefitSettings except for strings. Strings are set as-is /// Default, /// /// Json encodes everything, including strings /// [Obsolete("Use BodySerializationMethod.Serialized instead", false)] Json, /// /// Form-UrlEncode's the values /// UrlEncoded, /// /// Encodes everything using the ContentSerializer in RefitSettings /// Serialized } /// /// Set a parameter to be sent as the HTTP request's body. /// /// /// There are four behaviors when sending a parameter as the request body:
/// - If the type is/implements , the content will be streamed via .
/// - If the type is , it will be used directly as the content unless [Body(BodySerializationMethod.Json)] is set /// which will send it as a .
/// - If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded.
/// - For all other types, the object will be serialized using the content serializer specified in the request's . ///
[AttributeUsage(AttributeTargets.Parameter)] public class BodyAttribute : Attribute { /// /// Initializes a new instance of the class. /// public BodyAttribute() { } /// /// Initializes a new instance of the class. /// /// if set to true [buffered]. public BodyAttribute(bool buffered) => Buffered = buffered; /// /// Initializes a new instance of the class. /// /// The serialization method. /// if set to true [buffered]. public BodyAttribute(BodySerializationMethod serializationMethod, bool buffered) { SerializationMethod = serializationMethod; Buffered = buffered; } /// /// Initializes a new instance of the class. /// /// The serialization method. public BodyAttribute( BodySerializationMethod serializationMethod = BodySerializationMethod.Default ) { SerializationMethod = serializationMethod; } /// /// Gets or sets the buffered. /// /// /// The buffered. /// public bool? Buffered { get; } /// /// Gets or sets the serialization method. /// /// /// The serialization method. /// public BodySerializationMethod SerializationMethod { get; } = BodySerializationMethod.Default; } /// /// Override the key that will be sent in the query string. /// /// /// Initializes a new instance of the class. /// /// The name. [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] public class AliasAsAttribute(string name) : Attribute { /// /// Gets or sets the name. /// /// /// The name. /// public string Name { get; protected set; } = name; } /// /// Initializes a new instance of the class. /// /// The name. [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] [Obsolete( "Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, inherit from Refit.MultipartItem", false )] public class AttachmentNameAttribute(string name) : Attribute { /// /// Gets or sets the name. /// /// /// The name. /// public string Name { get; protected set; } = name; } /// /// Allows you to provide a Dictionary of headers to be added to the request. /// [AttributeUsage(AttributeTargets.Parameter)] public class HeaderCollectionAttribute : Attribute { } /// /// Add multiple headers to the request. /// /// /// Initializes a new instance of the class. /// /// The headers. [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)] public class HeadersAttribute(params string[] headers) : Attribute { /// /// Gets the headers. /// /// /// The headers. /// public string[] Headers { get; } = headers ?? []; } /// /// Add a header to the request. /// /// /// Initializes a new instance of the class. /// /// The header. [AttributeUsage(AttributeTargets.Parameter)] public class HeaderAttribute(string header) : Attribute { /// /// Gets the header. /// /// /// The header. /// public string Header { get; } = header; } /// /// Used to store the value in HttpRequestMessage.Properties for further processing in a custom DelegatingHandler. /// If a string is supplied to the constructor then it will be used as the key in the HttpRequestMessage.Properties dictionary. /// If no key is specified then the key will be defaulted to the name of the parameter. /// [AttributeUsage(AttributeTargets.Parameter)] public class PropertyAttribute : Attribute { /// /// Initializes a new instance of the class. /// public PropertyAttribute() { } /// /// Initializes a new instance of the class. /// /// The key. public PropertyAttribute(string key) { Key = key; } /// /// Specifies the key under which to store the value on the HttpRequestMessage.Properties dictionary. /// public string? Key { get; } } /// /// Add the Authorize header to the request with the value of the associated parameter. /// /// /// Default authorization scheme: Bearer /// /// /// Initializes a new instance of the class. /// /// The scheme. [AttributeUsage(AttributeTargets.Parameter)] public class AuthorizeAttribute(string scheme = "Bearer") : Attribute { /// /// Gets the scheme. /// /// /// The scheme. /// public string Scheme { get; } = scheme; } /// /// Associated value will be added to the request Uri as query-string, using a delimiter to split the values. (default: '.') /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] // Property is to allow for form url encoded data public class QueryAttribute : Attribute { CollectionFormat? collectionFormat; /// /// Initializes a new instance of the class. /// public QueryAttribute() { } /// /// Initializes a new instance of the class. /// /// The delimiter. public QueryAttribute(string delimiter) { Delimiter = delimiter; } /// /// Initializes a new instance of the class. /// /// The delimiter. /// The prefix. public QueryAttribute(string delimiter, string prefix) { Delimiter = delimiter; Prefix = prefix; } /// /// Initializes a new instance of the class. /// /// The delimiter. /// The prefix. /// The format. public QueryAttribute(string delimiter, string prefix, string format) { Delimiter = delimiter; Prefix = prefix; Format = format; } /// /// Initializes a new instance of the class. /// /// The collection format. public QueryAttribute(CollectionFormat collectionFormat) { CollectionFormat = collectionFormat; } /// /// Used to specify that the value should be treated as a string. /// Set to true if you want to call ToString() on the object before adding it to the query string. /// public bool TreatAsString { get; set; } /// /// Used to customize the name of either the query parameter pair or of the form field when form encoding. /// /// public string Delimiter { get; protected set; } = "."; /// /// Used to customize the name of the encoded value. /// /// /// Gets combined with in the format var name = $"{Prefix}{Delimiter}{originalFieldName}" /// where originalFieldName is the name of the object property or method parameter. /// /// /// /// class Form /// { /// [Query("-", "dontlog")] /// public string password { get; } /// } /// /// will result in the encoded form having a field named dontlog-password. /// public string? Prefix { get; protected set; } #pragma warning disable CA1019 // Define accessors for attribute arguments /// /// Used to customize the formatting of the encoded value. /// /// /// /// interface IServerApi /// { /// [Get("/expenses")] /// Task addExpense([Query(Format="0.00")] double expense); /// } /// /// Calling serverApi.addExpense(5) will result in a URI of {baseUri}/expenses?expense=5.00. /// public string? Format { get; set; } /// /// Specifies how the collection should be encoded. /// public CollectionFormat CollectionFormat { // Cannot make property nullable due to Attribute restrictions get => collectionFormat.GetValueOrDefault(); set => collectionFormat = value; } #pragma warning restore CA1019 // Define accessors for attribute arguments /// /// Gets a value indicating whether this instance is collection format specified. /// /// /// true if this instance is collection format specified; otherwise, false. /// public bool IsCollectionFormatSpecified => collectionFormat.HasValue; } /// /// QueryUriFormatAttribute. /// /// /// /// Initializes a new instance of the class. /// /// The URI format. [AttributeUsage(AttributeTargets.Method)] public class QueryUriFormatAttribute(UriFormat uriFormat) : Attribute { /// /// Specifies how the Query Params should be encoded. /// public UriFormat UriFormat { get; } = uriFormat; } #pragma warning restore CA1813 // Avoid unsealed attributes } ================================================ FILE: Refit/AuthenticatedHttpClientHandler.cs ================================================ using System.Net.Http; using System.Net.Http.Headers; namespace Refit { class AuthenticatedHttpClientHandler : DelegatingHandler { readonly Func> getToken; /// /// Initializes a new instance of the class. /// /// The function to get the authentication token. /// The optional inner handler. /// must not be null. /// /// Warning: This constructor sets the to an instance /// of , when is null. This is /// a behavior which is incompatible with the IHttpClientBuilder. /// public AuthenticatedHttpClientHandler( Func> getToken, HttpMessageHandler? innerHandler = null ) : base(innerHandler ?? new HttpClientHandler()) { this.getToken = getToken ?? throw new ArgumentNullException(nameof(getToken)); } /// /// Initializes a new instance of the class. /// /// The optional inner handler. /// The function to get the authentication token. /// must not be null. /// /// This function doesn't set the automatically to an /// instance of when is null, /// which is different from the old (legacy) constructor, and compliant with the behavior expected /// by the IHttpClientBuilder. /// public AuthenticatedHttpClientHandler( HttpMessageHandler? innerHandler, Func> getToken ) { this.getToken = getToken ?? throw new ArgumentNullException(nameof(getToken)); if (innerHandler != null) InnerHandler = innerHandler; } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { // See if the request has an authorize header var auth = request.Headers.Authorization; if (auth != null) { var token = await getToken(request, cancellationToken).ConfigureAwait(false); request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token); } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } } ================================================ FILE: Refit/Buffers/PooledBufferWriter.Stream.NETStandard21.cs ================================================ #if NET6_0_OR_GREATER using System; using System.IO; using System.Threading; using System.Threading.Tasks; #nullable enable namespace Refit.Buffers { internal sealed partial class PooledBufferWriter { private sealed partial class PooledMemoryStream : Stream { /// public Task CopyToInternalAsync(Stream destination, CancellationToken cancellationToken) { if (pooledBuffer is null) ThrowObjectDisposedException(); var bytesAvailable = length - position; var source = pooledBuffer.AsMemory(position, bytesAvailable); position += source.Length; return destination.WriteAsync(source, cancellationToken).AsTask(); } /// public override ValueTask ReadAsync( Memory buffer, CancellationToken cancellationToken = default ) { if (cancellationToken.IsCancellationRequested) { return new ValueTask(Task.FromCanceled(cancellationToken)); } try { var result = Read(buffer.Span); return new ValueTask(result); } catch (OperationCanceledException e) { return new ValueTask(Task.FromCanceled(e.CancellationToken)); } catch (Exception e) { return new ValueTask(Task.FromException(e)); } } /// public override int Read(Span buffer) { if (pooledBuffer is null) ThrowObjectDisposedException(); if (position >= length) return 0; var bytesAvailable = length - position; var source = pooledBuffer.AsSpan(position, bytesAvailable); var bytesCopied = Math.Min(source.Length, buffer.Length); var destination = buffer.Slice(0, bytesCopied); source.CopyTo(destination); position += bytesCopied; return bytesCopied; } } } } #endif ================================================ FILE: Refit/Buffers/PooledBufferWriter.Stream.cs ================================================ using System.Buffers; #nullable enable namespace Refit.Buffers { internal sealed partial class PooledBufferWriter { /// /// An in-memory that uses memory buffers rented from a shared pool /// private sealed partial class PooledMemoryStream : Stream { /// /// The current used length for /// private readonly int length; /// /// The buffer rented from currently in use /// private byte[]? pooledBuffer; /// /// The current position within /// private int position; /// /// Creates a new instance /// public PooledMemoryStream(PooledBufferWriter writer) { length = writer.position; pooledBuffer = writer.buffer; } /// /// Releases the resources for the current instance /// ~PooledMemoryStream() { Dispose(true); } /// public override bool CanRead => true; /// public override bool CanSeek => false; /// public override bool CanWrite => false; /// public override long Length => length; /// public override long Position { get => position; set => ThrowNotSupportedException(); } /// public override void Flush() { } /// public override Task FlushAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } return Task.CompletedTask; } /// public override Task CopyToAsync( Stream destination, int bufferSize, CancellationToken cancellationToken ) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } try { #if NET6_0_OR_GREATER return CopyToInternalAsync(destination, cancellationToken); #else CopyTo(destination, bufferSize); return Task.CompletedTask; #endif } catch (OperationCanceledException e) { return Task.FromCanceled(e.CancellationToken); } catch (Exception e) { return Task.FromException(e); } } /// public override int Read(byte[] buffer, int offset, int count) { if (offset < 0) ThrowArgumentOutOfRangeExceptionForNegativeOffset(); if (count < 0) ThrowArgumentOutOfRangeExceptionForNegativeCount(); if (offset + count > buffer.Length) ThrowArgumentOutOfRangeExceptionForEndOfStreamReached(); if (pooledBuffer is null) ThrowObjectDisposedException(); var destination = buffer.AsSpan(offset, count); var source = pooledBuffer.AsSpan(0, length).Slice(position); // If the source is contained within the destination, copy the entire span if (source.Length <= destination.Length) { source.CopyTo(destination); position += source.Length; return source.Length; } // Resize the source slice and only copy the overlapping region source.Slice(0, destination.Length).CopyTo(destination); position += destination.Length; return destination.Length; } /// public override Task ReadAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } try { var result = Read(buffer, offset, count); return Task.FromResult(result); } catch (OperationCanceledException e) { return Task.FromCanceled(e.CancellationToken); } catch (Exception e) { return Task.FromException(e); } } /// public override int ReadByte() { if (pooledBuffer is null) ThrowObjectDisposedException(); if (position >= pooledBuffer!.Length) { return -1; } return pooledBuffer[position++]; } /// public override long Seek(long offset, SeekOrigin origin) { ThrowNotSupportedException(); return default; } /// public override void SetLength(long value) { ThrowNotSupportedException(); } /// public override void Write(byte[] buffer, int offset, int count) { ThrowNotSupportedException(); } /// protected override void Dispose(bool disposing) { if (pooledBuffer == null) return; GC.SuppressFinalize(this); ArrayPool.Shared.Return(pooledBuffer); pooledBuffer = null; } } } } ================================================ FILE: Refit/Buffers/PooledBufferWriter.ThrowExceptions.cs ================================================ using System.Runtime.CompilerServices; namespace Refit.Buffers { internal sealed partial class PooledBufferWriter { /// /// Throws an when a method receives a negative "count" parameter. /// [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() { throw new ArgumentOutOfRangeException("count", "The count can't be < 0"); } /// /// Throws an when a method receives a negative "offset" parameter. /// [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeOffset() { throw new ArgumentOutOfRangeException("offset", "The offset can't be < 0"); } /// /// Throws an when advances too far. /// [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForAdvancedTooFar() { throw new ArgumentOutOfRangeException("count", "Advanced too far"); } /// /// Throws an when the end of a has been exceeded. /// [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForEndOfStreamReached() { throw new ArgumentException("The end of the stream has been exceeded"); } /// /// Throws an when a method is called on a disposed instance. /// [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException("The stream in use has alreadybeen disposed"); } /// /// Throws an when an operation in is not supported. /// [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowNotSupportedException() { throw new NotSupportedException("The stream doesn't support the requested operation"); } } } ================================================ FILE: Refit/Buffers/PooledBufferWriter.cs ================================================ using System.Buffers; using System.Runtime.CompilerServices; namespace Refit.Buffers { /// /// A that provides a fast implementation of a binary writer, leveraging for memory pooling /// sealed partial class PooledBufferWriter : IBufferWriter, IDisposable { /// /// The default size to use to create new instances /// public const int DefaultSize = 1024; /// /// The array current in use /// byte[] buffer = Array.Empty(); /// /// The current position into /// int position; /// /// Creates a new instance /// public PooledBufferWriter() { buffer = ArrayPool.Shared.Rent(DefaultSize); position = 0; } /// public void Advance(int count) { if (count < 0) ThrowArgumentOutOfRangeExceptionForNegativeCount(); if (position > buffer.Length - count) ThrowArgumentOutOfRangeExceptionForAdvancedTooFar(); position += count; } /// public Memory GetMemory(int sizeHint = 0) { EnsureFreeCapacity(sizeHint); return buffer.AsMemory(position); } /// public Span GetSpan(int sizeHint = 0) { EnsureFreeCapacity(sizeHint); return buffer.AsSpan(position); } /// /// Ensures the buffer in use has the free capacity to contain the specified amount of new data /// /// The size in bytes of the new data to insert into the buffer [MethodImpl(MethodImplOptions.AggressiveInlining)] void EnsureFreeCapacity(int count) { if (count < 0) ThrowArgumentOutOfRangeExceptionForNegativeCount(); if (count == 0) count = 1; int currentLength = buffer.Length, freeCapacity = currentLength - position; if (count <= freeCapacity) return; int growBy = Math.Max(count, currentLength), newSize = checked(currentLength + growBy); var rent = ArrayPool.Shared.Rent(newSize); Array.Copy(buffer, rent, position); ArrayPool.Shared.Return(buffer); buffer = rent; } /// public void Dispose() { if (buffer.Length == 0) return; ArrayPool.Shared.Return(buffer); } /// /// Gets a readable for the current instance, by detaching the used buffer /// /// A readable with the contents of the current instance public Stream DetachStream() { var stream = new PooledMemoryStream(this); buffer = Array.Empty(); return stream; } } } ================================================ FILE: Refit/CachedRequestBuilderImplementation.cs ================================================ using System.Collections.Concurrent; using System.Net.Http; #if NET8_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace Refit { class CachedRequestBuilderImplementation : CachedRequestBuilderImplementation, IRequestBuilder { public CachedRequestBuilderImplementation(IRequestBuilder innerBuilder) : base(innerBuilder) { } } class CachedRequestBuilderImplementation : IRequestBuilder { public CachedRequestBuilderImplementation(IRequestBuilder innerBuilder) { this.innerBuilder = innerBuilder ?? throw new ArgumentNullException(nameof(innerBuilder)); } readonly IRequestBuilder innerBuilder; internal readonly ConcurrentDictionary< MethodTableKey, Func > MethodDictionary = new(); #if NET8_0_OR_GREATER [RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces and DTOs are preserved when trimming.")] #endif public Func BuildRestResultFuncForMethod( string methodName, Type[]? parameterTypes = null, Type[]? genericArgumentTypes = null ) { var cacheKey = new MethodTableKey( methodName, parameterTypes ?? Array.Empty(), genericArgumentTypes ?? Array.Empty() ); if (MethodDictionary.TryGetValue(cacheKey, out var methodFunc)) { return methodFunc; } // use GetOrAdd with cloned array method table key. This prevents the array from being modified, breaking the dictionary. var func = MethodDictionary.GetOrAdd( new MethodTableKey(methodName, parameterTypes?.ToArray() ?? Array.Empty(), genericArgumentTypes?.ToArray() ?? Array.Empty()), _ => innerBuilder.BuildRestResultFuncForMethod( methodName, parameterTypes, genericArgumentTypes ) ); return func; } } /// /// Represents a method composed of its name, generic arguments and parameters. /// internal readonly struct MethodTableKey : IEquatable { /// /// Constructs an instance of . /// /// Represents the methods name. /// Array containing the methods parameters. /// Array containing the methods generic arguments. public MethodTableKey (string methodName, Type[] parameters, Type[] genericArguments) { MethodName = methodName; Parameters = parameters; GenericArguments = genericArguments; } /// /// The methods name. /// string MethodName { get; } /// /// Array containing the methods parameters. /// Type[] Parameters { get; } /// /// Array containing the methods generic arguments. /// Type[] GenericArguments { get; } public override int GetHashCode() { unchecked { var hashCode = MethodName.GetHashCode(); foreach (var argument in Parameters) { hashCode = (hashCode * 397) ^ argument.GetHashCode(); } foreach (var genericArgument in GenericArguments) { hashCode = (hashCode * 397) ^ genericArgument.GetHashCode(); } return hashCode; } } public bool Equals(MethodTableKey other) { if (Parameters.Length != other.Parameters.Length || GenericArguments.Length != other.GenericArguments.Length || MethodName != other.MethodName) { return false; } for (var i = 0; i < Parameters.Length; i++) { if (Parameters[i] != other.Parameters[i]) { return false; } } for (var i = 0; i < GenericArguments.Length; i++) { if (GenericArguments[i] != other.GenericArguments[i]) { return false; } } return true; } public override bool Equals(object? obj) => obj is MethodTableKey other && Equals(other); } } ================================================ FILE: Refit/CamelCaseUrlParameterKeyFormatter.cs ================================================ namespace Refit { /// /// Provides an implementation of that formats URL parameter keys in camelCase. /// public class CamelCaseUrlParameterKeyFormatter : IUrlParameterKeyFormatter { /// /// Formats the specified key. /// /// The key. /// public string Format(string key) { if (string.IsNullOrEmpty(key) || !char.IsUpper(key[0])) { return key; } #if NETCOREAPP return string.Create( key.Length, key, (chars, name) => { name.CopyTo(chars); FixCasing(chars); } ); #else char[] chars = key.ToCharArray(); FixCasing(chars); return new string(chars); #endif } private static void FixCasing(Span chars) { for (var i = 0; i < chars.Length; i++) { if (i == 1 && !char.IsUpper(chars[i])) { break; } var hasNext = (i + 1 < chars.Length); // Stop when next char is already lowercase. if (i > 0 && hasNext && !char.IsUpper(chars[i + 1])) { break; } chars[i] = char.ToLowerInvariant(chars[i]); } } } } ================================================ FILE: Refit/CloseGenericMethodKey.cs ================================================ using System.Reflection; namespace Refit { readonly struct CloseGenericMethodKey : IEquatable { internal CloseGenericMethodKey(MethodInfo openMethodInfo, Type[] types) { OpenMethodInfo = openMethodInfo; Types = types; } public MethodInfo OpenMethodInfo { get; } public Type[] Types { get; } public bool Equals(CloseGenericMethodKey other) => OpenMethodInfo == other.OpenMethodInfo && Types.SequenceEqual(other.Types); public override bool Equals(object? obj) { if (obj is CloseGenericMethodKey closeGenericMethodKey) { return Equals(closeGenericMethodKey); } return false; } public override int GetHashCode() { unchecked { var hash = 17; hash = hash * 23 + OpenMethodInfo.GetHashCode(); foreach (var type in Types) { hash = hash * 23 + type.GetHashCode(); } return hash; } } } } ================================================ FILE: Refit/CollectionFormat.cs ================================================ namespace Refit { /// /// Collection format defined in https://swagger.io/docs/specification/2-0/describing-parameters/ /// public enum CollectionFormat { /// /// Values formatted with or /// . /// RefitParameterFormatter, /// /// Comma-separated values /// Csv, /// /// Space-separated values /// Ssv, /// /// Tab-separated values /// Tsv, /// /// Pipe-separated values /// Pipes, /// /// Multiple parameter instances /// Multi } } ================================================ FILE: Refit/DynamicallyAccessedMembersAttribute.cs ================================================ #if NETSTANDARD2_0 || NET462 namespace System.Diagnostics.CodeAnalysis; /// /// Indicates that certain members on a specified are accessed dynamically, /// for example through . /// /// /// This allows tools to understand which members are being accessed during the execution /// of a program. /// /// This attribute is valid on members whose type is or . /// /// When this attribute is applied to a location of type , the assumption is /// that the string represents a fully qualified type name. /// /// When this attribute is applied to a class, interface, or struct, the members specified /// can be accessed dynamically on instances returned from calling /// on instances of that class, interface, or struct. /// /// If the attribute is applied to a method it's treated as a special case and it implies /// the attribute should be applied to the "this" parameter of the method. As such the attribute /// should only be used on instance methods of types assignable to System.Type (or string, but no methods /// will use it there). /// internal sealed class DynamicallyAccessedMembersAttribute : Attribute { /// /// Initializes a new instance of the class /// with the specified member types. /// /// The types of members dynamically accessed. public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) { MemberTypes = memberTypes; } /// /// Gets the which specifies the type /// of members dynamically accessed. /// public DynamicallyAccessedMemberTypes MemberTypes { get; } } [Flags] internal enum DynamicallyAccessedMemberTypes { None = 0, PublicParameterlessConstructor = 1, PublicConstructors = 3, NonPublicConstructors = 4, PublicMethods = 8, NonPublicMethods = 16, // 0x00000010 PublicFields = 32, // 0x00000020 NonPublicFields = 64, // 0x00000040 PublicNestedTypes = 128, // 0x00000080 NonPublicNestedTypes = 256, // 0x00000100 PublicProperties = 512, // 0x00000200 NonPublicProperties = 1024, // 0x00000400 PublicEvents = 2048, // 0x00000800 NonPublicEvents = 4096, // 0x00001000 Interfaces = 8192, // 0x00002000 All = -1, // 0xFFFFFFFF } #endif ================================================ FILE: Refit/EnumerableExtensions.cs ================================================ namespace Refit; internal static class EnumerableExtensions { internal static EnumerablePeek TryGetSingle(this IEnumerable enumerable, out T? value) { value = default; using var enumerator = enumerable.GetEnumerator(); var hasFirst = enumerator.MoveNext(); if (!hasFirst) return EnumerablePeek.Empty; value = enumerator.Current; if (!enumerator.MoveNext()) return EnumerablePeek.Single; value = default; return EnumerablePeek.Many; } } internal static class EmptyDictionary where TKey : notnull { private static readonly Dictionary Value = []; internal static Dictionary Get() => Value; } internal enum EnumerablePeek { Empty, Single, Many } ================================================ FILE: Refit/FormValueMultimap.cs ================================================ using System.Collections; using System.Reflection; namespace Refit { /// /// Transforms a form source from a .NET representation to the appropriate HTTP form encoded representation. /// /// Performs field renaming and value formatting as specified in s and /// . A given key may appear multiple times with the /// same or different values. class FormValueMultimap : IEnumerable> { static readonly Dictionary PropertyCache = []; readonly IList> formEntries = []; readonly IHttpContentSerializer contentSerializer; public FormValueMultimap(object source, RefitSettings settings) { if (settings is null) throw new ArgumentNullException(nameof(settings)); contentSerializer = settings.ContentSerializer; if (source == null) return; if (source is IDictionary dictionary) { foreach (var key in dictionary.Keys) { var value = dictionary[key]; if (value != null) { Add( key.ToString(), settings.FormUrlEncodedParameterFormatter.Format(value, null) ); } } return; } var type = source.GetType(); lock (PropertyCache) { if (!PropertyCache.TryGetValue(type, out var properties)) { properties = GetProperties(type); PropertyCache[type] = properties; } foreach (var property in properties) { var value = property.GetValue(source, null); if (value == null) continue; var fieldName = GetFieldNameForProperty(property); // see if there's a query attribute var attrib = property.GetCustomAttribute(true); // add strings/non enumerable properties if (value is not IEnumerable enumerable || value is string) { Add( fieldName, settings.FormUrlEncodedParameterFormatter.Format(value, attrib?.Format) ); continue; } var collectionFormat = attrib != null && attrib.IsCollectionFormatSpecified ? attrib.CollectionFormat : settings.CollectionFormat; switch (collectionFormat) { case CollectionFormat.Multi: foreach (var item in enumerable) { Add( fieldName, settings.FormUrlEncodedParameterFormatter.Format( item, attrib?.Format ) ); } break; case CollectionFormat.Csv: case CollectionFormat.Ssv: case CollectionFormat.Tsv: case CollectionFormat.Pipes: var delimiter = collectionFormat switch { CollectionFormat.Csv => ",", CollectionFormat.Ssv => " ", CollectionFormat.Tsv => "\t", _ => "|" }; var formattedValues = enumerable .Cast() .Select( v => settings.FormUrlEncodedParameterFormatter.Format( v, attrib?.Format ) ); Add(fieldName, string.Join(delimiter, formattedValues)); break; default: Add( fieldName, settings.FormUrlEncodedParameterFormatter.Format( value, attrib?.Format ) ); break; } } } } /// /// Returns a key for each entry. If multiple entries share the same key, the key is returned multiple times. /// public IEnumerable Keys => this.Select(it => it.Key); void Add(string? key, string? value) { formEntries.Add(new KeyValuePair(key, value)); } string GetFieldNameForProperty(PropertyInfo propertyInfo) { var name = propertyInfo .GetCustomAttributes(true) .Select(a => a.Name) .FirstOrDefault() ?? contentSerializer.GetFieldNameForProperty(propertyInfo) ?? propertyInfo.Name; var qattrib = propertyInfo .GetCustomAttributes(true) .Select( attr => !string.IsNullOrWhiteSpace(attr.Prefix) ? $"{attr.Prefix}{attr.Delimiter}{name}" : name ) .FirstOrDefault(); return qattrib ?? name; } static PropertyInfo[] GetProperties(Type type) { return [.. type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead && p.GetMethod?.IsPublic == true)]; } public IEnumerator> GetEnumerator() { return formEntries.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } ================================================ FILE: Refit/HttpContentExtensions.cs ================================================ using System.Net.Http; namespace Refit { #if !NET6_0_OR_GREATER static class HttpContentExtensions { #pragma warning disable IDE0079 // Remove unnecessary suppression #pragma warning disable IDE0060 // Remove unused parameter public static Task ReadAsStreamAsync( this HttpContent httpContent, CancellationToken cancellationToken ) { return httpContent.ReadAsStreamAsync(); } public static Task ReadAsStringAsync( this HttpContent httpContent, CancellationToken cancellationToken ) { return httpContent.ReadAsStringAsync(); } #pragma warning restore IDE0060 // Remove unused parameter #pragma warning restore IDE0079 // Remove unnecessary suppression } #endif } ================================================ FILE: Refit/HttpRequestMessageProperties.cs ================================================ namespace Refit { /// /// Contains Refit-defined properties on the HttpRequestMessage.Properties/Options /// public static class HttpRequestMessageOptions { /// /// Returns the of the top-level interface where the method was called from /// public static string InterfaceType { get; } = "Refit.InterfaceType"; /// /// Returns the of the top-level interface /// public static string RestMethodInfo { get; } = "Refit.RestMethodInfo"; } } ================================================ FILE: Refit/JsonContentSerializer.cs ================================================ using System.Net.Http; using System.Reflection; namespace Refit { /// /// JsonContentSerializer. /// /// [Obsolete( "Use NewtonsoftJsonContentSerializer in the Refit.Newtonsoft.Json package instead", true )] public class JsonContentSerializer : IHttpContentSerializer { /// /// Converts to httpcontent. /// /// Type of the object to serialize from. /// Object to serialize. /// /// that contains the serialized object. /// /// public HttpContent ToHttpContent(T item) => throw new NotImplementedException(); /// /// Deserializes an object of type from an object. /// /// Type of the object to serialize to. /// HttpContent object to deserialize. /// CancellationToken to abort the deserialization. /// /// The deserialized object of type . /// /// public Task FromHttpContentAsync( HttpContent content, CancellationToken cancellationToken = default ) => throw new NotImplementedException(); /// /// Calculates what the field name should be for the given property. This may be affected by custom attributes the serializer understands /// /// A PropertyInfo object. /// /// The calculated field name. /// /// public string GetFieldNameForProperty(PropertyInfo propertyInfo) => throw new NotImplementedException(); } } ================================================ FILE: Refit/MultipartItem.cs ================================================ using System.Net.Http; using System.Net.Http.Headers; namespace Refit { /// /// Initializes a new instance of the class. /// /// Name of the file. /// Type of the content. /// fileName public abstract class MultipartItem(string fileName, string? contentType) { /// /// Initializes a new instance of the class. /// /// Name of the file. /// Type of the content. /// The name. public MultipartItem(string fileName, string? contentType, string? name) : this(fileName, contentType) { Name = name; } /// /// Gets the name. /// /// /// The name. /// public string? Name { get; } /// /// Gets the type of the content. /// /// /// The type of the content. /// public string? ContentType { get; } = contentType; /// /// Gets the name of the file. /// /// /// The name of the file. /// public string FileName { get; } = fileName ?? throw new ArgumentNullException(nameof(fileName)); /// /// Converts to content. /// /// public HttpContent ToContent() { var content = CreateContent(); if (!string.IsNullOrEmpty(ContentType)) { content.Headers.ContentType = new MediaTypeHeaderValue(ContentType); } return content; } /// /// Creates the content. /// /// protected abstract HttpContent CreateContent(); } /// /// Allows the use of a generic in a multipart form body. /// /// /// Initializes a new instance of the class. /// /// The value. /// Name of the file. /// Type of the content. /// The name. /// value public class StreamPart( Stream value, string fileName, string? contentType = null, string? name = null ) : MultipartItem(fileName, contentType, name) { /// /// Gets the value. /// /// /// The value. /// public Stream Value { get; } = value ?? throw new ArgumentNullException(nameof(value)); /// /// Creates the content. /// /// protected override HttpContent CreateContent() { return new StreamContent(Value); } } /// /// Allows the use of a array in a multipart form body. /// /// /// Initializes a new instance of the class. /// /// The value. /// Name of the file. /// Type of the content. /// The name. /// value public class ByteArrayPart( byte[] value, string fileName, string? contentType = null, string? name = null ) : MultipartItem(fileName, contentType, name) { /// /// Gets the value. /// /// /// The value. /// public byte[] Value { get; } = value ?? throw new ArgumentNullException(nameof(value)); /// /// Creates the content. /// /// protected override HttpContent CreateContent() { return new ByteArrayContent(Value); } } /// /// Allows the use of a object in a multipart form body. /// /// /// Initializes a new instance of the class. /// /// The value. /// Name of the file. /// Type of the content. /// The name. /// value public class FileInfoPart( FileInfo value, string fileName, string? contentType = null, string? name = null ) : MultipartItem(fileName, contentType, name) { /// /// Gets the value. /// /// /// The value. /// public FileInfo Value { get; } = value ?? throw new ArgumentNullException(nameof(value)); /// /// Creates the content. /// /// protected override HttpContent CreateContent() { return new StreamContent(Value.OpenRead()); } } } ================================================ FILE: Refit/NameValueCollection.cs ================================================ namespace System.Collections.Specialized { class NameValueCollection : Dictionary { public string[] AllKeys => [.. Keys]; } } ================================================ FILE: Refit/Polyfills.Trimming.cs ================================================ #if NETSTANDARD2_0 || NET462 namespace System.Diagnostics.CodeAnalysis { [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Constructor | System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Event, Inherited = false)] internal sealed class RequiresUnreferencedCodeAttribute : System.Attribute { public RequiresUnreferencedCodeAttribute(string message) => Message = message; public string Message { get; } public string? Url { get; set; } } [System.AttributeUsage(System.AttributeTargets.Constructor | System.AttributeTargets.Method, AllowMultiple = true)] internal sealed class DynamicDependencyAttribute : System.Attribute { public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, System.Type type) { } public DynamicDependencyAttribute(string memberSignature, System.Type type) { } } } #endif ================================================ FILE: Refit/ProblemDetails.cs ================================================ using System.Text.Json.Serialization; namespace Refit { /// /// The object representing the details about a ValidationException caught by a service implementing RFC 7807. /// public class ProblemDetails { /// /// Collection of resulting errors for the request. /// public Dictionary Errors { get; set; } = []; /// /// Collection of ProblemDetails extensions /// [JsonExtensionData] public IDictionary Extensions { get; set; } = new Dictionary(StringComparer.Ordinal); /// /// A URI reference that identifies the problem type. /// public string? Type { get; set; } = "about:blank"; /// /// A short, human-readable summary of the problem type. /// public string? Title { get; set; } /// /// The HTTP status code generated by the origin server for this occurrence of the problem. /// public int Status { get; set; } /// /// A human-readable explanation specific to this occurrence of the problem. /// public string? Detail { get; set; } /// /// A URI reference that identifies the specific occurrence of the problem. /// public string? Instance { get; set; } } } ================================================ FILE: Refit/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Refit.Tests")] // "Refit.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001009dc017250415a0d51fddb74de84257c388028f04893673ca5c8f9e7145aea2b11443cb49dd79386d2255179a79ec516466b621f77e43386e711b775f77bb0e4b217f8c208c054e5f515ae33ee76bac1b56cdc20e1c151cf026a9b7f8362f1963825e546e16b360dfc63fe670403c9d6152c24491dd5dfb9ff68fe102ef3e1aed" // )] [assembly: InternalsVisibleTo("Refit.HttpClientFactory")] // "Refit.HttpClientFactory, PublicKey=00240000048000009400000006020000002400005253413100040000010001009dc017250415a0d51fddb74de84257c388028f04893673ca5c8f9e7145aea2b11443cb49dd79386d2255179a79ec516466b621f77e43386e711b775f77bb0e4b217f8c208c054e5f515ae33ee76bac1b56cdc20e1c151cf026a9b7f8362f1963825e546e16b360dfc63fe670403c9d6152c24491dd5dfb9ff68fe102ef3e1aed" // )] [assembly: InternalsVisibleTo("Refit.Newtonsoft.Json")] // "Refit.Newtonsoft.Json, PublicKey=00240000048000009400000006020000002400005253413100040000010001009dc017250415a0d51fddb74de84257c388028f04893673ca5c8f9e7145aea2b11443cb49dd79386d2255179a79ec516466b621f77e43386e711b775f77bb0e4b217f8c208c054e5f515ae33ee76bac1b56cdc20e1c151cf026a9b7f8362f1963825e546e16b360dfc63fe670403c9d6152c24491dd5dfb9ff68fe102ef3e1aed" // )] [assembly: InternalsVisibleTo("Refit.Xml")] // "Refit.Xml, PublicKey=00240000048000009400000006020000002400005253413100040000010001009dc017250415a0d51fddb74de84257c388028f04893673ca5c8f9e7145aea2b11443cb49dd79386d2255179a79ec516466b621f77e43386e711b775f77bb0e4b217f8c208c054e5f515ae33ee76bac1b56cdc20e1c151cf026a9b7f8362f1963825e546e16b360dfc63fe670403c9d6152c24491dd5dfb9ff68fe102ef3e1aed" // )] ================================================ FILE: Refit/PushStreamContent.cs ================================================ // Copyright(c) Microsoft Open Technologies, Inc.All rights reserved. // Microsoft Open Technologies would like to thank its contributors, a list // of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. // // Licensed under the Apache License, Version 2.0 (the "License"); you // may not use this file except in compliance with the License. You may // obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. See the License for the specific language governing permissions // and limitations under the License. // NOTICE // This has been converted from: // https://github.com/ASP-NET-MVC/aspnetwebstack/blob/d5188c8a75b5b26b09ab89bedfd7ee635ae2ff17/src/System.Net.Http.Formatting/PushStreamContent.cs // to work on NET Standard 1.4 using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Net.Http.Headers; namespace System.Net.Http { /// /// Provides an implementation that exposes an output /// which can be written to directly. The ability to push data to the output stream differs from the /// where data is pulled and not pushed. /// // https://github.com/ASP-NET-MVC/aspnetwebstack/blob/d5188c8a75b5b26b09ab89bedfd7ee635ae2ff17/src/System.Net.Http.Formatting/PushStreamContent.cs [ExcludeFromCodeCoverage] class PushStreamContent : HttpContent { readonly Func onStreamAvailable; /// /// Initializes a new instance of the class. The /// action is called when an output stream /// has become available allowing the action to write to it directly. When the /// stream is closed, it will signal to the content that is has completed and the /// HTTP request or response will be completed. /// /// The action to call when an output stream is available. public PushStreamContent(Action onStreamAvailable) : this(Taskify(onStreamAvailable), (MediaTypeHeaderValue?)null) { } /// /// Initializes a new instance of the class. /// /// The action to call when an output stream is available. The stream is automatically /// closed when the return task is completed. public PushStreamContent( Func onStreamAvailable ) : this(onStreamAvailable, (MediaTypeHeaderValue?)null) { } /// /// Initializes a new instance of the class with the given media type. /// public PushStreamContent( Action onStreamAvailable, string mediaType ) : this(Taskify(onStreamAvailable), new MediaTypeHeaderValue(mediaType)) { } /// /// Initializes a new instance of the class with the given media type. /// public PushStreamContent( Func onStreamAvailable, string mediaType ) : this(onStreamAvailable, new MediaTypeHeaderValue(mediaType)) { } /// /// Initializes a new instance of the class with the given . /// public PushStreamContent( Action onStreamAvailable, MediaTypeHeaderValue? mediaType ) : this(Taskify(onStreamAvailable), mediaType) { } /// /// Initializes a new instance of the class with the given . /// public PushStreamContent( Func onStreamAvailable, MediaTypeHeaderValue? mediaType ) { this.onStreamAvailable = onStreamAvailable ?? throw new ArgumentNullException(nameof(onStreamAvailable)); Headers.ContentType = mediaType ?? new MediaTypeHeaderValue("application/octet-stream"); } static Func Taskify( Action onStreamAvailable ) { if (onStreamAvailable == null) { throw new ArgumentNullException(nameof(onStreamAvailable)); } return (Stream stream, HttpContent content, TransportContext? transportContext) => { onStreamAvailable(stream, content, transportContext); // https://github.com/ASP-NET-MVC/aspnetwebstack/blob/5118a14040b13f95bf778d1fc4522eb4ea2eef18/src/Common/TaskHelpers.cs#L10 return Task.FromResult(default); }; } /// /// Used as the T in a "conversion" of a Task into a Task{T} /// // https://github.com/ASP-NET-MVC/aspnetwebstack/blob/5118a14040b13f95bf778d1fc4522eb4ea2eef18/src/Common/TaskHelpers.cs#L65 struct AsyncVoid { } /// /// When this method is called, it calls the action provided in the constructor with the output /// stream to write to. Once the action has completed its work it closes the stream which will /// close this content instance and complete the HTTP request or response. /// /// The to which to write. /// The associated . /// A instance that is asynchronously serializing the object's content. protected override async Task SerializeToStreamAsync( Stream stream, TransportContext? context ) { var serializeToStreamTask = new TaskCompletionSource(); using Stream wrappedStream = new CompleteTaskOnCloseStream(stream, serializeToStreamTask); await onStreamAvailable(wrappedStream, this, context).ConfigureAwait(false); // wait for wrappedStream.Close/Dispose to get called. await serializeToStreamTask.Task.ConfigureAwait(false); } /// /// Computes the length of the stream if possible. /// /// The computed length of the stream. /// true if the length has been computed; otherwise false. protected override bool TryComputeLength(out long length) { // We can't know the length of the content being pushed to the output stream. length = -1; return false; } internal class CompleteTaskOnCloseStream : DelegatingStream { readonly TaskCompletionSource serializeToStreamTask; public CompleteTaskOnCloseStream( Stream innerStream, TaskCompletionSource serializeToStreamTask ) : base(innerStream) { Contract.Assert(serializeToStreamTask != null); this.serializeToStreamTask = serializeToStreamTask ?? throw new ArgumentNullException(nameof(serializeToStreamTask)); } [SuppressMessage("Usage", "CA2215:Dispose methods should call base class dispose", Justification = "We don't dispose the underlying stream because we don't own it. Dispose in this case just signifies that the user's action is finished.")] protected override void Dispose(bool disposing) { // We don't dispose the underlying stream because we don't own it. Dispose in this case just signifies // that the user's action is finished. serializeToStreamTask.TrySetResult(true); } } } /// /// Stream that delegates to inner stream. /// This is taken from System.Net.Http /// // https://github.com/ASP-NET-MVC/aspnetwebstack/blob/d5188c8a75b5b26b09ab89bedfd7ee635ae2ff17/src/System.Net.Http.Formatting/Internal/DelegatingStream.cs [ExcludeFromCodeCoverage] abstract class DelegatingStream(Stream innerStream) : Stream { protected Stream InnerStream { get; private set; } = innerStream ?? throw new ArgumentNullException(nameof(innerStream)); public override bool CanRead { get { return InnerStream.CanRead; } } public override bool CanSeek { get { return InnerStream.CanSeek; } } public override bool CanWrite { get { return InnerStream.CanWrite; } } public override long Length { get { return InnerStream.Length; } } public override long Position { get { return InnerStream.Position; } set { InnerStream.Position = value; } } public override int ReadTimeout { get { return InnerStream.ReadTimeout; } set { InnerStream.ReadTimeout = value; } } public override bool CanTimeout { get { return InnerStream.CanTimeout; } } public override int WriteTimeout { get { return InnerStream.WriteTimeout; } set { InnerStream.WriteTimeout = value; } } protected override void Dispose(bool disposing) { if (disposing) { InnerStream.Dispose(); } base.Dispose(disposing); } public override long Seek(long offset, SeekOrigin origin) { return InnerStream.Seek(offset, origin); } public override int Read(byte[] buffer, int offset, int count) { return InnerStream.Read(buffer, offset, count); } public override Task ReadAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) { return InnerStream.ReadAsync(buffer, offset, count, cancellationToken); } public override int ReadByte() { return InnerStream.ReadByte(); } public override void Flush() { InnerStream.Flush(); } public override Task CopyToAsync( Stream destination, int bufferSize, CancellationToken cancellationToken ) { return InnerStream.CopyToAsync(destination, bufferSize, cancellationToken); } public override Task FlushAsync(CancellationToken cancellationToken) { return InnerStream.FlushAsync(cancellationToken); } public override void SetLength(long value) { InnerStream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { InnerStream.Write(buffer, offset, count); } public override Task WriteAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) { return InnerStream.WriteAsync(buffer, offset, count, cancellationToken); } public override void WriteByte(byte value) { InnerStream.WriteByte(value); } } } ================================================ FILE: Refit/Refit.csproj ================================================  Refit ($(TargetFramework)) $(RefitTargets) true enable true true link true true false true ================================================ FILE: Refit/RefitSettings.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; namespace Refit { /// /// Defines various parameters on how Refit should work. /// public class RefitSettings { /// /// Creates a new instance with the default parameters /// public RefitSettings() { ContentSerializer = new SystemTextJsonContentSerializer(); UrlParameterKeyFormatter = new DefaultUrlParameterKeyFormatter(); UrlParameterFormatter = new DefaultUrlParameterFormatter(); FormUrlEncodedParameterFormatter = new DefaultFormUrlEncodedParameterFormatter(); ExceptionFactory = new DefaultApiExceptionFactory(this).CreateAsync; } /// /// Creates a new instance with the specified parameters /// /// The instance to use /// The instance to use (defaults to ) /// The instance to use (defaults to ) public RefitSettings( IHttpContentSerializer contentSerializer, IUrlParameterFormatter? urlParameterFormatter, IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter ) : this(contentSerializer, urlParameterFormatter, formUrlEncodedParameterFormatter, null) { } /// /// Creates a new instance with the specified parameters /// /// The instance to use /// The instance to use (defaults to ) /// The instance to use (defaults to ) /// The instance to use (defaults to ) public RefitSettings( IHttpContentSerializer contentSerializer, IUrlParameterFormatter? urlParameterFormatter = null, IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter = null, IUrlParameterKeyFormatter? urlParameterKeyFormatter = null ) { ContentSerializer = contentSerializer ?? throw new ArgumentNullException( nameof(contentSerializer), "The content serializer can't be null" ); UrlParameterFormatter = urlParameterFormatter ?? new DefaultUrlParameterFormatter(); FormUrlEncodedParameterFormatter = formUrlEncodedParameterFormatter ?? new DefaultFormUrlEncodedParameterFormatter(); UrlParameterKeyFormatter = urlParameterKeyFormatter ?? new DefaultUrlParameterKeyFormatter(); ExceptionFactory = new DefaultApiExceptionFactory(this).CreateAsync; } /// /// Supply a function to provide the Authorization header. Does not work if you supply an HttpClient instance. /// public Func< HttpRequestMessage, CancellationToken, Task >? AuthorizationHeaderValueGetter { get; set; } /// /// Supply a custom inner HttpMessageHandler. Does not work if you supply an HttpClient instance. /// public Func? HttpMessageHandlerFactory { get; set; } /// /// Supply a function to provide based on . /// If function returns null - no exception is thrown. /// public Func> ExceptionFactory { get; set; } /// /// Supply a function to provide when deserialization exception is encountered. /// If function returns null - no exception is thrown. /// public Func>? DeserializationExceptionFactory { get; set; } /// /// Defines how requests' content should be serialized. (defaults to ) /// public IHttpContentSerializer ContentSerializer { get; set; } /// /// The instance to use for formatting URL parameter keys (defaults to . /// Allows customization of key naming conventions. /// public IUrlParameterKeyFormatter UrlParameterKeyFormatter { get; set; } /// /// The instance to use (defaults to ) /// public IUrlParameterFormatter UrlParameterFormatter { get; set; } /// /// The instance to use (defaults to ) /// public IFormUrlEncodedParameterFormatter FormUrlEncodedParameterFormatter { get; set; } /// /// Sets the default collection format to use. (defaults to ) /// public CollectionFormat CollectionFormat { get; set; } = CollectionFormat.RefitParameterFormatter; /// /// Sets the default behavior when sending a request's body content. (defaults to false, request body is not streamed to the server) /// public bool Buffered { get; set; } /// /// Optional Key-Value pairs, which are displayed in the property . /// public Dictionary? HttpRequestMessageOptions { get; set; } #if NET6_0_OR_GREATER /// /// Gets or sets the version. /// /// /// The version. /// public Version Version { get; set; } = HttpVersion.Version11; /// /// Gets or sets the version policy. /// /// /// The version policy. /// public System.Net.Http.HttpVersionPolicy VersionPolicy { get; set; } = HttpVersionPolicy.RequestVersionOrLower; #endif } /// /// Provides content serialization to . /// public interface IHttpContentSerializer { /// /// Serializes an object of type to /// /// Type of the object to serialize from. /// Object to serialize. /// that contains the serialized object. HttpContent ToHttpContent(T item); /// /// Deserializes an object of type from an object. /// /// Type of the object to serialize to. /// HttpContent object to deserialize. /// CancellationToken to abort the deserialization. /// The deserialized object of type . Task FromHttpContentAsync( HttpContent content, CancellationToken cancellationToken = default ); /// /// Calculates what the field name should be for the given property. This may be affected by custom attributes the serializer understands /// /// A PropertyInfo object. /// The calculated field name. string? GetFieldNameForProperty(PropertyInfo propertyInfo); } /// /// Provides a mechanism for formatting URL parameter keys, allowing customization of key naming conventions. /// public interface IUrlParameterKeyFormatter { /// /// Formats the specified key. /// /// The key. /// string Format(string key); } /// /// Provides Url parameter formatting. /// public interface IUrlParameterFormatter { /// /// Formats the specified value. /// /// The value. /// The attribute provider. /// Container class type. /// string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type); } /// /// Provides form Url-encoded parameter formatting. /// public interface IFormUrlEncodedParameterFormatter { /// /// Formats the specified value. /// /// The value. /// The format string. /// string? Format(object? value, string? formatString); } /// /// Default Url parameter key formatter. Does not do any formatting. /// public class DefaultUrlParameterKeyFormatter : IUrlParameterKeyFormatter { /// /// Formats the specified key. /// /// The key. /// public virtual string Format(string key) => key; } /// /// Default Url parameter formater. /// public class DefaultUrlParameterFormatter : IUrlParameterFormatter { static readonly ConcurrentDictionary< Type, ConcurrentDictionary > EnumMemberCache = new(); Dictionary<(Type containerType, Type parameterType), string> SpecificFormats { get; } = new(); Dictionary GeneralFormats { get; } = new(); /// /// Add format for specified parameter type contained within container class of specified type. /// Might be suppressed by a QueryAttribute format. /// /// The format string. /// Container class type. /// Parameter type. public void AddFormat(string format) { SpecificFormats.Add((typeof(TContainer), typeof(TParameter)), format); } /// /// Add format for specified parameter type. /// Might be suppressed by a QueryAttribute format or a container specific format. /// /// The format string. /// Parameter type. public void AddFormat(string format) { GeneralFormats.Add(typeof(TParameter), format); } /// /// Formats the specified parameter value. /// /// The parameter value. /// The attribute provider. /// Container class type. /// /// attributeProvider public virtual string? Format( object? parameterValue, ICustomAttributeProvider attributeProvider, Type type ) { if (attributeProvider is null) { throw new ArgumentNullException(nameof(attributeProvider)); } if (parameterValue == null) { return null; } // See if we have a format var formatString = attributeProvider .GetCustomAttributes(typeof(QueryAttribute), true) .OfType() .FirstOrDefault() ?.Format; EnumMemberAttribute? enumMember = null; var parameterType = parameterValue.GetType(); if (parameterType.IsEnum) { var cached = EnumMemberCache.GetOrAdd( parameterType, t => new ConcurrentDictionary() ); enumMember = cached.GetOrAdd( parameterValue.ToString()!, val => parameterType .GetMember(val) .First() .GetCustomAttribute() ); } if (string.IsNullOrWhiteSpace(formatString) && SpecificFormats.TryGetValue((type, parameterType), out var specificFormat)) { formatString = specificFormat; } if (string.IsNullOrWhiteSpace(formatString) && GeneralFormats.TryGetValue(parameterType, out var generalFormat)) { formatString = generalFormat; } return string.Format( CultureInfo.InvariantCulture, string.IsNullOrWhiteSpace(formatString) ? "{0}" : $"{{0:{formatString}}}", enumMember?.Value ?? parameterValue ); } } /// /// Default form Url-encoded parameter formatter. /// public class DefaultFormUrlEncodedParameterFormatter : IFormUrlEncodedParameterFormatter { static readonly ConcurrentDictionary< Type, ConcurrentDictionary > EnumMemberCache = new(); /// /// Formats the specified parameter value. /// /// The parameter value. /// The format string. /// public virtual string? Format(object? parameterValue, string? formatString) { if (parameterValue == null) { return null; } var parameterType = parameterValue.GetType(); EnumMemberAttribute? enumMember = null; if (parameterType.GetTypeInfo().IsEnum) { var cached = EnumMemberCache.GetOrAdd( parameterType, t => new ConcurrentDictionary() ); enumMember = cached.GetOrAdd( parameterValue.ToString()!, val => parameterType .GetMember(val) .First() .GetCustomAttribute() ); } return string.Format( CultureInfo.InvariantCulture, string.IsNullOrWhiteSpace(formatString) ? "{0}" : $"{{0:{formatString}}}", enumMember?.Value ?? parameterValue ); } } /// /// Default Api exception factory. /// public class DefaultApiExceptionFactory(RefitSettings refitSettings) { static readonly Task NullTask = Task.FromResult(null); /// /// Creates the asynchronous. /// /// The response message. /// public Task CreateAsync(HttpResponseMessage responseMessage) { if (responseMessage?.IsSuccessStatusCode == false) { return CreateExceptionAsync(responseMessage, refitSettings)!; } else { return NullTask; } } static async Task CreateExceptionAsync( HttpResponseMessage responseMessage, RefitSettings refitSettings ) { var requestMessage = responseMessage.RequestMessage!; var method = requestMessage.Method; return await ApiException .Create(requestMessage, method, responseMessage, refitSettings) .ConfigureAwait(false); } } } ================================================ FILE: Refit/RequestBuilder.cs ================================================ using System.Net.Http; #if NET8_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace Refit { /// /// Defines a method for creating a delegate that executes a REST API method with the specified parameters and /// returns the result. /// /// The returned delegate can be used to invoke a REST API method dynamically, given an HTTP /// client and an array of arguments. This interface is typically used by code generation or dynamic proxy libraries /// to construct method invokers at runtime. Implementations may use reflection and may require referenced types to /// be preserved when trimming assemblies. public interface IRequestBuilder { /// /// Builds a delegate that executes the specified REST method using the provided HTTP client and arguments. /// /// The returned delegate uses reflection to invoke the specified method and may require /// referenced interfaces and data transfer objects (DTOs) to be preserved when trimming assemblies. This method /// is typically used to dynamically generate REST API client calls at runtime. /// The name of the interface method to generate the REST call delegate for. Must correspond to a method defined /// on the target interface. /// An array of parameter types for the target method, or null to infer from the method signature. The order /// must match the method's parameter list if specified. /// An array of generic argument types to use when constructing a generic method, or null if the method is not /// generic. /// A delegate that takes an HttpClient and an array of argument values, and returns the result of invoking the /// specified REST method. The return type matches the method's declared return type. #if NET8_0_OR_GREATER [RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces and DTOs are preserved when trimming.")] #endif Func BuildRestResultFuncForMethod( string methodName, Type[]? parameterTypes = null, Type[]? genericArgumentTypes = null ); } /// /// Defines a generic contract for building requests with a specified result type. /// /// The type of the result produced by the request builder. public interface IRequestBuilder : IRequestBuilder { } /// /// Provides static methods for creating request builders for Refit interface types. This class enables the /// generation of strongly-typed HTTP client implementations based on interface definitions and optional settings. /// /// Request builders generated by this class use reflection to analyze the provided interface /// type and create implementations for HTTP API calls. When using trimming or linking, ensure that referenced /// interfaces and data transfer objects are preserved, as reflection is required for correct operation. All members /// of this class are thread-safe and can be used concurrently across multiple threads. public static class RequestBuilder { static readonly RequestBuilderFactory PlatformRequestBuilderFactory = new(); /// /// Creates an HTTP request builder for the specified interface type. /// /// Use this method to obtain a request builder for a given API interface, typically for /// advanced scenarios such as custom client generation or integration with dependency injection. The returned /// builder can be used to create HTTP requests corresponding to the methods defined on the interface. /// The interface type that defines the HTTP API methods to be implemented by the request builder. Must be an /// interface. /// The Refit settings to use for configuring the request builder. May be null to use default settings. /// An instance of IRequestBuilder T that can be used to construct HTTP requests for the specified interface /// type. #if NET8_0_OR_GREATER public static IRequestBuilder ForType< [ DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T >(RefitSettings? settings) => PlatformRequestBuilderFactory.Create(settings); #else public static IRequestBuilder ForType(RefitSettings? settings) => PlatformRequestBuilderFactory.Create(settings); #endif /// /// Creates a request builder for the specified type, enabling the construction of requests targeting members of /// that type. /// /// The type for which to create the request builder. This type's methods will be available for request /// construction. /// An object that can be used to build requests for the specified type. #if NET8_0_OR_GREATER public static IRequestBuilder ForType< [ DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T >() => PlatformRequestBuilderFactory.Create(null); #else public static IRequestBuilder ForType() => PlatformRequestBuilderFactory.Create(null); #endif /// /// Creates an implementation of the specified Refit interface for making HTTP requests. /// /// The returned IRequestBuilder uses reflection to analyze the provided interface type. /// When using trimming or linking, ensure that all referenced interfaces and data transfer objects are /// preserved to avoid runtime errors. /// The interface type that defines the HTTP API contract. Must be a non-generic interface decorated with Refit /// attributes. /// Optional settings to customize the behavior of the generated request builder. If null, default settings are /// used. /// An IRequestBuilder instance that can be used to construct HTTP requests for the specified interface. #if NET8_0_OR_GREATER [RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces and DTOs are preserved when trimming.")] public static IRequestBuilder ForType( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, RefitSettings? settings ) #else public static IRequestBuilder ForType( Type refitInterfaceType, RefitSettings? settings ) #endif { return new CachedRequestBuilderImplementation( new RequestBuilderImplementation(refitInterfaceType, settings) ); } /// /// Creates an instance of an IRequestBuilder for the specified Refit interface type. /// /// The specified interface type must be decorated with Refit attributes to define the /// API endpoints. If trimming is enabled, ensure that all referenced interfaces and data transfer objects are /// preserved, as reflection is used to analyze the interface methods. /// The interface type that defines the Refit API contract. Must be a non-generic interface decorated with Refit /// attributes. /// An IRequestBuilder instance that can be used to construct HTTP requests for the specified interface type. #if NET8_0_OR_GREATER [RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces and DTOs are preserved when trimming.")] #endif public static IRequestBuilder ForType(Type refitInterfaceType) => ForType(refitInterfaceType, null); } } ================================================ FILE: Refit/RequestBuilderFactory.cs ================================================ #if NET8_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace Refit { interface IRequestBuilderFactory { #if NET8_0_OR_GREATER IRequestBuilder Create< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(RefitSettings? settings); [RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces and DTOs are preserved when trimming.")] IRequestBuilder Create( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, RefitSettings? settings ); #else IRequestBuilder Create(RefitSettings? settings); IRequestBuilder Create(Type refitInterfaceType, RefitSettings? settings); #endif } class RequestBuilderFactory : IRequestBuilderFactory { #if NET8_0_OR_GREATER public IRequestBuilder Create< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(RefitSettings? settings = null) #else public IRequestBuilder Create(RefitSettings? settings = null) #endif { return new CachedRequestBuilderImplementation( new RequestBuilderImplementation(settings) ); } #if NET8_0_OR_GREATER [RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces and DTOs are preserved when trimming.")] public IRequestBuilder Create( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, RefitSettings? settings = null ) #else public IRequestBuilder Create(Type refitInterfaceType, RefitSettings? settings = null) #endif { return new CachedRequestBuilderImplementation( new RequestBuilderImplementation(refitInterfaceType, settings) ); } } } ================================================ FILE: Refit/RequestBuilderImplementation.TaskToObservable.cs ================================================ namespace Refit { partial class RequestBuilderImplementation { sealed class TaskToObservable : IObservable { readonly Func> taskFactory; public TaskToObservable(Func> taskFactory) { this.taskFactory = taskFactory; } public IDisposable Subscribe(IObserver observer) { var cts = new CancellationTokenSource(); #pragma warning disable VSTHRD110 // Observe result of async calls taskFactory(cts.Token) .ContinueWith( t => { if (cts.IsCancellationRequested) return; ToObservableDone(t, observer); }, TaskScheduler.Default ); #pragma warning restore VSTHRD110 // Observe result of async calls return new AnonymousDisposable(cts.Cancel); } static void ToObservableDone(Task task, IObserver subject) { switch (task.Status) { case TaskStatus.RanToCompletion: #pragma warning disable VSTHRD002 // Avoid problematic synchronous waits subject.OnNext(task.Result); #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits subject.OnCompleted(); break; case TaskStatus.Faulted: subject.OnError(task.Exception!.InnerException!); break; case TaskStatus.Canceled: subject.OnError(new TaskCanceledException(task)); break; } } } } } ================================================ FILE: Refit/RequestBuilderImplementation.cs ================================================ using System.Collections; using System.Collections.Concurrent; using System.Diagnostics; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Web; #if NET5_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace Refit { #if NET5_0_OR_GREATER class RequestBuilderImplementation< [ DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] TApi> : RequestBuilderImplementation, IRequestBuilder #else class RequestBuilderImplementation : RequestBuilderImplementation, IRequestBuilder #endif { public RequestBuilderImplementation(RefitSettings? refitSettings = null) : base(typeof(TApi), refitSettings) { } } partial class RequestBuilderImplementation : IRequestBuilder { private const int StackallocThreshold = 512; static readonly QueryAttribute DefaultQueryAttribute = new (); static readonly Uri BaseUri = new ("http://api"); readonly Dictionary> interfaceHttpMethods; readonly ConcurrentDictionary< CloseGenericMethodKey, RestMethodInfoInternal > interfaceGenericHttpMethods; readonly IHttpContentSerializer serializer; readonly RefitSettings settings; public Type TargetType { get; } #if NET5_0_OR_GREATER [RequiresUnreferencedCode("RequestBuilder uses reflection on the provided Refit interface and DTO types. Ensure necessary members are preserved when trimming.")] #endif public RequestBuilderImplementation( #if NET5_0_OR_GREATER [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] #endif Type refitInterfaceType, RefitSettings? refitSettings = null ) { var targetInterfaceInheritedInterfaces = refitInterfaceType.GetInterfaces(); settings = refitSettings ?? new RefitSettings(); serializer = settings.ContentSerializer; interfaceGenericHttpMethods = new ConcurrentDictionary(); if (refitInterfaceType == null || !refitInterfaceType.GetTypeInfo().IsInterface) { throw new ArgumentException("targetInterface must be an Interface"); } TargetType = refitInterfaceType; var dict = new Dictionary>(StringComparer.Ordinal); AddInterfaceHttpMethods(refitInterfaceType, dict); foreach (var inheritedInterface in targetInterfaceInheritedInterfaces) { AddInterfaceHttpMethods(inheritedInterface, dict); } interfaceHttpMethods = dict; } static string GetLookupKeyForMethod(MethodInfo methodInfo) { var name = methodInfo.Name; var lastDot = name.LastIndexOf('.'); return lastDot >= 0 ? name.Substring(lastDot + 1) : name; } void AddInterfaceHttpMethods( #if NET5_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] #endif Type interfaceType, Dictionary> methods ) { var methodInfos = interfaceType .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(i => i.IsAbstract); foreach (var methodInfo in methodInfos) { var attrs = methodInfo.GetCustomAttributes(true); var hasHttpMethod = attrs.OfType().Any(); if (hasHttpMethod) { var key = GetLookupKeyForMethod(methodInfo); if (!methods.TryGetValue(key, out var value)) { value = []; methods.Add(key, value); } var restinfo = new RestMethodInfoInternal(interfaceType, methodInfo, settings); value.Add(restinfo); } } } 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 == null) { if (httpMethods.Count > 1) { throw new ArgumentException( $"MethodName exists more than once, '{nameof(parameterTypes)}' mut be defined" ); } return CloseGenericMethodIfNeeded(httpMethods[0], genericArgumentTypes); } var isGeneric = genericArgumentTypes?.Length > 0; var possibleMethodsCollection = httpMethods.Where( method => method.MethodInfo.GetParameters().Length == parameterTypes.Length ); if (isGeneric) possibleMethodsCollection = possibleMethodsCollection.Where( method => method.MethodInfo.IsGenericMethod && method.MethodInfo.GetGenericArguments().Length == genericArgumentTypes!.Length ); else possibleMethodsCollection = possibleMethodsCollection.Where( method => !method.MethodInfo.IsGenericMethod ); var possibleMethods = possibleMethodsCollection.ToArray(); if (possibleMethods.Length == 1) return CloseGenericMethodIfNeeded(possibleMethods[0], genericArgumentTypes); foreach (var method in possibleMethods) { var match = method .MethodInfo.GetParameters() .Select(p => p.ParameterType) .SequenceEqual(parameterTypes); if (match) { return CloseGenericMethodIfNeeded(method, genericArgumentTypes); } } throw new Exception("No suitable Method found..."); } RestMethodInfoInternal CloseGenericMethodIfNeeded( RestMethodInfoInternal restMethodInfo, Type[]? genericArgumentTypes ) { if (genericArgumentTypes != null) { return interfaceGenericHttpMethods.GetOrAdd( new CloseGenericMethodKey(restMethodInfo.MethodInfo, genericArgumentTypes), _ => new RestMethodInfoInternal( restMethodInfo.Type, restMethodInfo.MethodInfo.MakeGenericMethod(genericArgumentTypes), restMethodInfo.RefitSettings ) ); } return restMethodInfo; } #if NET5_0_OR_GREATER [RequiresUnreferencedCode("Building rest result delegates uses reflection over interface methods and DTOs. Ensure required members are preserved.")] #endif public Func BuildRestResultFuncForMethod( string methodName, Type[]? parameterTypes = null, Type[]? genericArgumentTypes = null ) { if (!interfaceHttpMethods.ContainsKey(methodName)) { throw new ArgumentException( "Method must be defined and have an HTTP Method attribute" ); } var restMethod = FindMatchingRestMethodInfo( methodName, parameterTypes, genericArgumentTypes ); // Task (void) if (restMethod.ReturnType == typeof(Task)) { return BuildVoidTaskFuncForMethod(restMethod); } // Task if (restMethod.ReturnType.GetTypeInfo().IsGenericType && restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { var taskFuncMi = typeof(RequestBuilderImplementation).GetMethod( nameof(BuildTaskFuncForMethod), BindingFlags.NonPublic | BindingFlags.Instance ); var taskFunc = (MulticastDelegate?) ( taskFuncMi!.MakeGenericMethod( restMethod.ReturnResultType, restMethod.DeserializedResultType ) ).Invoke(this, [restMethod]); return (client, args) => taskFunc!.DynamicInvoke(client, args); } // IObservable if (restMethod.ReturnType.GetTypeInfo().IsGenericType && restMethod.ReturnType.GetGenericTypeDefinition() == typeof(IObservable<>)) { var rxFuncMi = typeof(RequestBuilderImplementation).GetMethod( nameof(BuildRxFuncForMethod), BindingFlags.NonPublic | BindingFlags.Instance ); var rxFunc = (MulticastDelegate?) ( rxFuncMi!.MakeGenericMethod( restMethod.ReturnResultType, restMethod.DeserializedResultType ) ).Invoke(this, [restMethod]); return (client, args) => rxFunc!.DynamicInvoke(client, args); } // Synchronous return types: build a sync wrapper that awaits internally and returns the value var syncFuncMi = typeof(RequestBuilderImplementation).GetMethod( nameof(BuildSyncFuncForMethod), BindingFlags.NonPublic | BindingFlags.Instance ); var syncFunc = (MulticastDelegate?) ( syncFuncMi!.MakeGenericMethod( restMethod.ReturnResultType, restMethod.DeserializedResultType ) ).Invoke(this, [restMethod]); return (client, args) => syncFunc!.DynamicInvoke(client, args); } private Func BuildSyncFuncForMethod(RestMethodInfoInternal restMethod) { var taskFunc = BuildTaskFuncForMethod(restMethod); return (client, paramList) => { var task = taskFunc(client, paramList); return (object?)task.GetAwaiter().GetResult(); }; } void AddMultipartItem( MultipartFormDataContent multiPartContent, string fileName, string parameterName, object itemValue ) { if (itemValue is HttpContent content) { multiPartContent.Add(content); return; } if (itemValue is MultipartItem multipartItem) { var httpContent = multipartItem.ToContent(); multiPartContent.Add( httpContent, multipartItem.Name ?? parameterName, string.IsNullOrEmpty(multipartItem.FileName) ? fileName : multipartItem.FileName ); return; } if (itemValue is Stream streamValue) { var streamContent = new StreamContent(streamValue); multiPartContent.Add(streamContent, parameterName, fileName); return; } if (itemValue is string stringValue) { multiPartContent.Add(new StringContent(stringValue), parameterName); return; } if (itemValue is FileInfo fileInfoValue) { var fileContent = new StreamContent(fileInfoValue.OpenRead()); multiPartContent.Add(fileContent, parameterName, fileInfoValue.Name); return; } if (itemValue is byte[] byteArrayValue) { var fileContent = new ByteArrayContent(byteArrayValue); multiPartContent.Add(fileContent, parameterName, fileName); return; } // Fallback to serializer Exception e; try { multiPartContent.Add( settings.ContentSerializer.ToHttpContent(itemValue), parameterName ); return; } catch (Exception ex) { // Eat this since we're about to throw as a fallback anyway e = ex; } throw new ArgumentException( $"Unexpected parameter type in a Multipart request. Parameter {fileName} is of type {itemValue.GetType().Name}, whereas allowed types are String, Stream, FileInfo, Byte array and anything that's JSON serializable", nameof(itemValue), e ); } Func> BuildCancellableTaskFuncForMethod< T, TBody >(RestMethodInfoInternal restMethod) { return async (client, ct, paramList) => { if (client.BaseAddress == null) throw new InvalidOperationException( "BaseAddress must be set on the HttpClient instance" ); var factory = BuildRequestFactoryForMethod( restMethod, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null ); var rq = factory(paramList); HttpResponseMessage? resp = null; HttpContent? content = null; var disposeResponse = true; try { // Load the data into buffer when body should be buffered. if (IsBodyBuffered(restMethod, rq)) { await rq.Content!.LoadIntoBufferAsync().ConfigureAwait(false); } resp = await client .SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct) .ConfigureAwait(false); content = resp.Content ?? new StringContent(string.Empty); Exception? e = null; disposeResponse = restMethod.ShouldDisposeResponse; if (typeof(T) != typeof(HttpResponseMessage)) { e = await settings.ExceptionFactory(resp).ConfigureAwait(false); } if (restMethod.IsApiResponse) { var body = default(TBody); try { // Only attempt to deserialize content if no error present for backward-compatibility body = e == null ? await DeserializeContentAsync(resp, content, ct) .ConfigureAwait(false) : default; } catch (Exception ex) { //if an error occured while attempting to deserialize return the wrapped ApiException if (settings.DeserializationExceptionFactory != null) e = await settings.DeserializationExceptionFactory(resp, ex).ConfigureAwait(false); else { e = await ApiException.Create( "An error occured deserializing the response.", resp.RequestMessage!, resp.RequestMessage!.Method, resp, settings, ex ).ConfigureAwait(false); } } return ApiResponse.Create( resp, body, settings, e as ApiException ); } else if (e != null) { disposeResponse = false; // caller has to dispose throw e; } else { try { return await DeserializeContentAsync(resp, content, ct) .ConfigureAwait(false); } catch (Exception ex) { if (settings.DeserializationExceptionFactory != null) { var customEx = await settings.DeserializationExceptionFactory(resp, ex).ConfigureAwait(false); if (customEx != null) throw customEx; return default; } else { throw await ApiException.Create( "An error occured deserializing the response.", resp.RequestMessage!, resp.RequestMessage!.Method, resp, settings, ex ).ConfigureAwait(false); } } } } finally { // Ensure we clean up the request // Especially important if it has open files/streams rq.Dispose(); if (disposeResponse) { resp?.Dispose(); content?.Dispose(); } } }; } async Task DeserializeContentAsync( HttpResponseMessage resp, HttpContent content, CancellationToken cancellationToken ) { T? result; if (typeof(T) == typeof(HttpResponseMessage)) { // NB: This double-casting manual-boxing hate crime is the only way to make // this work without a 'class' generic constraint. It could blow up at runtime // and would be A Bad Idea if we hadn't already vetted the return type. result = (T)(object)resp; } else if (typeof(T) == typeof(HttpContent)) { result = (T)(object)content; } else if (typeof(T) == typeof(Stream)) { var stream = (object) await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); result = (T)stream; } else if (typeof(T) == typeof(string)) { using var stream = await content .ReadAsStreamAsync(cancellationToken) .ConfigureAwait(false); using var reader = new StreamReader(stream); var str = (object)await reader.ReadToEndAsync().ConfigureAwait(false); result = (T)str; } else { result = await serializer .FromHttpContentAsync(content, cancellationToken) .ConfigureAwait(false); } return result; } List> BuildQueryMap( object? @object, string? delimiter = null, RestMethodParameterInfo? parameterInfo = null ) { if (@object is IDictionary idictionary) { return BuildQueryMap(idictionary, delimiter); } var kvps = new List>(); if (@object is null) return kvps; var props = @object .GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.CanRead && p.GetMethod?.IsPublic == true); foreach (var propertyInfo in props) { if (ShouldIgnorePropertyInQueryMap(propertyInfo)) continue; var obj = propertyInfo.GetValue(@object); if (obj == null) continue; //if we have a parameter info lets check it to make sure it isn't bound to the path if (parameterInfo is { IsObjectPropertyParameter: true }) { if (parameterInfo.ParameterProperties.Any(x => x.PropertyInfo == propertyInfo)) { continue; } } var key = propertyInfo.Name; var aliasAttribute = propertyInfo.GetCustomAttribute(); key = aliasAttribute?.Name ?? settings.UrlParameterKeyFormatter.Format(key); // Look to see if the property has a Query attribute, and if so, format it accordingly var queryAttribute = propertyInfo.GetCustomAttribute(); if (queryAttribute is { Format: not null }) { obj = settings.FormUrlEncodedParameterFormatter.Format( obj, queryAttribute.Format ); } // If obj is IEnumerable - format it accounting for Query attribute and CollectionFormat if (obj is not string && obj is IEnumerable ienu && obj is not IDictionary) { foreach ( var value in ParseEnumerableQueryParameterValue( ienu, propertyInfo, propertyInfo.PropertyType, queryAttribute ) ) { kvps.Add(new KeyValuePair(key, value)); } continue; } if (DoNotConvertToQueryMap(obj)) { kvps.Add(new KeyValuePair(key, obj)); continue; } switch (obj) { case IDictionary idict: foreach (var keyValuePair in BuildQueryMap(idict, delimiter)) { kvps.Add( new KeyValuePair( $"{key}{delimiter}{keyValuePair.Key}", keyValuePair.Value ) ); } break; default: foreach (var keyValuePair in BuildQueryMap(obj, delimiter)) { kvps.Add( new KeyValuePair( $"{key}{delimiter}{keyValuePair.Key}", keyValuePair.Value ) ); } break; } } return kvps; } List> BuildQueryMap( IDictionary dictionary, string? delimiter = null ) { var kvps = new List>(); foreach (var key in dictionary.Keys) { var obj = dictionary[key]; if (obj == null) continue; var keyType = key.GetType(); var formattedKey = settings.UrlParameterFormatter.Format(key, keyType, keyType); if (string.IsNullOrWhiteSpace(formattedKey)) // blank keys can't be put in the query string { continue; } if (DoNotConvertToQueryMap(obj)) { kvps.Add(new KeyValuePair(formattedKey!, obj)); } else { foreach (var keyValuePair in BuildQueryMap(obj, delimiter)) { kvps.Add( new KeyValuePair( $"{formattedKey}{delimiter}{keyValuePair.Key}", keyValuePair.Value ) ); } } } return kvps; } static bool ShouldIgnorePropertyInQueryMap(PropertyInfo propertyInfo) { foreach (var attributeData in propertyInfo.GetCustomAttributesData()) { var fullName = attributeData.AttributeType.FullName; if ( fullName == "System.Runtime.Serialization.IgnoreDataMemberAttribute" || fullName == "System.Text.Json.Serialization.JsonIgnoreAttribute" || fullName == "Newtonsoft.Json.JsonIgnoreAttribute" ) { return true; } } return false; } Func BuildRequestFactoryForMethod( RestMethodInfoInternal restMethod, string basePath, bool paramsContainsCancellationToken ) { return paramList => { var cancellationToken = CancellationToken.None; // make sure we strip out any cancellation tokens if (paramsContainsCancellationToken) { cancellationToken = paramList.OfType().FirstOrDefault(); paramList = paramList .Where(o => o == null || o.GetType() != typeof(CancellationToken)) .ToArray(); } var ret = new HttpRequestMessage { Method = restMethod.HttpMethod }; // set up multipart content MultipartFormDataContent? multiPartContent = null; if (restMethod.IsMultipart) { multiPartContent = new MultipartFormDataContent(restMethod.MultipartBoundary); ret.Content = multiPartContent; } List>? queryParamsToAdd = null; var headersToAdd = restMethod.Headers.Count > 0 ? new Dictionary(restMethod.Headers) : null; RestMethodParameterInfo? parameterInfo = null; for (var i = 0; i < paramList.Length; i++) { var isParameterMappedToRequest = false; var param = paramList[i]; // if part of REST resource URL, substitute it in if (restMethod.ParameterMap.TryGetValue(i, out var parameterMapValue)) { parameterInfo = parameterMapValue; if (!parameterInfo.IsObjectPropertyParameter) { // mark parameter mapped if not an object // we want objects to fall through so any parameters on this object not bound here get passed as query parameters isParameterMappedToRequest = true; } } // if marked as body, add to content if ( restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item3 == i ) { AddBodyToRequest(restMethod, param, ret); isParameterMappedToRequest = true; } // if header, add to request headers if (restMethod.HeaderParameterMap.TryGetValue(i, out var headerParameterValue)) { headersToAdd ??= []; headersToAdd[headerParameterValue] = param?.ToString(); isParameterMappedToRequest = true; } //if header collection, add to request headers if (restMethod.HeaderCollectionAt(i)) { if (param is IDictionary headerCollection) { foreach (var header in headerCollection) { headersToAdd ??= []; headersToAdd[header.Key] = header.Value; } } isParameterMappedToRequest = true; } //if authorize, add to request headers with scheme if ( restMethod.AuthorizeParameterInfo != null && restMethod.AuthorizeParameterInfo.Item2 == i ) { headersToAdd ??= []; headersToAdd["Authorization"] = $"{restMethod.AuthorizeParameterInfo.Item1} {param}"; isParameterMappedToRequest = true; } //if property, add to populate into HttpRequestMessage.Properties if (restMethod.PropertyParameterMap.ContainsKey(i)) { isParameterMappedToRequest = true; } // ignore nulls and already processed parameters if (isParameterMappedToRequest || param == null) continue; // for anything that fell through to here, if this is not a multipart method add the parameter to the query string // or if is an object bound to the path add any non-path bound properties to query string // or if it's an object with a query attribute var queryAttribute = restMethod .ParameterInfoArray[i] .GetCustomAttribute(); if ( !restMethod.IsMultipart || restMethod.ParameterMap.ContainsKey(i) && restMethod.ParameterMap[i].IsObjectPropertyParameter || queryAttribute != null ) { queryParamsToAdd ??= []; AddQueryParameters(restMethod, queryAttribute, param, queryParamsToAdd, i, parameterInfo); continue; } AddMultiPart(restMethod, i, param, multiPartContent); } AddHeadersToRequest(headersToAdd, ret); AddAuthorizationHeadersFromGetterAsync(ret, cancellationToken) .GetAwaiter() .GetResult(); AddPropertiesToRequest(restMethod, ret, paramList); #if NET6_0_OR_GREATER AddVersionToRequest(ret); #endif // NB: The URI methods in .NET are dumb. Also, we do this // UriBuilder business so that we preserve any hardcoded query // parameters as well as add the parameterized ones. var urlTarget = BuildRelativePath(basePath, restMethod, paramList); var uri = new UriBuilder(new Uri(BaseUri, urlTarget)); ParseExistingQueryString(uri, ref queryParamsToAdd); if (queryParamsToAdd is not null && queryParamsToAdd.Count != 0) { uri.Query = CreateQueryString(queryParamsToAdd);; } else { uri.Query = null; } ret.RequestUri = new Uri( uri.Uri.GetComponents(UriComponents.PathAndQuery, restMethod.QueryUriFormat), UriKind.Relative ); return ret; }; } string BuildRelativePath(string basePath, RestMethodInfoInternal restMethod, object[] paramList) { basePath = basePath == "/" ? string.Empty : basePath; var pathFragments = restMethod.FragmentPath; if (pathFragments.Count == 0) { return basePath; } if (string.IsNullOrEmpty(basePath) && pathFragments.Count == 1) { Debug.Assert(pathFragments[0].IsConstant); return pathFragments[0].Value!; } #pragma warning disable CA2000 var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); #pragma warning restore CA2000 vsb.Append(basePath); foreach (var fragment in pathFragments) { AppendPathFragmentValue(ref vsb, restMethod, paramList, fragment); } return vsb.ToString(); } void AppendPathFragmentValue(ref ValueStringBuilder vsb, RestMethodInfoInternal restMethod, object[] paramList, ParameterFragment fragment) { if (fragment.IsConstant) { vsb.Append(fragment.Value!); return; } var contains = restMethod.ParameterMap.TryGetValue(fragment.ArgumentIndex, out var parameterMapValue); if (!contains || parameterMapValue is null) throw new InvalidOperationException($"{restMethod.ParameterMap} should contain parameter."); if (fragment.IsObjectProperty) { var param = paramList[fragment.ArgumentIndex]; var property = parameterMapValue.ParameterProperties[fragment.PropertyIndex]; var propertyObject = property.PropertyInfo.GetValue(param); vsb.Append(Uri.EscapeDataString(settings.UrlParameterFormatter.Format( propertyObject, property.PropertyInfo, property.PropertyInfo.PropertyType ) ?? string.Empty)); return; } if (fragment.IsDynamicRoute) { var param = paramList[fragment.ArgumentIndex]; if (parameterMapValue.Type == ParameterType.Normal) { vsb.Append(Uri.EscapeDataString( settings.UrlParameterFormatter.Format( param, restMethod.ParameterInfoArray[fragment.ArgumentIndex], restMethod.ParameterInfoArray[fragment.ArgumentIndex].ParameterType ) ?? string.Empty )); return; } // If round tripping, split string up, format each segment and append to vsb. Debug.Assert(parameterMapValue.Type == ParameterType.RoundTripping); var paramValue = (string)param; var split = paramValue.Split('/'); var firstSection = true; foreach (var section in split) { if(!firstSection) vsb.Append('/'); vsb.Append( Uri.EscapeDataString( settings.UrlParameterFormatter.Format( section, restMethod.ParameterInfoArray[fragment.ArgumentIndex], restMethod.ParameterInfoArray[fragment.ArgumentIndex].ParameterType ) ?? string.Empty )); firstSection = false; } return; } throw new ArgumentException($"{nameof(ParameterFragment)} is in an invalid form."); } void AddBodyToRequest(RestMethodInfoInternal restMethod, object param, HttpRequestMessage ret) { if (param is HttpContent httpContentParam) { ret.Content = httpContentParam; } else if (param is Stream streamParam) { ret.Content = new StreamContent(streamParam); } // Default sends raw strings else if ( restMethod.BodyParameterInfo!.Item1 == BodySerializationMethod.Default && param is string stringParam ) { ret.Content = new StringContent(stringParam); } else { switch (restMethod.BodyParameterInfo.Item1) { case BodySerializationMethod.UrlEncoded: ret.Content = param is string str ? (HttpContent) new StringContent( Uri.EscapeDataString(str), Encoding.UTF8, "application/x-www-form-urlencoded" ) : new FormUrlEncodedContent( new FormValueMultimap(param, settings) ); break; case BodySerializationMethod.Default: #pragma warning disable CS0618 // Type or member is obsolete case BodySerializationMethod.Json: #pragma warning restore CS0618 // Type or member is obsolete case BodySerializationMethod.Serialized: var content = serializer.ToHttpContent(param); switch (restMethod.BodyParameterInfo.Item2) { case false: ret.Content = new PushStreamContent( #pragma warning disable IDE1006 // Naming Styles async (stream, _, __) => #pragma warning restore IDE1006 // Naming Styles { using (stream) { await content .CopyToAsync(stream) .ConfigureAwait(false); } }, content.Headers.ContentType ); break; case true: ret.Content = content; break; } break; } } } void AddQueryParameters(RestMethodInfoInternal restMethod, QueryAttribute? queryAttribute, object param, List> queryParamsToAdd, int i, RestMethodParameterInfo? parameterInfo) { var attr = queryAttribute ?? DefaultQueryAttribute; if (attr.TreatAsString) { queryParamsToAdd.AddRange( ParseQueryParameter( param.ToString(), restMethod.ParameterInfoArray[i], restMethod.QueryParameterMap[i], attr ) ); } else if (DoNotConvertToQueryMap(param)) { queryParamsToAdd.AddRange( ParseQueryParameter( param, restMethod.ParameterInfoArray[i], restMethod.QueryParameterMap[i], attr ) ); } else { foreach (var kvp in BuildQueryMap(param, attr.Delimiter, parameterInfo)) { var path = !string.IsNullOrWhiteSpace(attr.Prefix) ? $"{attr.Prefix}{attr.Delimiter}{kvp.Key}" : kvp.Key; queryParamsToAdd.AddRange( ParseQueryParameter( kvp.Value, restMethod.ParameterInfoArray[i], path, attr ) ); } } } void AddMultiPart(RestMethodInfoInternal restMethod, int i, object param, MultipartFormDataContent? multiPartContent) { // we are in a multipart method, add the part to the content // the parameter name should be either the attachment name or the parameter name (as fallback) string itemName; string parameterName; if (!restMethod.AttachmentNameMap.TryGetValue(i, out var attachment)) { itemName = restMethod.QueryParameterMap[i]; parameterName = itemName; } else { itemName = attachment.Item1; parameterName = attachment.Item2; } // Check to see if it's an IEnumerable if (param is IEnumerable enumerable) { foreach (var item in enumerable!) { AddMultipartItem(multiPartContent!, itemName, parameterName, item); } } else { AddMultipartItem(multiPartContent!, itemName, parameterName, param); } } static void AddHeadersToRequest(Dictionary? headersToAdd, HttpRequestMessage ret) { // NB: We defer setting headers until the body has been // added so any custom content headers don't get left out. if (headersToAdd is null || headersToAdd.Count <= 0) return; // We could have content headers, so we need to make // sure we have an HttpContent object to add them to, // provided the HttpClient will allow it for the method if (ret.Content == null && !IsBodyless(ret.Method)) ret.Content = new ByteArrayContent([]); foreach (var header in headersToAdd) { SetHeader(ret, header.Key, header.Value); } } async Task AddAuthorizationHeadersFromGetterAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (settings.AuthorizationHeaderValueGetter == null) return; var auth = request.Headers.Authorization; if (auth == null || !string.IsNullOrWhiteSpace(auth.Parameter)) return; var token = await settings.AuthorizationHeaderValueGetter(request, cancellationToken) .ConfigureAwait(false); request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token); } void AddPropertiesToRequest(RestMethodInfoInternal restMethod, HttpRequestMessage ret, object[] paramList) { // Add RefitSetting.HttpRequestMessageOptions to the HttpRequestMessage if (settings.HttpRequestMessageOptions != null) { foreach (var p in settings.HttpRequestMessageOptions) { #if NET6_0_OR_GREATER ret.Options.Set(new HttpRequestOptionsKey(p.Key), p.Value); #else ret.Properties.Add(p); #endif } } for (var i = 0; i < paramList.Length; i++) { if (restMethod.PropertyParameterMap.TryGetValue(i, out var propertyKey)) { #if NET6_0_OR_GREATER ret.Options.Set( new HttpRequestOptionsKey(propertyKey), paramList[i] ); #else ret.Properties[propertyKey] = paramList[i]; #endif } } // Always add the top-level type of the interface to the properties #if NET6_0_OR_GREATER ret.Options.Set( new HttpRequestOptionsKey(HttpRequestMessageOptions.InterfaceType), TargetType ); ret.Options.Set( new HttpRequestOptionsKey( HttpRequestMessageOptions.RestMethodInfo ), restMethod.RestMethodInfo ); #else ret.Properties[HttpRequestMessageOptions.InterfaceType] = TargetType; ret.Properties[HttpRequestMessageOptions.RestMethodInfo] = restMethod.RestMethodInfo; #endif } #if NET6_0_OR_GREATER void AddVersionToRequest(HttpRequestMessage ret) { ret.Version = settings.Version; ret.VersionPolicy = settings.VersionPolicy; } #endif IEnumerable> ParseQueryParameter( object? param, ParameterInfo parameterInfo, string queryPath, QueryAttribute queryAttribute ) { if (param is not string && param is IEnumerable paramValues) { foreach ( var value in ParseEnumerableQueryParameterValue( paramValues, parameterInfo, parameterInfo.ParameterType, queryAttribute ) ) { yield return new KeyValuePair(queryPath, value); } } else { yield return new KeyValuePair( queryPath, settings.UrlParameterFormatter.Format( param, parameterInfo, parameterInfo.ParameterType ) ); } } IEnumerable ParseEnumerableQueryParameterValue( IEnumerable paramValues, ICustomAttributeProvider customAttributeProvider, Type type, QueryAttribute? queryAttribute ) { var collectionFormat = queryAttribute != null && queryAttribute.IsCollectionFormatSpecified ? queryAttribute.CollectionFormat : settings.CollectionFormat; switch (collectionFormat) { case CollectionFormat.Multi: foreach (var paramValue in paramValues) { yield return settings.UrlParameterFormatter.Format( paramValue, customAttributeProvider, type ); } break; default: var delimiter = collectionFormat switch { CollectionFormat.Ssv => " ", CollectionFormat.Tsv => "\t", CollectionFormat.Pipes => "|", _ => "," }; // Missing a "default" clause was preventing the collection from serializing at all, as it was hitting "continue" thus causing an off-by-one error var formattedValues = paramValues .Cast () .Select( v => settings.UrlParameterFormatter.Format( v, customAttributeProvider, type ) ); yield return string.Join(delimiter, formattedValues); break; } } static void ParseExistingQueryString(UriBuilder uri, ref List>? queryParamsToAdd) { if (string.IsNullOrEmpty(uri.Query)) return; queryParamsToAdd ??= []; var query = HttpUtility.ParseQueryString(uri.Query); var index = 0; foreach (var key in query.AllKeys) { if (!string.IsNullOrWhiteSpace(key)) { queryParamsToAdd.Insert( index++, new KeyValuePair(key, query[key]) ); } } } static string CreateQueryString(List> queryParamsToAdd) { // Suppress warning as ValueStringBuilder.ToString calls Dispose() #pragma warning disable CA2000 var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); #pragma warning restore CA2000 var firstQuery = true; foreach (var queryParam in queryParamsToAdd) { if(queryParam is not { Key: not null, Value: not null }) continue; if(!firstQuery) { // for all items after the first we add a & symbol vsb.Append('&'); } var key = Uri.EscapeDataString(queryParam.Key); #if NET6_0_OR_GREATER // if first query does not start with ? then prepend it if (vsb.Length == 0 && key.Length > 0 && key[0] != '?') { // query starts with ? vsb.Append('?'); } #endif vsb.Append(key); vsb.Append('='); vsb.Append(Uri.EscapeDataString(queryParam.Value ?? string.Empty)); if (firstQuery) firstQuery = false; } return vsb.ToString(); } Func> BuildRxFuncForMethod( RestMethodInfoInternal restMethod ) { var taskFunc = BuildCancellableTaskFuncForMethod(restMethod); return (client, paramList) => { return new TaskToObservable(ct => { var methodCt = CancellationToken.None; if (restMethod.CancellationToken != null) { methodCt = paramList.OfType().FirstOrDefault(); } // link the two var cts = CancellationTokenSource.CreateLinkedTokenSource(methodCt, ct); return taskFunc(client, cts.Token, paramList); }); }; } Func> BuildTaskFuncForMethod( RestMethodInfoInternal restMethod ) { var ret = BuildCancellableTaskFuncForMethod(restMethod); return (client, paramList) => { if (restMethod.CancellationToken != null) { return ret( client, paramList.OfType().FirstOrDefault(), paramList ); } return ret(client, CancellationToken.None, paramList); }; } Func BuildVoidTaskFuncForMethod( RestMethodInfoInternal restMethod ) { return async (client, paramList) => { if (client.BaseAddress == null) throw new InvalidOperationException( "BaseAddress must be set on the HttpClient instance" ); var factory = BuildRequestFactoryForMethod( restMethod, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null ); var rq = factory(paramList); var ct = CancellationToken.None; if (restMethod.CancellationToken != null) { ct = paramList.OfType().FirstOrDefault(); } // Load the data into buffer when body should be buffered. if (IsBodyBuffered(restMethod, rq)) { await rq.Content!.LoadIntoBufferAsync().ConfigureAwait(false); } using var resp = await client.SendAsync(rq, ct).ConfigureAwait(false); var exception = await settings.ExceptionFactory(resp).ConfigureAwait(false); if (exception != null) { throw exception; } }; } private static bool IsBodyBuffered( RestMethodInfoInternal restMethod, HttpRequestMessage? request ) { return (restMethod.BodyParameterInfo?.Item2 ?? false) && (request?.Content != null); } static bool DoNotConvertToQueryMap(object? value) { if (value == null) return false; var type = value.GetType(); // Bail out early & match string if (ShouldReturn(type)) return true; if (value is not IEnumerable) return false; // Get the element type for enumerables var ienu = typeof(IEnumerable<>); // We don't want to enumerate to get the type, so we'll just look for IEnumerable var intType = type.GetInterfaces() .FirstOrDefault( i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == ienu ); if (intType == null) return false; type = intType.GetGenericArguments()[0]; return ShouldReturn(type); // Check if type is a simple string or IFormattable type, check underlying type if Nullable static bool ShouldReturn(Type type) => Nullable.GetUnderlyingType(type) is { } underlyingType ? ShouldReturn(underlyingType) : type == typeof(string) || type == typeof(bool) || type == typeof(char) || typeof(IFormattable).IsAssignableFrom(type) || type == typeof(Uri); } static void SetHeader(HttpRequestMessage request, string name, string? value) { // Clear any existing version of this header that might be set, because // we want to allow removal/redefinition of headers. // We also don't want to double up content headers which may have been // set for us automatically. // NB: We have to enumerate the header names to check existence because // Contains throws if it's the wrong header type for the collection. if (request.Headers.Any(x => x.Key == name)) { request.Headers.Remove(name); } if (request.Content != null && request.Content.Headers.Any(x => x.Key == name)) { request.Content.Headers.Remove(name); } if (value == null) return; // CRLF injection protection name = EnsureSafe(name); value = EnsureSafe(value); var added = request.Headers.TryAddWithoutValidation(name, value); // Don't even bother trying to add the header as a content header // if we just added it to the other collection. if (!added && request.Content != null) { request.Content.Headers.TryAddWithoutValidation(name, value); } } static string EnsureSafe(string value) { // Remove CR and LF characters #pragma warning disable CA1307 // Specify StringComparison for clarity return value.Replace("\r", string.Empty).Replace("\n", string.Empty); #pragma warning restore CA1307 // Specify StringComparison for clarity } static bool IsBodyless(HttpMethod method) => method == HttpMethod.Get || method == HttpMethod.Head; } } ================================================ FILE: Refit/RestMethodInfo.cs ================================================ using System.Diagnostics; using System.Net.Http; using System.Reflection; using System.Text.RegularExpressions; // Enable support for C# 9 record types #if !NET6_0_OR_GREATER namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } } #endif namespace Refit { /// /// RestMethodInfo /// public record RestMethodInfo( string Name, Type HostingType, MethodInfo MethodInfo, string RelativePath, Type ReturnType ); [DebuggerDisplay("{MethodInfo}")] internal class RestMethodInfoInternal { private int HeaderCollectionParameterIndex { get; } private string Name => MethodInfo.Name; public Type Type { get; } public MethodInfo MethodInfo { get; } public HttpMethod HttpMethod { get; } public string RelativePath { get; } public bool IsMultipart { get; } public string MultipartBoundary { get; private set; } public RestMethodInfo RestMethodInfo { get; } public ParameterInfo? CancellationToken { get; } public UriFormat QueryUriFormat { get; } public Dictionary Headers { get; } public Dictionary HeaderParameterMap { get; } public Dictionary PropertyParameterMap { get; } public Tuple? BodyParameterInfo { get; } public Tuple? AuthorizeParameterInfo { get; } public Dictionary QueryParameterMap { get; } public Dictionary> AttachmentNameMap { get; } public ParameterInfo[] ParameterInfoArray { get; } public Dictionary ParameterMap { get; } public List FragmentPath { get ; set ; } public Type ReturnType { get; set; } public Type ReturnResultType { get; set; } public Type DeserializedResultType { get; set; } public RefitSettings RefitSettings { get; } public bool IsApiResponse { get; } public bool ShouldDisposeResponse { get; private set; } static readonly Regex ParameterRegex = new("{(([^/?\r\n])*?)}"); static readonly HttpMethod PatchMethod = new("PATCH"); #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public RestMethodInfoInternal( Type targetInterface, MethodInfo methodInfo, RefitSettings? refitSettings = null ) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { RefitSettings = refitSettings ?? new RefitSettings(); Type = targetInterface ?? throw new ArgumentNullException(nameof(targetInterface)); MethodInfo = methodInfo ?? throw new ArgumentNullException(nameof(methodInfo)); var hma = methodInfo.GetCustomAttributes(true).OfType().First(); HttpMethod = hma.Method; RelativePath = hma.Path; IsMultipart = methodInfo.GetCustomAttributes(true).OfType().Any(); MultipartBoundary = IsMultipart ? methodInfo.GetCustomAttribute(true)?.BoundaryText ?? new MultipartAttribute().BoundaryText : string.Empty; VerifyUrlPathIsSane(RelativePath); DetermineReturnTypeInfo(methodInfo); DetermineIfResponseMustBeDisposed(); // Exclude cancellation token parameters from this list ParameterInfoArray = methodInfo .GetParameters() .Where(static p => p.ParameterType != typeof(CancellationToken) && p.ParameterType != typeof(CancellationToken?)) .ToArray(); (ParameterMap, FragmentPath) = BuildParameterMap(RelativePath, ParameterInfoArray); BodyParameterInfo = FindBodyParameter(ParameterInfoArray, IsMultipart, hma.Method); AuthorizeParameterInfo = FindAuthorizationParameter(ParameterInfoArray); Headers = ParseHeaders(methodInfo); HeaderParameterMap = BuildHeaderParameterMap(ParameterInfoArray); HeaderCollectionParameterIndex = GetHeaderCollectionParameterIndex( ParameterInfoArray ); PropertyParameterMap = BuildRequestPropertyMap(ParameterInfoArray); // get names for multipart attachments Dictionary>? attachmentDict = null; if (IsMultipart) { for (var i = 0; i < ParameterInfoArray.Length; i++) { if ( ParameterMap.ContainsKey(i) || HeaderParameterMap.ContainsKey(i) || PropertyParameterMap.ContainsKey(i) || HeaderCollectionAt(i) ) { continue; } var attachmentName = GetAttachmentNameForParameter(ParameterInfoArray[i]); if (attachmentName == null) continue; attachmentDict ??= []; attachmentDict[i] = Tuple.Create( attachmentName, GetUrlNameForParameter(ParameterInfoArray[i]) ); } } AttachmentNameMap = attachmentDict ?? EmptyDictionary>.Get(); Dictionary? queryDict = null; for (var i = 0; i < ParameterInfoArray.Length; i++) { if ( ParameterMap.ContainsKey(i) || HeaderParameterMap.ContainsKey(i) || PropertyParameterMap.ContainsKey(i) || HeaderCollectionAt(i) || (BodyParameterInfo != null && BodyParameterInfo.Item3 == i) || (AuthorizeParameterInfo != null && AuthorizeParameterInfo.Item2 == i) ) { continue; } queryDict ??= []; queryDict.Add(i, GetUrlNameForParameter(ParameterInfoArray[i])); } QueryParameterMap = queryDict ?? EmptyDictionary.Get(); var ctParamEnumerable = methodInfo .GetParameters() .Where(p => p.ParameterType == typeof(CancellationToken)) .TryGetSingle(out var ctParam); if (ctParamEnumerable == EnumerablePeek.Many) { throw new ArgumentException( $"Argument list to method \"{methodInfo.Name}\" can only contain a single CancellationToken" ); } RestMethodInfo = new RestMethodInfo(Name, Type, MethodInfo, RelativePath, ReturnType!); CancellationToken = ctParam; QueryUriFormat = methodInfo.GetCustomAttribute()?.UriFormat ?? UriFormat.UriEscaped; IsApiResponse = ReturnResultType!.GetTypeInfo().IsGenericType && ( ReturnResultType!.GetGenericTypeDefinition() == typeof(ApiResponse<>) || ReturnResultType.GetGenericTypeDefinition() == typeof(IApiResponse<>) ) || ReturnResultType == typeof(IApiResponse); } public bool HasHeaderCollection => HeaderCollectionParameterIndex >= 0; public bool HeaderCollectionAt(int index) => HeaderCollectionParameterIndex >= 0 && HeaderCollectionParameterIndex == index; static int GetHeaderCollectionParameterIndex(ParameterInfo[] parameterArray) { var headerIndex = -1; for (var i = 0; i < parameterArray.Length; i++) { var param = parameterArray[i]; var headerCollection = param .GetCustomAttributes(true) .OfType() .FirstOrDefault(); if (headerCollection == null) continue; //opted for IDictionary semantics here as opposed to the looser IEnumerable> because IDictionary will enforce uniqueness of keys if (param.ParameterType.IsAssignableFrom(typeof(IDictionary))) { // throw if there is already a HeaderCollection parameter if(headerIndex >= 0) throw new ArgumentException("Only one parameter can be a HeaderCollection parameter"); headerIndex = i; } else { throw new ArgumentException( $"HeaderCollection parameter of type {param.ParameterType.Name} is not assignable from IDictionary" ); } } return headerIndex; } static Dictionary BuildRequestPropertyMap(ParameterInfo[] parameterArray) { Dictionary? propertyMap = null; for (var i = 0; i < parameterArray.Length; i++) { var param = parameterArray[i]; var propertyAttribute = param .GetCustomAttributes(true) .OfType() .FirstOrDefault(); if (propertyAttribute != null) { var propertyKey = !string.IsNullOrEmpty(propertyAttribute.Key) ? propertyAttribute.Key : param.Name!; propertyMap ??= new Dictionary(); propertyMap[i] = propertyKey!; } } return propertyMap ?? EmptyDictionary.Get(); } static IEnumerable GetParameterProperties(ParameterInfo parameter) { return parameter .ParameterType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(static p => p.CanRead && p.GetMethod?.IsPublic == true); } static void VerifyUrlPathIsSane(string relativePath) { if (string.IsNullOrEmpty(relativePath)) return; if (!relativePath.StartsWith("/")) throw new ArgumentException( $"URL path {relativePath} must start with '/' and be of the form '/foo/bar/baz'" ); // CRLF injection protection if (relativePath.Contains('\r') || relativePath.Contains('\n')) throw new ArgumentException( $"URL path {relativePath} must not contain CR or LF characters" ); } static (Dictionary ret, List fragmentList) BuildParameterMap( string relativePath, ParameterInfo[] parameterInfo ) { var ret = new Dictionary(); // This section handles pattern matching in the URL. We also need it to add parameter key/values for any attribute with a [Query] var parameterizedParts = ParameterRegex.Matches(relativePath).Cast().ToArray(); if (parameterizedParts.Length == 0) { if(string.IsNullOrEmpty(relativePath)) return (ret, []); return (ret, [ParameterFragment.Constant(relativePath)]); } var paramValidationDict = parameterInfo.ToDictionary( k => GetUrlNameForParameter(k).ToLowerInvariant(), v => v ); //if the param is an lets make a dictionary for all it's potential parameters var objectParamValidationDict = parameterInfo .Where(x => x.ParameterType.GetTypeInfo().IsClass) .SelectMany(x => GetParameterProperties(x).Select(p => Tuple.Create(x, p))) .GroupBy( i => $"{i.Item1.Name}.{GetUrlNameForProperty(i.Item2)}".ToLowerInvariant() ) .ToDictionary(k => k.Key, v => v.First()); var fragmentList = new List(); var index = 0; foreach (var match in parameterizedParts) { // Add constant value from given http path if (match.Index != index) { fragmentList.Add(ParameterFragment.Constant(relativePath.Substring(index, match.Index - index))); } index = match.Index + match.Length; var rawName = match.Groups[1].Value.ToLowerInvariant(); var isRoundTripping = rawName.StartsWith("**"); var name = isRoundTripping ? rawName.Substring(2) : rawName; if (paramValidationDict.TryGetValue(name, out var value)) //if it's a standard parameter { var paramType = value.ParameterType; if (isRoundTripping && paramType != typeof(string)) { throw new ArgumentException( $"URL {relativePath} has round-tripping parameter {rawName}, but the type of matched method parameter is {paramType.FullName}. It must be a string." ); } var parameterType = isRoundTripping ? ParameterType.RoundTripping : ParameterType.Normal; var restMethodParameterInfo = new RestMethodParameterInfo(name, value) { Type = parameterType }; var parameterIndex = Array.IndexOf(parameterInfo, restMethodParameterInfo.ParameterInfo); fragmentList.Add(ParameterFragment.Dynamic(parameterIndex)); #if NET6_0_OR_GREATER ret.TryAdd( parameterIndex, restMethodParameterInfo ); #else if (!ret.ContainsKey(parameterIndex)) { ret.Add(parameterIndex, restMethodParameterInfo); } #endif } //else if it's a property on a object parameter else if ( objectParamValidationDict.TryGetValue(name, out var value1) && !isRoundTripping ) { var property = value1; var parameterIndex = Array.IndexOf(parameterInfo, property.Item1); //If we already have this parameter, add additional ParameterProperty if (ret.TryGetValue(parameterIndex, out var value2)) { if (!value2.IsObjectPropertyParameter) { throw new ArgumentException( $"Parameter {property.Item1.Name} matches both a parameter and nested parameter on a parameter object" ); } value2.ParameterProperties.Add( new RestMethodParameterProperty(name, property.Item2) ); fragmentList.Add(ParameterFragment.DynamicObject(parameterIndex, value2.ParameterProperties.Count - 1)); } else { var restMethodParameterInfo = new RestMethodParameterInfo( true, property.Item1 ); restMethodParameterInfo.ParameterProperties.Add( new RestMethodParameterProperty(name, property.Item2) ); var idx = Array.IndexOf(parameterInfo, restMethodParameterInfo.ParameterInfo); fragmentList.Add(ParameterFragment.DynamicObject(idx, 0)); #if NET6_0_OR_GREATER ret.TryAdd( idx, restMethodParameterInfo ); #else // Do the contains check if (!ret.ContainsKey(idx)) { ret.Add(idx, restMethodParameterInfo); } #endif } } else { throw new ArgumentException( $"URL {relativePath} has parameter {rawName}, but no method parameter matches" ); } } if (index >= relativePath.Length) return (ret, fragmentList); // add trailing string var trailingConstant = relativePath.Substring(index); fragmentList.Add(ParameterFragment.Constant(trailingConstant)); return (ret, fragmentList); } static string GetUrlNameForParameter(ParameterInfo paramInfo) { var aliasAttr = paramInfo .GetCustomAttributes(true) .OfType() .FirstOrDefault(); return aliasAttr != null ? aliasAttr.Name : paramInfo.Name!; } static string GetUrlNameForProperty(PropertyInfo propInfo) { var aliasAttr = propInfo .GetCustomAttributes(true) .OfType() .FirstOrDefault(); return aliasAttr != null ? aliasAttr.Name : propInfo.Name; } static string GetAttachmentNameForParameter(ParameterInfo paramInfo) { #pragma warning disable CS0618 // Type or member is obsolete var nameAttr = paramInfo .GetCustomAttributes(true) #pragma warning restore CS0618 // Type or member is obsolete .FirstOrDefault(); // also check for AliasAs return nameAttr?.Name ?? paramInfo.GetCustomAttributes(true).FirstOrDefault()?.Name!; } Tuple? FindBodyParameter( ParameterInfo[] parameterArray, bool isMultipart, HttpMethod method ) { // The body parameter is found using the following logic / order of precedence: // 1) [Body] attribute // 2) POST/PUT/PATCH: Reference type other than string // 3) If there are two reference types other than string, without the body attribute, throw var bodyParamEnumerable = parameterArray .Select( x => ( Parameter: x, BodyAttribute: x.GetCustomAttributes(true) .OfType() .FirstOrDefault() ) ) .Where(x => x.BodyAttribute != null) .TryGetSingle(out var bodyParam); // multipart requests may not contain a body, implicit or explicit if (isMultipart) { if (bodyParamEnumerable != EnumerablePeek.Empty) { throw new ArgumentException( "Multipart requests may not contain a Body parameter" ); } return null; } if (bodyParamEnumerable == EnumerablePeek.Many) { throw new ArgumentException("Only one parameter can be a Body parameter"); } // #1, body attribute wins if (bodyParamEnumerable == EnumerablePeek.Single) { return Tuple.Create( bodyParam!.BodyAttribute!.SerializationMethod, bodyParam.BodyAttribute.Buffered ?? RefitSettings.Buffered, Array.IndexOf(parameterArray, bodyParam.Parameter) ); } // Not in post/put/patch? bail if ( !method.Equals(HttpMethod.Post) && !method.Equals(HttpMethod.Put) && !method.Equals(PatchMethod) ) { return null; } // see if we're a post/put/patch // explicitly skip [Query], [HeaderCollection], and [Property]-denoted params var refParamEnumerable = parameterArray .Where( pi => !pi.ParameterType.GetTypeInfo().IsValueType && pi.ParameterType != typeof(string) && pi.GetCustomAttribute() == null && pi.GetCustomAttribute() == null && pi.GetCustomAttribute() == null ) .TryGetSingle(out var refParam); // Check for rule #3 if (refParamEnumerable == EnumerablePeek.Many) { throw new ArgumentException( "Multiple complex types found. Specify one parameter as the body using BodyAttribute" ); } if (refParamEnumerable == EnumerablePeek.Single) { return Tuple.Create( BodySerializationMethod.Serialized, RefitSettings.Buffered, Array.IndexOf(parameterArray, refParam!) ); } return null; } static Tuple? FindAuthorizationParameter(ParameterInfo[] parameterArray) { var authorizeParamsEnumerable = parameterArray .Select( x => ( Parameter: x, AuthorizeAttribute: x.GetCustomAttributes(true) .OfType() .FirstOrDefault() ) ) .Where(x => x.AuthorizeAttribute != null) .TryGetSingle(out var authorizeParam); if (authorizeParamsEnumerable == EnumerablePeek.Many) { throw new ArgumentException("Only one parameter can be an Authorize parameter"); } if (authorizeParamsEnumerable == EnumerablePeek.Single) { return Tuple.Create( authorizeParam!.AuthorizeAttribute!.Scheme, Array.IndexOf(parameterArray, authorizeParam.Parameter) ); } return null; } static Dictionary ParseHeaders(MethodInfo methodInfo) { var inheritedAttributes = methodInfo.DeclaringType != null ? methodInfo .DeclaringType.GetInterfaces() .SelectMany(i => i.GetTypeInfo().GetCustomAttributes(true)) .Reverse() : []; var declaringTypeAttributes = methodInfo.DeclaringType != null ? methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true) : []; // Headers set on the declaring type have to come first, // so headers set on the method can replace them. Switching // the order here will break stuff. var headers = inheritedAttributes .Concat(declaringTypeAttributes) .Concat(methodInfo.GetCustomAttributes(true)) .OfType() .SelectMany(ha => ha.Headers); Dictionary? ret = null; foreach (var header in headers) { if (string.IsNullOrWhiteSpace(header)) continue; ret ??= []; // NB: Silverlight doesn't have an overload for String.Split() // with a count parameter, but header values can contain // ':' so we have to re-join all but the first part to get the // value. var parts = header.Split(':'); ret[parts[0].Trim()] = parts.Length > 1 ? string.Join(":", parts.Skip(1)).Trim() : null; } return ret ?? EmptyDictionary.Get(); } static Dictionary BuildHeaderParameterMap(ParameterInfo[] parameterArray) { Dictionary? ret = null; for (var i = 0; i < parameterArray.Length; i++) { var header = parameterArray[i] .GetCustomAttributes(true) .OfType() .Select(ha => ha.Header) .FirstOrDefault(); if (!string.IsNullOrWhiteSpace(header)) { ret ??= []; ret[i] = header.Trim(); } } return ret ?? EmptyDictionary.Get(); } void DetermineReturnTypeInfo(MethodInfo methodInfo) { var returnType = methodInfo.ReturnType; if ( returnType.IsGenericType && ( methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) || methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>) || methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(IObservable<>) ) ) { ReturnType = returnType; ReturnResultType = returnType.GetGenericArguments()[0]; if ( ReturnResultType.IsGenericType && ( ReturnResultType.GetGenericTypeDefinition() == typeof(ApiResponse<>) || ReturnResultType.GetGenericTypeDefinition() == typeof(IApiResponse<>) ) ) { DeserializedResultType = ReturnResultType.GetGenericArguments()[0]; } else if (ReturnResultType == typeof(IApiResponse)) { DeserializedResultType = typeof(HttpContent); } else DeserializedResultType = ReturnResultType; } else if (returnType == typeof(Task)) { ReturnType = methodInfo.ReturnType; ReturnResultType = typeof(void); DeserializedResultType = typeof(void); } else { // Allow synchronous return types only for non-public or explicit interface members. // This supports internal Refit methods and explicit interface members annotated with Refit attributes. var isExplicitInterfaceMember = methodInfo.Name.IndexOf('.') >= 0; var isNonPublic = !(methodInfo.IsPublic); if (!(isExplicitInterfaceMember || isNonPublic)) { throw new ArgumentException( $"Method \"{methodInfo.Name}\" is invalid. All REST Methods must return either Task or ValueTask or IObservable" ); } ReturnType = methodInfo.ReturnType; ReturnResultType = methodInfo.ReturnType; DeserializedResultType = methodInfo.ReturnType == typeof(IApiResponse) ? typeof(HttpContent) : methodInfo.ReturnType; } } void DetermineIfResponseMustBeDisposed() { // Rest method caller will have to dispose if it's one of those 3 ShouldDisposeResponse = DeserializedResultType != typeof(HttpResponseMessage) && DeserializedResultType != typeof(HttpContent) && DeserializedResultType != typeof(Stream); } } internal record struct ParameterFragment(string? Value, int ArgumentIndex, int PropertyIndex) { public bool IsConstant => Value != null; public bool IsDynamicRoute => ArgumentIndex >= 0 && PropertyIndex < 0; public bool IsObjectProperty => ArgumentIndex >= 0 && PropertyIndex >= 0; public static ParameterFragment Constant(string value) => new (value, -1, -1); public static ParameterFragment Dynamic(int index) => new (null, index, -1); public static ParameterFragment DynamicObject(int index, int propertyIndex) => new (null, index, propertyIndex); } } ================================================ FILE: Refit/RestMethodParameterInfo.cs ================================================ using System.Reflection; namespace Refit { /// /// RestMethodParameterInfo. /// public class RestMethodParameterInfo { /// /// Initializes a new instance of the class. /// /// The name. /// The parameter information. public RestMethodParameterInfo(string name, ParameterInfo parameterInfo) { Name = name; ParameterInfo = parameterInfo; } /// /// Initializes a new instance of the class. /// /// if set to true [is object property parameter]. /// The parameter information. public RestMethodParameterInfo(bool isObjectPropertyParameter, ParameterInfo parameterInfo) { IsObjectPropertyParameter = isObjectPropertyParameter; ParameterInfo = parameterInfo; } /// /// Gets or sets the name. /// /// /// The name. /// public string? Name { get; set; } /// /// Gets or sets the parameter information. /// /// /// The parameter information. /// public ParameterInfo ParameterInfo { get; set; } /// /// Gets or sets a value indicating whether this instance is object property parameter. /// /// /// true if this instance is object property parameter; otherwise, false. /// public bool IsObjectPropertyParameter { get; set; } /// /// Gets or sets the parameter properties. /// /// /// The parameter properties. /// public List ParameterProperties { get; set; } = []; /// /// Gets or sets the type. /// /// /// The type. /// public ParameterType Type { get; set; } = ParameterType.Normal; } /// /// RestMethodParameterProperty. /// /// /// Initializes a new instance of the class. /// /// The name. /// The property information. public class RestMethodParameterProperty(string name, PropertyInfo propertyInfo) { /// /// Gets or sets the name. /// /// /// The name. /// public string Name { get; set; } = name; /// /// Gets or sets the property information. /// /// /// The property information. /// public PropertyInfo PropertyInfo { get; set; } = propertyInfo; } /// /// ParameterType. /// public enum ParameterType { /// /// The normal /// Normal, /// /// The round tripping /// RoundTripping } } ================================================ FILE: Refit/RestService.cs ================================================ using System.Collections.Concurrent; using System.Net.Http; #if NET8_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace Refit { /// /// RestService. /// public static class RestService { static readonly ConcurrentDictionary TypeMapping = new(); /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// The the implementation will use to send requests. /// to use to build requests. /// An instance that implements . #if NET8_0_OR_GREATER public static T For< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(HttpClient client, IRequestBuilder builder) => (T)For(typeof(T), client, builder); #else public static T For(HttpClient client, IRequestBuilder builder) => (T)For(typeof(T), client, builder); #endif /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// The the implementation will use to send requests. /// to use to configure the HttpClient. /// An instance that implements . #if NET8_0_OR_GREATER public static T For< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(HttpClient client, RefitSettings? settings) #else public static T For(HttpClient client, RefitSettings? settings) #endif { var requestBuilder = RequestBuilder.ForType(settings); return For(client, requestBuilder); } /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// The the implementation will use to send requests. /// An instance that implements . #if NET8_0_OR_GREATER public static T For< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(HttpClient client) => For(client, (RefitSettings?)null); #else public static T For(HttpClient client) => For(client, (RefitSettings?)null); #endif /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// Base address the implementation will use. /// to use to configure the HttpClient. /// An instance that implements . #if NET8_0_OR_GREATER public static T For< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(string hostUrl, RefitSettings? settings) #else public static T For(string hostUrl, RefitSettings? settings) #endif { var client = CreateHttpClient(hostUrl, settings); return For(client, settings); } /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// Base address the implementation will use. /// An instance that implements . #if NET8_0_OR_GREATER public static T For< [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] T>(string hostUrl) => For(hostUrl, null); #else public static T For(string hostUrl) => For(hostUrl, null); #endif /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// The the implementation will use to send requests. /// to use to build requests. /// An instance that implements . #if NET8_0_OR_GREATER public static object For( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, HttpClient client, IRequestBuilder builder ) #else public static object For( Type refitInterfaceType, HttpClient client, IRequestBuilder builder ) #endif { var generatedType = TypeMapping.GetOrAdd(refitInterfaceType, GetGeneratedType); return Activator.CreateInstance(generatedType, client, builder)!; } /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// The the implementation will use to send requests. /// to use to configure the HttpClient. /// An instance that implements . #if NET8_0_OR_GREATER public static object For( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, HttpClient client, RefitSettings? settings ) #else public static object For( Type refitInterfaceType, HttpClient client, RefitSettings? settings ) #endif { var requestBuilder = RequestBuilder.ForType(refitInterfaceType, settings); return For(refitInterfaceType, client, requestBuilder); } /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// The the implementation will use to send requests. /// An instance that implements . #if NET8_0_OR_GREATER public static object For( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, HttpClient client ) => For(refitInterfaceType, client, (RefitSettings?)null); #else public static object For(Type refitInterfaceType, HttpClient client) => For(refitInterfaceType, client, (RefitSettings?)null); #endif /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// Base address the implementation will use. /// to use to configure the HttpClient. /// An instance that implements . #if NET8_0_OR_GREATER public static object For( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, string hostUrl, RefitSettings? settings ) #else public static object For(Type refitInterfaceType, string hostUrl, RefitSettings? settings) #endif { var client = CreateHttpClient(hostUrl, settings); return For(refitInterfaceType, client, settings); } /// /// Generate a Refit implementation of the specified interface. /// /// Interface to create the implementation for. /// Base address the implementation will use. /// An instance that implements . #if NET8_0_OR_GREATER public static object For( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType, string hostUrl ) => For(refitInterfaceType, hostUrl, null); #else public static object For(Type refitInterfaceType, string hostUrl) => For(refitInterfaceType, hostUrl, null); #endif /// /// Create an with as the base address. /// /// Base address. /// to use to configure the HttpClient. /// A with the various parameters provided. /// public static HttpClient CreateHttpClient(string hostUrl, RefitSettings? settings) { if (string.IsNullOrWhiteSpace(hostUrl)) { throw new ArgumentException( $"`{nameof(hostUrl)}` must not be null or whitespace.", nameof(hostUrl) ); } // check to see if user provided custom auth token HttpMessageHandler? innerHandler = null; if (settings != null) { if (settings.HttpMessageHandlerFactory != null) { innerHandler = settings.HttpMessageHandlerFactory(); } if (settings.AuthorizationHeaderValueGetter != null) { innerHandler = new AuthenticatedHttpClientHandler( settings.AuthorizationHeaderValueGetter, innerHandler ); } } return new HttpClient(innerHandler ?? new HttpClientHandler()) { BaseAddress = new Uri(hostUrl.TrimEnd('/')) }; } #if NET8_0_OR_GREATER [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] static Type GetGeneratedType( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods )] Type refitInterfaceType ) #else static Type GetGeneratedType(Type refitInterfaceType) #endif { var typeName = UniqueName.ForType(refitInterfaceType); var generatedType = Type.GetType(typeName, throwOnError: false); if (generatedType == null) { var message = refitInterfaceType.Name + " doesn't look like a Refit interface. Make sure it has at least one " + "method with a Refit HTTP method attribute, the Refit source generator is installed in the project, " + "and your build produced the generated client. For Native AOT or trimmed apps, prefer generated clients " + "plus source-generated System.Text.Json metadata."; throw new InvalidOperationException(message); } return generatedType; } } } ================================================ FILE: Refit/SystemTextJsonContentSerializer.cs ================================================ using System.Net.Http; using System.Net.Http.Json; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; #if NET8_0_OR_GREATER using System.Text.Json.Serialization.Metadata; #endif namespace Refit { /// /// A implementing using the System.Text.Json APIs /// /// /// Creates a new instance with the specified parameters /// /// The serialization options to use for the current instance public sealed class SystemTextJsonContentSerializer(JsonSerializerOptions jsonSerializerOptions) : IHttpContentSerializer { /// /// Creates a new instance /// public SystemTextJsonContentSerializer() : this(GetDefaultJsonSerializerOptions()) { } /// public HttpContent ToHttpContent(T item) { #if NET8_0_OR_GREATER if (TryGetJsonTypeInfo(out var jsonTypeInfo)) { return JsonContent.Create(item, jsonTypeInfo); } #endif return JsonContent.Create(item, options: jsonSerializerOptions); } /// public async Task FromHttpContentAsync( HttpContent content, CancellationToken cancellationToken = default ) { #if NET8_0_OR_GREATER if (TryGetJsonTypeInfo(out var jsonTypeInfo)) { return await content.ReadFromJsonAsync(jsonTypeInfo, cancellationToken).ConfigureAwait(false); } #endif return await content .ReadFromJsonAsync(jsonSerializerOptions, cancellationToken) .ConfigureAwait(false); } /// /// Calculates what the field name should be for the given property. This may be affected by custom attributes the serializer understands /// /// A PropertyInfo object. /// /// The calculated field name. /// /// propertyInfo public string? GetFieldNameForProperty(PropertyInfo propertyInfo) => propertyInfo switch { null => throw new ArgumentNullException(nameof(propertyInfo)), _ => propertyInfo .GetCustomAttributes(true) .Select(a => a.Name) .FirstOrDefault() }; /// /// Creates new and fills it with default parameters /// public static JsonSerializerOptions GetDefaultJsonSerializerOptions() { // Default to case insensitive property name matching as that's likely the behavior most users expect var jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; jsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); jsonSerializerOptions.Converters.Add(new CamelCaseStringEnumConverter()); return jsonSerializerOptions; } #if NET8_0_OR_GREATER bool TryGetJsonTypeInfo(out JsonTypeInfo jsonTypeInfo) { if (jsonSerializerOptions.TypeInfoResolver is not null) { var typeInfo = jsonSerializerOptions.GetTypeInfo(typeof(T)); if (typeInfo is JsonTypeInfo typedTypeInfo) { jsonTypeInfo = typedTypeInfo; return true; } } jsonTypeInfo = null!; return false; } #endif } /// /// ObjectToInferredTypesConverter. /// From https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0#deserialize-inferred-types-to-object-properties /// public class ObjectToInferredTypesConverter : JsonConverter { /// /// Reads and converts the JSON to type typeToConvert />. /// /// The reader. /// The type to convert. /// An object that specifies serialization options to use. /// /// The converted value. /// public override object? Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options ) => reader.TokenType switch { JsonTokenType.True => true, JsonTokenType.False => false, JsonTokenType.Number when reader.TryGetInt64(out var l) => l, JsonTokenType.Number => reader.GetDouble(), JsonTokenType.String when reader.TryGetDateTime(out var datetime) => datetime, JsonTokenType.String => reader.GetString(), _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() }; /// /// Writes the specified writer. /// /// The writer. /// The object to write. /// The options. public override void Write( Utf8JsonWriter writer, object objectToWrite, JsonSerializerOptions options ) { #if NET8_0_OR_GREATER if (options.TypeInfoResolver is not null) { JsonSerializer.Serialize(writer, objectToWrite, options.GetTypeInfo(objectToWrite.GetType())); return; } #endif JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options); } } sealed class CamelCaseStringEnumConverter : JsonConverterFactory { public override bool CanConvert(Type typeToConvert) => (Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert).IsEnum; public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var enumType = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert; var isNullable = Nullable.GetUnderlyingType(typeToConvert) != null; return new NonGenericEnumConverter(typeToConvert, enumType, isNullable); } sealed class NonGenericEnumConverter(Type targetType, Type enumType, bool isNullable) : JsonConverter { public override bool CanConvert(Type typeToConvert) => typeToConvert == targetType; public override object? Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options ) { if (reader.TokenType == JsonTokenType.Null) { if (!isNullable) throw new JsonException($"Cannot convert null to {targetType}."); return null; } if (reader.TokenType == JsonTokenType.String) { var value = reader.GetString(); if (string.IsNullOrWhiteSpace(value)) { if (isNullable) return null; throw new JsonException($"Cannot convert an empty value to {targetType}."); } foreach (var name in Enum.GetNames(enumType)) { if (string.Equals(ToCamelCase(name), value, StringComparison.Ordinal)) return Enum.Parse(enumType, name, ignoreCase: false); } try { return Enum.Parse(enumType, value, ignoreCase: true); } catch (ArgumentException) { throw new JsonException($"Unable to convert '{value}' to {targetType}."); } } if (reader.TokenType == JsonTokenType.Number) { var numericValue = reader.GetInt64(); return Enum.ToObject(enumType, numericValue); } throw new JsonException($"Unexpected token {reader.TokenType} when parsing {targetType}."); } public override void Write( Utf8JsonWriter writer, object? value, JsonSerializerOptions options ) { if (value is null) { writer.WriteNullValue(); return; } var name = Enum.GetName(enumType, value); if (name is null) { writer.WriteNumberValue(Convert.ToInt64(value)); return; } writer.WriteStringValue(ToCamelCase(name)); } static string ToCamelCase(string value) => string.IsNullOrEmpty(value) || !char.IsUpper(value[0]) ? value : char.ToLowerInvariant(value[0]) + value.Substring(1); } } } ================================================ FILE: Refit/UniqueName.cs ================================================ namespace Refit { class UniqueName { public static string ForType() { return ForType(typeof(T)); } public static string ForType(object? serviceKey) { return ForType(typeof(T), serviceKey); } public static string ForType(Type refitInterfaceType, object? serviceKey) { return ForType(refitInterfaceType) + GetServiceKeySuffix(serviceKey); } public static string ForType(Type refitInterfaceType) { var interfaceTypeName = refitInterfaceType.FullName!; // remove namespace/nested, up to anything before a ` var searchEnd = interfaceTypeName.IndexOf('`'); if (searchEnd < 0) searchEnd = interfaceTypeName.Length; var lastDot = interfaceTypeName.Substring(0, searchEnd).LastIndexOf('.'); if (lastDot > 0) { interfaceTypeName = interfaceTypeName.Substring(lastDot + 1); } // Now we have the interface name like IFooBar`1[[Some Generic Args]] // Or Nested+IFrob var genericArgs = string.Empty; // if there's any generics, split that if (refitInterfaceType.IsGenericType) { genericArgs = interfaceTypeName.Substring(interfaceTypeName.IndexOf("[")); interfaceTypeName = interfaceTypeName.Substring( 0, interfaceTypeName.Length - genericArgs.Length ); } // Remove any + from the type name portion interfaceTypeName = interfaceTypeName.Replace("+", ""); // Get the namespace and remove the dots var ns = refitInterfaceType.Namespace?.Replace(".", ""); // Refit types will be generated as private classes within a Generated type in namespace // Refit.Implementation // E.g., Refit.Implementation.Generated.NamespaceContainingTpeInterfaceType var refitTypeName = $"Refit.Implementation.Generated+{ns}{interfaceTypeName}{genericArgs}"; var assmQualified = $"{refitTypeName}, {refitInterfaceType.Assembly.FullName}"; return assmQualified; } /// /// Returns the suffix for the service key to be added to the unique name for a given type. /// /// The service key to create the suffix from. /// The suffix to be added to the unique name of a given type. static string GetServiceKeySuffix(object? serviceKey) { return serviceKey is null or "" ? string.Empty : $", ServiceKey={serviceKey}"; } } } ================================================ FILE: Refit/ValidationApiException.cs ================================================ using System.Text.Json; namespace Refit { /// /// An ApiException that is raised according to RFC 7807, which contains problem details for validation exceptions. /// [Serializable] public class ValidationApiException : ApiException { static readonly JsonSerializerOptions SerializerOptions = new(); static ValidationApiException() { SerializerOptions.PropertyNameCaseInsensitive = true; SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; SerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); } ValidationApiException(ApiException apiException) : base( apiException.RequestMessage, apiException.HttpMethod, apiException.Content, apiException.StatusCode, apiException.ReasonPhrase, apiException.Headers, apiException.RefitSettings ) { } /// /// Creates a new instance of a ValidationException from an existing ApiException. /// /// An instance of an ApiException to use to build a ValidationException. /// ValidationApiException public static ValidationApiException Create(ApiException exception) { if (exception is null) throw new ArgumentNullException(nameof(exception)); if (string.IsNullOrWhiteSpace(exception.Content)) throw new ArgumentException( "Content must be an 'application/problem+json' compliant json string." ); var ex = new ValidationApiException(exception); if (!string.IsNullOrWhiteSpace(exception.Content)) { ex.Content = JsonSerializer.Deserialize( exception.Content!, SerializerOptions ); } return ex; } /// /// The problem details of the RFC 7807 validation exception. /// public new ProblemDetails? Content { get; private set; } } } ================================================ FILE: Refit/ValueStringBuilder.cs ================================================ using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Refit; // From https://github/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/ValueStringBuilder.cs internal ref struct ValueStringBuilder { private char[]? _arrayToReturnToPool; private Span _chars; private int _pos; public ValueStringBuilder(Span initialBuffer) { _arrayToReturnToPool = null; _chars = initialBuffer; _pos = 0; } public ValueStringBuilder(int initialCapacity) { _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); _chars = _arrayToReturnToPool; _pos = 0; } public int Length { get => _pos; set { Debug.Assert(value >= 0); Debug.Assert(value <= _chars.Length); _pos = value; } } public int Capacity => _chars.Length; public void EnsureCapacity(int capacity) { // This is not expected to be called this with negative capacity Debug.Assert(capacity >= 0); // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. if ((uint)capacity > (uint)_chars.Length) Grow(capacity - _pos); } /// /// Get a pinnable reference to the builder. /// Does not ensure there is a null char after /// This overload is pattern matched in the C# 7.3+ compiler so you can omit /// the explicit method call, and write eg "fixed (char* c = builder)" /// public ref char GetPinnableReference() => ref MemoryMarshal.GetReference(_chars); /// /// Get a pinnable reference to the builder. /// /// Ensures that the builder has a null char after public ref char GetPinnableReference(bool terminate) { if (terminate) { EnsureCapacity(Length + 1); _chars[Length] = '\0'; } return ref MemoryMarshal.GetReference(_chars); } public ref char this[int index] { get { Debug.Assert(index < _pos); return ref _chars[index]; } } public override string ToString() { var s = _chars.Slice(0, _pos).ToString(); Dispose(); return s; } /// Returns the underlying storage of the builder. public Span RawChars => _chars; /// /// Returns a span around the contents of the builder. /// /// Ensures that the builder has a null char after public ReadOnlySpan AsSpan(bool terminate) { if (terminate) { EnsureCapacity(Length + 1); _chars[Length] = '\0'; } return _chars.Slice(0, _pos); } public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); public bool TryCopyTo(Span destination, out int charsWritten) { if (_chars.Slice(0, _pos).TryCopyTo(destination)) { charsWritten = _pos; Dispose(); return true; } else { charsWritten = 0; Dispose(); return false; } } public void Insert(int index, char value, int count) { if (_pos > _chars.Length - count) { Grow(count); } var remaining = _pos - index; _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); _chars.Slice(index, count).Fill(value); _pos += count; } public void Insert(int index, string? s) { if (s == null) { return; } var count = s.Length; if (_pos > (_chars.Length - count)) { Grow(count); } var remaining = _pos - index; _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); s #if !NETCOREAPP .AsSpan() #endif .CopyTo(_chars.Slice(index)); _pos += count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(char c) { var pos = _pos; var chars = _chars; if ((uint)pos < (uint)chars.Length) { chars[pos] = c; _pos = pos + 1; } else { GrowAndAppend(c); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(string? s) { if (s == null) { return; } var pos = _pos; if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. { _chars[pos] = s[0]; _pos = pos + 1; } else { AppendSlow(s); } } private void AppendSlow(string s) { var pos = _pos; if (pos > _chars.Length - s.Length) { Grow(s.Length); } s #if !NETCOREAPP .AsSpan() #endif .CopyTo(_chars.Slice(pos)); _pos += s.Length; } public void Append(char c, int count) { if (_pos > _chars.Length - count) { Grow(count); } var dst = _chars.Slice(_pos, count); for (var i = 0; i < dst.Length; i++) { dst[i] = c; } _pos += count; } public void Append(ReadOnlySpan value) { var pos = _pos; if (pos > _chars.Length - value.Length) { Grow(value.Length); } value.CopyTo(_chars.Slice(_pos)); _pos += value.Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span AppendSpan(int length) { var origPos = _pos; if (origPos > _chars.Length - length) { Grow(length); } _pos = origPos + length; return _chars.Slice(origPos, length); } [MethodImpl(MethodImplOptions.NoInlining)] private void GrowAndAppend(char c) { Grow(1); Append(c); } /// /// Resize the internal buffer either by doubling current buffer size or /// by adding to /// whichever is greater. /// /// /// Number of chars requested beyond current position. /// [MethodImpl(MethodImplOptions.NoInlining)] private void Grow(int additionalCapacityBeyondPos) { Debug.Assert(additionalCapacityBeyondPos > 0); Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try // to double the size if possible, bounding the doubling to not go beyond the max array length. var newCapacity = (int)Math.Max( (uint)(_pos + additionalCapacityBeyondPos), Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. // This could also go negative if the actual required length wraps around. var poolArray = ArrayPool.Shared.Rent(newCapacity); _chars.Slice(0, _pos).CopyTo(poolArray); var toReturn = _arrayToReturnToPool; _chars = _arrayToReturnToPool = poolArray; if (toReturn != null) { ArrayPool.Shared.Return(toReturn); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { var toReturn = _arrayToReturnToPool; this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again if (toReturn != null) { ArrayPool.Shared.Return(toReturn); } } } ================================================ FILE: Refit/targets/refit.props ================================================ ================================================ FILE: Refit/targets/refit.targets ================================================ $(RootNamespace) <_RefitMSBuildMinVersion>16.8.0 <_RefitAnalyzer Include="@(Analyzer)" Condition="'%(Analyzer.NuGetPackageId)' == 'Refit'" /> ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_AllReturnTypes.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_ObservableHttpResponseMessage.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *ObservableHttpResponseMessage* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_Task.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *Task_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskApiResponseT.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskApiResponseT_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskHttpContent.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpContent_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskHttpResponseMessage.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpResponseMessage_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskStream.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskStream_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskString.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskString_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskT.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskT_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_AllReturnTypes.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_ObservableHttpResponseMessage.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *ObservableHttpResponseMessage* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_Task.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *Task_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_TaskApiResponseT.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskApiResponseT_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_TaskHttpContent.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpContent_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_TaskHttpResponseMessage.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpResponseMessage_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_TaskStream.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskStream_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_TaskString.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskString_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/net6.0/Benchmark_TaskT.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskT_Async* --framework=net6.0 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_AllReturnTypes.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_ObservableHttpResponseMessage.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *ObservableHttpResponseMessage* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_Task.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *Task_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_TaskApiResponseT.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskApiResponseT_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_TaskHttpContent.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpContent_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_TaskHttpResponseMessage.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpResponseMessage_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_TaskStream.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskStream_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_TaskString.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskString_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/Benchmarks/netcoreapp3.1/Benchmark_TaskT.bat ================================================ dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskT_Async* --framework=netcoreapp3.1 ================================================ FILE: Refit.Benchmarks/EndToEndBenchmark.cs ================================================ using System.Net; using AutoFixture; using BenchmarkDotNet.Attributes; namespace Refit.Benchmarks; [MemoryDiagnoser] public class EndToEndBenchmark { private readonly Fixture autoFixture = new(); private const string Host = "https://github.com"; private SystemTextJsonContentSerializer systemTextJsonContentSerializer; private NewtonsoftJsonContentSerializer newtonsoftJsonContentSerializer; private readonly IDictionary> users = new Dictionary>(); private readonly IDictionary< SerializationStrategy, IDictionary > refitClient = new Dictionary< SerializationStrategy, IDictionary > { { SerializationStrategy.SystemTextJson, new Dictionary() }, { SerializationStrategy.NewtonsoftJson, new Dictionary() } }; private readonly IDictionary httpMethod = new Dictionary< HttpVerb, HttpMethod > { { HttpVerb.Get, HttpMethod.Get }, { HttpVerb.Post, HttpMethod.Post } }; private const int TenUsers = 10; public enum SerializationStrategy { SystemTextJson, NewtonsoftJson } public enum HttpVerb { Get, Post } [GlobalSetup] public Task SetupAsync() { systemTextJsonContentSerializer = new SystemTextJsonContentSerializer(); refitClient[SerializationStrategy.SystemTextJson][HttpStatusCode.OK] = RestService.For( Host, new RefitSettings(systemTextJsonContentSerializer) { HttpMessageHandlerFactory = () => new StaticFileHttpResponseHandler( "system-text-json-10-users.json", HttpStatusCode.OK ) } ); refitClient[SerializationStrategy.SystemTextJson][HttpStatusCode.InternalServerError] = RestService.For( Host, new RefitSettings(systemTextJsonContentSerializer) { HttpMessageHandlerFactory = () => new StaticFileHttpResponseHandler( "system-text-json-10-users.json", HttpStatusCode.InternalServerError ) } ); newtonsoftJsonContentSerializer = new NewtonsoftJsonContentSerializer(); refitClient[SerializationStrategy.NewtonsoftJson][HttpStatusCode.OK] = RestService.For( Host, new RefitSettings(newtonsoftJsonContentSerializer) { HttpMessageHandlerFactory = () => new StaticFileHttpResponseHandler( "newtonsoft-json-10-users.json", System.Net.HttpStatusCode.OK ) } ); refitClient[SerializationStrategy.NewtonsoftJson][HttpStatusCode.InternalServerError] = RestService.For( Host, new RefitSettings(newtonsoftJsonContentSerializer) { HttpMessageHandlerFactory = () => new StaticFileHttpResponseHandler( "newtonsoft-json-10-users.json", System.Net.HttpStatusCode.InternalServerError ) } ); users[TenUsers] = autoFixture.CreateMany(TenUsers); return Task.CompletedTask; } /* * Each [Benchmark] tests one return type that Refit allows and is parameterized to test different, serializers, and http methods, and status codes */ [Params(HttpStatusCode.OK, HttpStatusCode.InternalServerError)] public HttpStatusCode HttpStatusCode { get; set; } [Params(TenUsers)] public int ModelCount { get; set; } [ParamsAllValues] public HttpVerb Verb { get; set; } [ParamsAllValues] public SerializationStrategy Serializer { get; set; } [Benchmark] public async Task Task_Async() { try { switch (Verb) { case HttpVerb.Get: await refitClient[Serializer][HttpStatusCode].GetUsersTaskAsync(); break; case HttpVerb.Post: await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskAsync(users[ModelCount]); break; default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } catch { //swallow } } [Benchmark] public async Task TaskString_Async() { try { switch (Verb) { case HttpVerb.Get: return await refitClient[Serializer] [HttpStatusCode] .GetUsersTaskStringAsync(); case HttpVerb.Post: return await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskStringAsync(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } catch { //swallow } return default; } [Benchmark] public async Task TaskStream_Async() { try { switch (Verb) { case HttpVerb.Get: return await refitClient[Serializer] [HttpStatusCode] .GetUsersTaskStreamAsync(); case HttpVerb.Post: return await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskStreamAsync(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } catch { //swallow } return default; } [Benchmark] public async Task TaskHttpContent_Async() { try { switch (Verb) { case HttpVerb.Get: return await refitClient[Serializer] [HttpStatusCode] .GetUsersTaskHttpContentAsync(); case HttpVerb.Post: return await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskHttpContentAsync(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } catch { //swallow } return default; } [Benchmark] public async Task TaskHttpResponseMessage_Async() { switch (Verb) { case HttpVerb.Get: return await refitClient[Serializer] [HttpStatusCode] .GetUsersTaskHttpResponseMessageAsync(); case HttpVerb.Post: return await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskHttpResponseMessageAsync(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } [Benchmark] public IObservable ObservableHttpResponseMessage() { switch (Verb) { case HttpVerb.Get: return refitClient[Serializer] [HttpStatusCode] .GetUsersObservableHttpResponseMessage(); case HttpVerb.Post: return refitClient[Serializer] [HttpStatusCode] .PostUsersObservableHttpResponseMessage(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } [Benchmark] public async Task> TaskT_Async() { try { switch (Verb) { case HttpVerb.Get: return await refitClient[Serializer][HttpStatusCode].GetUsersTaskTAsync(); case HttpVerb.Post: return await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskTAsync(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } catch { //swallow } return default; } [Benchmark] public async Task>> TaskApiResponseT_Async() { switch (Verb) { case HttpVerb.Get: return await refitClient[Serializer] [HttpStatusCode] .GetUsersTaskApiResponseTAsync(); case HttpVerb.Post: return await refitClient[Serializer] [HttpStatusCode] .PostUsersTaskApiResponseTAsync(users[ModelCount]); default: throw new ArgumentOutOfRangeException(nameof(Verb)); } } } ================================================ FILE: Refit.Benchmarks/IGitHubService.cs ================================================ namespace Refit.Benchmarks; public interface IGitHubService { //Task - throws [Get("/users")] public Task GetUsersTaskAsync(); [Post("/users")] public Task PostUsersTaskAsync([Body] IEnumerable users); //Task - throws [Get("/users")] public Task GetUsersTaskStringAsync(); [Post("/users")] public Task PostUsersTaskStringAsync([Body] IEnumerable users); //Task - throws [Get("/users")] public Task GetUsersTaskStreamAsync(); [Post("/users")] public Task PostUsersTaskStreamAsync([Body] IEnumerable users); //Task - throws [Get("/users")] public Task GetUsersTaskHttpContentAsync(); [Post("/users")] public Task PostUsersTaskHttpContentAsync([Body] IEnumerable users); //Task [Get("/users")] public Task GetUsersTaskHttpResponseMessageAsync(); [Post("/users")] public Task PostUsersTaskHttpResponseMessageAsync( [Body] IEnumerable users ); //IObservable [Get("/users")] public IObservable GetUsersObservableHttpResponseMessage(); [Post("/users")] public IObservable PostUsersObservableHttpResponseMessage( [Body] IEnumerable users ); //Task<> - throws [Get("/users")] public Task> GetUsersTaskTAsync(); [Post("/users")] public Task> PostUsersTaskTAsync([Body] IEnumerable users); //Task> [Get("/users")] public Task>> GetUsersTaskApiResponseTAsync(); [Post("/users")] public Task>> PostUsersTaskApiResponseTAsync( [Body] IEnumerable users ); } public class User { public int Id { get; set; } public string Name { get; set; } public string Bio { get; set; } public int Followers { get; set; } public int Following { get; set; } public string Url { get; set; } } ================================================ FILE: Refit.Benchmarks/IPerformanceService.cs ================================================ namespace Refit.Benchmarks; public interface IPerformanceService { [Get("/users")] public Task ConstantRoute(); [Get("/users/{id}")] public Task DynamicRoute(int id); [Get("/users/{id}/{user}/{status}")] public Task ComplexDynamicRoute(int id, string user, string status); [Get("/users/{request.someProperty}")] public Task ObjectRequest(PathBoundObject request); [Post("/users/{id}/{request.someProperty}")] [Headers("User-Agent: Awesome Octocat App", "X-Emoji: :smile_cat:")] public Task ComplexRequest(int id, PathBoundObject request, [Query(CollectionFormat.Multi)]int[] queries); } public class PathBoundObject { public string SomeProperty { get; set; } [Query] public string SomeQuery { get; set; } } ================================================ FILE: Refit.Benchmarks/PerformanceBenchmark.cs ================================================ using System.Net; using BenchmarkDotNet.Attributes; namespace Refit.Benchmarks; [MemoryDiagnoser] public class PerformanceBenchmark { private IPerformanceService? service; private const string Host = "https://github.com"; private SystemTextJsonContentSerializer systemTextJsonContentSerializer; [GlobalSetup] public Task SetupAsync() { systemTextJsonContentSerializer = new SystemTextJsonContentSerializer(); service = RestService.For( Host, new RefitSettings(systemTextJsonContentSerializer) { HttpMessageHandlerFactory = () => new StaticValueHttpResponseHandler( "Ok", HttpStatusCode.OK ) } ); return Task.CompletedTask; } [Benchmark] public async Task ConstantRouteAsync() => await service.ConstantRoute(); [Benchmark] public async Task DynamicRouteAsync() => await service.DynamicRoute(101); [Benchmark] public async Task ComplexDynamicRouteAsync() => await service.ComplexDynamicRoute(101, "tom", "yCxv"); [Benchmark] public async Task ObjectRequestAsync() => await service.ObjectRequest(new PathBoundObject(){SomeProperty = "myProperty", SomeQuery = "myQuery"}); [Benchmark] public async Task ComplexRequestAsync() => await service.ComplexRequest(101, new PathBoundObject(){SomeProperty = "myProperty", SomeQuery = "myQuery"}, [1,2,3,4,5,6]); } ================================================ FILE: Refit.Benchmarks/Program.cs ================================================ using BenchmarkDotNet.Running; using Refit.Benchmarks; if (args is { Length: > 0 }) { BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } else { BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); } ================================================ FILE: Refit.Benchmarks/Refit.Benchmarks.csproj ================================================  Exe net8.0 false $(NoWarn);CS1591 PreserveNewest PreserveNewest ================================================ FILE: Refit.Benchmarks/SourceGeneratorBenchmark.cs ================================================ using System.Reflection; using BenchmarkDotNet.Attributes; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Refit.Generator; namespace Refit.Benchmarks; [MemoryDiagnoser] public class SourceGeneratorBenchmark { static readonly MetadataReference RefitAssembly = MetadataReference.CreateFromFile( typeof(GetAttribute).Assembly.Location, documentation: XmlDocumentationProvider.CreateFromFile( Path.ChangeExtension(typeof(GetAttribute).Assembly.Location, ".xml") ) ); static readonly Type[] ImportantAssemblies = { typeof(Binder), typeof(GetAttribute), typeof(Enumerable), typeof(Newtonsoft.Json.JsonConvert), typeof(HttpContent), typeof(Attribute) }; static Assembly[] AssemblyReferencesForCodegen => AppDomain.CurrentDomain .GetAssemblies() .Concat(ImportantAssemblies.Select(x=>x.Assembly)) .Distinct() .Where(a => !a.IsDynamic) .ToArray(); private Compilation compilation; private CSharpGeneratorDriver driver; private void Setup(string sourceText) { var references = new List(); var assemblies = AssemblyReferencesForCodegen; foreach (var assembly in assemblies) { if (!assembly.IsDynamic) { references.Add(MetadataReference.CreateFromFile(assembly.Location)); } } references.Add(RefitAssembly); compilation = CSharpCompilation.Create( "compilation", [CSharpSyntaxTree.ParseText(sourceText)], references, new CSharpCompilationOptions(OutputKind.ConsoleApplication) ); var generator = new InterfaceStubGeneratorV2().AsSourceGenerator(); driver = CSharpGeneratorDriver.Create(generator); } [GlobalSetup(Target = nameof(Compile))] public void SetupSmall() => Setup(SourceGeneratorBenchmarksProjects.SmallInterface); [Benchmark] public GeneratorDriver Compile() { return driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); } [GlobalSetup(Target = nameof(Cached))] public void SetupCached() { Setup(SourceGeneratorBenchmarksProjects.SmallInterface); driver = (CSharpGeneratorDriver)driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct MyValue {}")); } [Benchmark] public GeneratorDriver Cached() { return driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); } [GlobalSetup(Target = nameof(CompileMany))] public void SetupMany() => Setup(SourceGeneratorBenchmarksProjects.ManyInterfaces); [Benchmark] public GeneratorDriver CompileMany() { return driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); } [GlobalSetup(Target = nameof(CachedMany))] public void SetupCachedMany() { Setup(SourceGeneratorBenchmarksProjects.ManyInterfaces); driver = (CSharpGeneratorDriver)driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct MyValue {}")); } [Benchmark] public GeneratorDriver CachedMany() { return driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); } } ================================================ FILE: Refit.Benchmarks/SourceGeneratorBenchmarksProjects.cs ================================================ namespace Refit.Benchmarks; public static class SourceGeneratorBenchmarksProjects { #region SmallInterface public const string SmallInterface = """ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IReallyExcitingCrudApi where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } """; #endregion #region LargeInterface public const string ManyInterfaces = """ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IReallyExcitingCrudApi0 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi1 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi2 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi3 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi4 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi5 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi6 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi7 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi8 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi9 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi10 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi11 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi12 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi13 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi14 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi15 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi16 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi17 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi18 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi19 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi20 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi21 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi22 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi23 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi24 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi25 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi26 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi27 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi28 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi29 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi30 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi31 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi32 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi33 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi34 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi35 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi36 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi37 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi38 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi39 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi40 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi41 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi42 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi43 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi44 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi45 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi46 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi47 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi48 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi49 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi50 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi51 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi52 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi53 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi54 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi55 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi56 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi57 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi58 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi59 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi60 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi61 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi62 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi63 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi64 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi65 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi66 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi67 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi68 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi69 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi70 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi71 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi72 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi73 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi74 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi75 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi76 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi77 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi78 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi79 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi80 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi81 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi82 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi83 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi84 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi85 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi86 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi87 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi88 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi89 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi90 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi91 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi92 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi93 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi94 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi95 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi96 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi97 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi98 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface IReallyExcitingCrudApi99 where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); } """; #endregion } ================================================ FILE: Refit.Benchmarks/StartupBenchmark.cs ================================================ using System.Net; using BenchmarkDotNet.Attributes; namespace Refit.Benchmarks; [MemoryDiagnoser] public class StartupBenchmark { private IPerformanceService initialisedService; private const string Host = "https://github.com"; private readonly RefitSettings settings = new RefitSettings() { HttpMessageHandlerFactory = () => new StaticValueHttpResponseHandler( "Ok", HttpStatusCode.OK ) }; [IterationSetup(Targets = [nameof(FirstCallConstantRouteAsync), nameof(FirstCallComplexRequestAsync)])] public void Setup() { initialisedService = RestService.For(Host, settings); } [Benchmark] public IPerformanceService CreateService() => RestService.For(Host, settings); [Benchmark] public async Task FirstCallConstantRouteAsync() => await initialisedService.ConstantRoute(); [Benchmark] public async Task ConstantRouteAsync() { var service = RestService.For(Host, settings); return await service.ConstantRoute(); } [Benchmark] public async Task FirstCallComplexRequestAsync() => await initialisedService.ObjectRequest(new PathBoundObject(){SomeProperty = "myProperty", SomeQuery = "myQuery"}); [Benchmark] public async Task ComplexRequestAsync() { var service = RestService.For(Host, settings); return await service.ObjectRequest(new PathBoundObject(){SomeProperty = "myProperty", SomeQuery = "myQuery"}); } } ================================================ FILE: Refit.Benchmarks/StaticFileHttpResponseHandler.cs ================================================ using System.Net; namespace Refit.Benchmarks; public class StaticFileHttpResponseHandler : HttpMessageHandler { private readonly HttpStatusCode responseCode; private readonly string responsePayload; public StaticFileHttpResponseHandler(string fileName, HttpStatusCode responseCode) { if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException(nameof(fileName)); responsePayload = File.ReadAllText(fileName); ; this.responseCode = responseCode; } protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { return Task.FromResult( new HttpResponseMessage(responseCode) { RequestMessage = request, Content = new StringContent(responsePayload) } ); } } ================================================ FILE: Refit.Benchmarks/StaticValueHttpResponseHandler.cs ================================================ using System.Net; namespace Refit.Benchmarks; public class StaticValueHttpResponseHandler (string response, HttpStatusCode code) : HttpMessageHandler { protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { return Task.FromResult( new HttpResponseMessage(code) { RequestMessage = request, Content = new StringContent(response) } ); } } ================================================ FILE: Refit.Benchmarks/newtonsoft-json-10-users.json ================================================ [ { "Id": 253, "Name": "Namee3a23814-bfe9-4d4b-96db-8fc95d209ea8", "Bio": "Biof413d158-7ca7-4b1b-9073-565b3621bb83", "Followers": 154, "Following": 136, "Url": "Url70f46596-f86f-4e82-900d-0f07d7dc468c" }, { "Id": 122, "Name": "Namef5407028-0d39-41bf-86a3-94890dabff6c", "Bio": "Bioed5c52a9-37ed-4bc3-94fc-ab2de047ad31", "Followers": 48, "Following": 188, "Url": "Url7d04e0a5-63d8-42e9-996a-af1efeee6b3d" }, { "Id": 78, "Name": "Name3176c49e-d65d-44f6-89af-14a79aa6103e", "Bio": "Bioc901bb07-874a-449e-8cc7-892ea008de32", "Followers": 218, "Following": 229, "Url": "Urlf2a24e82-c01e-4ca5-86c7-4360dbffd955" }, { "Id": 169, "Name": "Namebf433061-810b-4e0e-b9a4-33690ba557a7", "Bio": "Bioe43d91cd-a399-401e-b126-a94f4f35ac97", "Followers": 167, "Following": 142, "Url": "Urlea793c4a-436f-4ba8-8f3b-e48d88f8ff16" }, { "Id": 173, "Name": "Name2ff320b0-4c21-4799-825f-2da0b00eecb7", "Bio": "Bio4d33933d-484c-468b-a433-7cbf5801795a", "Followers": 255, "Following": 198, "Url": "Url84860aea-c2d1-4b0d-8be3-9c961b06d08a" }, { "Id": 100, "Name": "Name13c3cb21-4a84-4c02-8fc5-30b2287c203d", "Bio": "Bio90233954-82e4-4e36-9152-3bc8e1ca37ab", "Followers": 128, "Following": 84, "Url": "Url126ac70f-429a-458e-8138-957883491938" }, { "Id": 172, "Name": "Name97594a03-d0a8-4711-8d9f-af2067da734c", "Bio": "Bio9feb6a20-49ae-480c-a819-a2655ef44a9b", "Followers": 41, "Following": 146, "Url": "Urldaa25203-cf3a-4744-b5b0-58505a602980" }, { "Id": 243, "Name": "Name74b11b10-f465-4217-9de4-d8d9884a4c99", "Bio": "Bio4733a1be-9b5f-44bc-ada1-2154265e3dec", "Followers": 120, "Following": 45, "Url": "Urleb4bb38d-ae61-4cc4-b9f7-111f2d6e06b8" }, { "Id": 106, "Name": "Name4c1a2b16-ebaf-4d5b-b262-a0b33562fc6b", "Bio": "Bioa9ff296d-921b-4f8f-95bb-5a569e0cb176", "Followers": 79, "Following": 133, "Url": "Url732335eb-f081-4842-940e-e130bb8a475b" }, { "Id": 245, "Name": "Name0c42a076-95d3-49b8-a2ea-711fc1569c19", "Bio": "Bio78b6cbf1-7595-4ed1-b530-f438ea6320ce", "Followers": 5, "Following": 212, "Url": "Urlc2a5296a-ea03-4b6a-bbc9-92e1e1e56d21" } ] ================================================ FILE: Refit.Benchmarks/system-text-json-10-users.json ================================================ [ { "id": 253, "name": "Namee3a23814-bfe9-4d4b-96db-8fc95d209ea8", "bio": "Biof413d158-7ca7-4b1b-9073-565b3621bb83", "followers": 154, "following": 136, "url": "Url70f46596-f86f-4e82-900d-0f07d7dc468c" }, { "id": 122, "name": "Namef5407028-0d39-41bf-86a3-94890dabff6c", "bio": "Bioed5c52a9-37ed-4bc3-94fc-ab2de047ad31", "followers": 48, "following": 188, "url": "Url7d04e0a5-63d8-42e9-996a-af1efeee6b3d" }, { "id": 78, "name": "Name3176c49e-d65d-44f6-89af-14a79aa6103e", "bio": "Bioc901bb07-874a-449e-8cc7-892ea008de32", "followers": 218, "following": 229, "url": "Urlf2a24e82-c01e-4ca5-86c7-4360dbffd955" }, { "id": 169, "name": "Namebf433061-810b-4e0e-b9a4-33690ba557a7", "bio": "Bioe43d91cd-a399-401e-b126-a94f4f35ac97", "followers": 167, "following": 142, "url": "Urlea793c4a-436f-4ba8-8f3b-e48d88f8ff16" }, { "id": 173, "name": "Name2ff320b0-4c21-4799-825f-2da0b00eecb7", "bio": "Bio4d33933d-484c-468b-a433-7cbf5801795a", "followers": 255, "following": 198, "url": "Url84860aea-c2d1-4b0d-8be3-9c961b06d08a" }, { "id": 100, "name": "Name13c3cb21-4a84-4c02-8fc5-30b2287c203d", "bio": "Bio90233954-82e4-4e36-9152-3bc8e1ca37ab", "followers": 128, "following": 84, "url": "Url126ac70f-429a-458e-8138-957883491938" }, { "id": 172, "name": "Name97594a03-d0a8-4711-8d9f-af2067da734c", "bio": "Bio9feb6a20-49ae-480c-a819-a2655ef44a9b", "followers": 41, "following": 146, "url": "Urldaa25203-cf3a-4744-b5b0-58505a602980" }, { "id": 243, "name": "Name74b11b10-f465-4217-9de4-d8d9884a4c99", "bio": "Bio4733a1be-9b5f-44bc-ada1-2154265e3dec", "followers": 120, "following": 45, "url": "Urleb4bb38d-ae61-4cc4-b9f7-111f2d6e06b8" }, { "id": 106, "name": "Name4c1a2b16-ebaf-4d5b-b262-a0b33562fc6b", "bio": "Bioa9ff296d-921b-4f8f-95bb-5a569e0cb176", "followers": 79, "following": 133, "url": "Url732335eb-f081-4842-940e-e130bb8a475b" }, { "id": 245, "name": "Name0c42a076-95d3-49b8-a2ea-711fc1569c19", "bio": "Bio78b6cbf1-7595-4ed1-b530-f438ea6320ce", "followers": 5, "following": 212, "url": "Urlc2a5296a-ea03-4b6a-bbc9-92e1e1e56d21" } ] ================================================ FILE: Refit.GeneratorTests/Fixture.cs ================================================ using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Refit.Generator; namespace Refit.GeneratorTests; public static class Fixture { static readonly MetadataReference RefitAssembly = MetadataReference.CreateFromFile( typeof(GetAttribute).Assembly.Location, documentation: XmlDocumentationProvider.CreateFromFile( Path.ChangeExtension(typeof(GetAttribute).Assembly.Location, ".xml") ) ); private static readonly Type[] ImportantAssemblies = { typeof(Binder), typeof(GetAttribute), typeof(System.Reactive.Unit), typeof(Enumerable), typeof(Newtonsoft.Json.JsonConvert), typeof(FactAttribute), typeof(HttpContent), typeof(Attribute) }; private static Assembly[] AssemblyReferencesForCodegen => AppDomain.CurrentDomain .GetAssemblies() .Concat(ImportantAssemblies.Select(x=>x.Assembly)) .Distinct() .Where(a => !a.IsDynamic) .ToArray(); public static Task VerifyForBody(string body, bool ignoreNonInterfaces = true) { var source = $$""" using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGeneratedClient { {{body}} } """; return VerifyGenerator(source, ignoreNonInterfaces); } public static Task VerifyForType(string declarations) { var source = $$""" using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; {{declarations}} """; return VerifyGenerator(source); } public static Task VerifyForDeclaration(string declarations) { var source = $$""" using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; {{declarations}} """; return VerifyGenerator(source); } public static CSharpCompilation CreateLibrary(params SyntaxTree[] source) { var references = new List(); var assemblies = AssemblyReferencesForCodegen; foreach (var assembly in assemblies) { if (!assembly.IsDynamic) { references.Add(MetadataReference.CreateFromFile(assembly.Location)); } } references.Add(RefitAssembly); var compilation = CSharpCompilation.Create( "compilation", source, references, new CSharpCompilationOptions(OutputKind.ConsoleApplication) ); return compilation; } private static CSharpCompilation CreateLibrary(params string[] source) { return CreateLibrary(source.Select(s => CSharpSyntaxTree.ParseText(s)).ToArray()); } private static Task VerifyGenerator(string source, bool ignoreNonInterfaces = true) { var compilation = CreateLibrary(source); var generator = new InterfaceStubGeneratorV2(); var driver = CSharpGeneratorDriver.Create(generator); var ranDriver = driver.RunGenerators(compilation); var settings = new VerifySettings(); if (ignoreNonInterfaces) { settings.IgnoreGeneratedResult(x => x.HintName.Contains("PreserveAttribute.g.cs", StringComparison.Ordinal)); settings.IgnoreGeneratedResult(x => x.HintName.Contains("Generated.g.cs", StringComparison.Ordinal)); } var verify = Verify(ranDriver, settings); return verify.ToTask(); } } ================================================ FILE: Refit.GeneratorTests/GeneratedTest.cs ================================================ namespace Refit.GeneratorTests; public class GeneratedTest { [Fact] public Task ShouldEmitAllFiles() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get(); """, false); } [Fact] public Task ShouldNotEmitFilesWhenNoRefitInterfaces() { // Refit should not generate any code when no valid Refit interfaces are present. return Fixture.VerifyForBody("", false); } } ================================================ FILE: Refit.GeneratorTests/Incremental/FunctionTest.cs ================================================ using Microsoft.CodeAnalysis.CSharp; namespace Refit.GeneratorTests.Incremental; public class FunctionTest { private const string DefaultInterface = """ #nullable enabled using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; private const string ReturnValueInterface = """ #nullable enabled using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; [Fact] public void ModifyParameterNameDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change parameter name var newInterface = """ public interface IGitHubApi { [Get("/users/{myUser}")] Task GetUser(string myUser); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void ModifyParameterTypeDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change parameter type var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(int user); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void ModifyParameterNullabilityDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change parameter nullability var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string? user); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void AddParameterDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // add parameter var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user, [Query] int myParam); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void ModifyReturnTypeDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change return type var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void ModifyReturnObjectNullabilityDoesNotRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change return nullability var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Cached); } [Fact] public void ModifyReturnValueNullabilityDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change return nullability var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void AddNonRefitMethodDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // change parameter name var newInterface = """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); void NonRefitMethod(); } """; var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Modified); } } ================================================ FILE: Refit.GeneratorTests/Incremental/GenericTest.cs ================================================ using Microsoft.CodeAnalysis.CSharp; namespace Refit.GeneratorTests.Incremental; public class GenericTest { private const string GenericInterface = """ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGeneratedInterface { [Get("/users")] Task Get(); } """; [Fact] public void RenameGenericTypeDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(GenericInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // rename generic type var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGeneratedInterface", """ public interface IGeneratedInterface { [Get("/users")] Task Get(); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void AddGenericConstraintDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(GenericInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // add generic constraint var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGeneratedInterface", """ public interface IGeneratedInterface where T1 : class { [Get("/users")] Task Get(); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); // add new generic constraint var compilation3 = TestHelper.ReplaceMemberDeclaration( compilation2, "IGeneratedInterface", """ public interface IGeneratedInterface where T1 : class, new() { [Get("/users")] Task Get(); } """ ); var driver3 = driver2.RunGenerators(compilation3); TestHelper.AssertRunReasons(driver3, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void AddObjectGenericConstraintDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(GenericInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // add object generic constraint var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGeneratedInterface", """ public interface IGeneratedInterface where T1 : IDisposable { [Get("/users")] Task Get(); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void AddGenericTypeDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(GenericInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // add second generic type var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGeneratedInterface", """ public interface IGeneratedInterface { [Get("/users")] Task Get(); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } } ================================================ FILE: Refit.GeneratorTests/Incremental/IncrementalGeneratorRunReasons.cs ================================================ using Microsoft.CodeAnalysis; namespace Refit.GeneratorTests.Incremental; internal record IncrementalGeneratorRunReasons( IncrementalStepRunReason BuildRefitStep, IncrementalStepRunReason ReportDiagnosticsStep ) { public static readonly IncrementalGeneratorRunReasons New = new(IncrementalStepRunReason.New, IncrementalStepRunReason.New); public static readonly IncrementalGeneratorRunReasons Cached = new( // compilation step should always be modified as each time a new compilation is passed IncrementalStepRunReason.Cached, IncrementalStepRunReason.Unchanged ); public static readonly IncrementalGeneratorRunReasons Modified = Cached with { ReportDiagnosticsStep = IncrementalStepRunReason.Modified, BuildRefitStep = IncrementalStepRunReason.Modified, }; public static readonly IncrementalGeneratorRunReasons ModifiedSource = Cached with { ReportDiagnosticsStep = IncrementalStepRunReason.Unchanged, BuildRefitStep = IncrementalStepRunReason.Modified, }; } ================================================ FILE: Refit.GeneratorTests/Incremental/IncrementalTest.cs ================================================ using Microsoft.CodeAnalysis.CSharp; namespace Refit.GeneratorTests.Incremental; public class IncrementalTest { private const string DefaultInterface = """ #nullable enabled using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; [Fact] public void AddUnrelatedTypeDoesntRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); var compilation2 = compilation1.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct MyValue {}")); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Cached); } [Fact] public void SmallChangeDoesntRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // update syntax tree by replacing interface with itself var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGitHubApi", """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Cached); } [Fact] public void AddNewMemberDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // add unrelated member, don't change the method var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGitHubApi", """ public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); private record Temp(); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } } ================================================ FILE: Refit.GeneratorTests/Incremental/InheritanceTest.cs ================================================ using Microsoft.CodeAnalysis.CSharp; namespace Refit.GeneratorTests.Incremental; public class InheritanceTest { private const string DefaultInterface = """ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } """; private const string TwoInterface = """ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; namespace RefitGeneratorTest; public interface IGitHubApi { [Get("/users/{user}")] Task GetUser(string user); } public interface IBaseInterface { void NonRefitMethod(); } """; [Fact] public void InheritFromIDisposableDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(DefaultInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // inherit from IDisposable var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGitHubApi", """ public interface IGitHubApi : IDisposable { [Get("/users/{user}")] Task GetUser(string user); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); } [Fact] public void InheritFromInterfaceDoesRegenerate() { var syntaxTree = CSharpSyntaxTree.ParseText(TwoInterface, CSharpParseOptions.Default); var compilation1 = Fixture.CreateLibrary(syntaxTree); var driver1 = TestHelper.GenerateTracked(compilation1); TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); // inherit from second interface var compilation2 = TestHelper.ReplaceMemberDeclaration( compilation1, "IGitHubApi", """ public interface IGitHubApi : IBaseInterface { [Get("/users/{user}")] Task GetUser(string user); } """ ); var driver2 = driver1.RunGenerators(compilation2); TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Modified); } } ================================================ FILE: Refit.GeneratorTests/Incremental/TestHelper.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Refit.Generator; namespace Refit.GeneratorTests.Incremental; internal static class TestHelper { private static readonly GeneratorDriverOptions EnableIncrementalTrackingDriverOptions = new(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true); internal static GeneratorDriver GenerateTracked(Compilation compilation) { var generator = new InterfaceStubGeneratorV2(); var driver = CSharpGeneratorDriver.Create( new[] { generator.AsSourceGenerator() }, driverOptions: EnableIncrementalTrackingDriverOptions ); return driver.RunGenerators(compilation); } internal static CSharpCompilation ReplaceMemberDeclaration( CSharpCompilation compilation, string memberName, string newMember ) { var syntaxTree = compilation.SyntaxTrees.Single(); var memberDeclaration = syntaxTree .GetCompilationUnitRoot() .DescendantNodes() .OfType() .Single(x => x.Identifier.Text == memberName); var updatedMemberDeclaration = SyntaxFactory.ParseMemberDeclaration(newMember)!; var newRoot = syntaxTree.GetCompilationUnitRoot().ReplaceNode(memberDeclaration, updatedMemberDeclaration); var newTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options); return compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), newTree); } internal static CSharpCompilation ReplaceLocalDeclaration( CSharpCompilation compilation, string variableName, string newDeclaration ) { var syntaxTree = compilation.SyntaxTrees.Single(); var memberDeclaration = syntaxTree .GetCompilationUnitRoot() .DescendantNodes() .OfType() .Single(x => x.Declaration.Variables.Any(x => x.Identifier.ToString() == variableName)); var updatedMemberDeclaration = SyntaxFactory.ParseStatement(newDeclaration)!; var newRoot = syntaxTree.GetCompilationUnitRoot().ReplaceNode(memberDeclaration, updatedMemberDeclaration); var newTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options); return compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), newTree); } internal static void AssertRunReasons( GeneratorDriver driver, IncrementalGeneratorRunReasons reasons, int outputIndex = 0 ) { var runResult = driver.GetRunResult().Results[0]; AssertRunReason( runResult, RefitGeneratorStepName.ReportDiagnostics, reasons.ReportDiagnosticsStep, outputIndex ); AssertRunReason(runResult, RefitGeneratorStepName.BuildRefit, reasons.BuildRefitStep, outputIndex); } private static void AssertRunReason( GeneratorRunResult runResult, string stepName, IncrementalStepRunReason expectedStepReason, int outputIndex ) { var actualStepReason = runResult .TrackedSteps[stepName] .SelectMany(x => x.Outputs) .ElementAt(outputIndex) .Reason; Assert.Equal(expectedStepReason, actualStepReason); } } internal static class RefitGeneratorStepName { public const string ReportDiagnostics = "ReportDiagnostics"; public const string BuildRefit = "BuildRefit"; } ================================================ FILE: Refit.GeneratorTests/InterfaceTests.cs ================================================ namespace Refit.GeneratorTests; public class InterfaceTests { [Fact] public Task ContainedInterfaceTest() { return Fixture.VerifyForType( """ public class ContainerType { public interface IContainedInterface { [Get("/users")] Task Get(); } } """); } [Fact] public Task RefitInterfaceDerivedFromRefitBaseTest() { return Fixture.VerifyForType( """ public interface IGeneratedInterface : IBaseInterface { [Get("/users")] Task Get(); } public interface IBaseInterface { [Get("/posts")] Task GetPosts(); } """); } [Fact] public Task RefitInterfaceDerivedFromBaseTest() { return Fixture.VerifyForType( """ public interface IGeneratedInterface : IBaseInterface { [Get("/users")] Task Get(); } public interface IBaseInterface { void NonRefitMethod(); } """); } [Fact] public Task InterfaceDerivedFromRefitBaseTest() { return Fixture.VerifyForType( """ public interface IBaseInterface { [Get("/users")] Task Get(); } public interface IDerivedInterface : IBaseInterface { } """); } [Fact] public Task DefaultInterfaceMethod() { return Fixture.VerifyForType( """ public interface IGeneratedInterface { [Get("/users")] Task Get(); void Default() {{ Console.WriteLine("Default"); }} } """); } [Fact] public Task DerivedDefaultInterfaceMethod() { return Fixture.VerifyForType( """ public interface IGeneratedInterface : IBaseInterface { [Get("/users")] Task Get(); } public interface IBaseInterface { [Get("/posts")] Task GetPosts(); void Default() {{ Console.WriteLine("Default"); }} } """); } [Fact] public Task InterfacesWithDifferentCasing() { return Fixture.VerifyForType( """ public interface IApi { [Get("/users")] Task Get(); } public interface Iapi { [Get("/users")] Task Get(); } """); } [Fact] public Task InterfacesWithDifferentSignature() { return Fixture.VerifyForType( """ public interface IApi { [Get("/users")] Task Get(); } public interface IApi { [Get("/users")] Task Get(); } """); } [Fact] public Task NestedNamespaceTest() { return Fixture.VerifyForDeclaration( """ namespace Nested.RefitGeneratorTest; public interface IGeneratedInterface { [Get("/users")] Task Get(); } """); } [Fact] public Task GlobalNamespaceTest() { return Fixture.VerifyForDeclaration( """ public interface IGeneratedInterface { [Get("/users")] Task Get(); } """); } [Fact] public Task DisposableTest() { return Fixture.VerifyForDeclaration( """ public interface IGeneratedInterface : IDisposable { [Get("/users")] Task Get(); } """); } [Fact] public Task NonRefitMethodShouldRaiseDiagnostic() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get(); void NonRefitMethod(); """); } [Fact] public Task InterfaceWithGenericConstraint() { return Fixture.VerifyForDeclaration( """ public interface IGeneratedInterface where T1 : class where T2 : unmanaged where T3 : struct where T4 : notnull where T5 : class, IDisposable, new() { [Get("/users")] Task Get(); } """); } } ================================================ FILE: Refit.GeneratorTests/MethodTests.cs ================================================ namespace Refit.GeneratorTests; public class MethodTests { [Fact] public Task MethodsWithGenericConstraints() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get() where T1 : class where T2 : unmanaged where T3 : struct where T4 : notnull where T5 : class, IDisposable, new(); void NonRefitMethod() where T1 : class where T2 : unmanaged where T3 : struct where T4 : notnull where T5 : class, IDisposable, new(); """); } } ================================================ FILE: Refit.GeneratorTests/ModuleInitializer.cs ================================================ using System.Runtime.CompilerServices; using VerifyTests.DiffPlex; namespace Refit.GeneratorTests; public static class ModuleInitializer { // ModuleInitializer should only be used in apps #pragma warning disable CA2255 [ModuleInitializer] #pragma warning restore CA2255 public static void Init() { DerivePathInfo((file, _, type, method) => new(Path.Combine(Path.GetDirectoryName(file), "_snapshots"), type.Name, method.Name)); VerifySourceGenerators.Initialize(); VerifyDiffPlex.Initialize(OutputType.Compact); } } ================================================ FILE: Refit.GeneratorTests/ParameterTests.cs ================================================ namespace Refit.GeneratorTests; public class ParameterTests { [Fact] public Task RouteParameter() { return Fixture.VerifyForBody( """ [Get("/users/{user}")] Task Get(string user); """); } [Fact] public Task NullableRouteParameter() { return Fixture.VerifyForBody( """ [Get("/users/{user}")] Task Get(string? user); """); } [Fact] public Task ValueTypeRouteParameter() { return Fixture.VerifyForBody( """ [Get("/users/{user}")] Task Get(int user); """); } [Fact] public Task NullableValueTypeRouteParameter() { return Fixture.VerifyForBody( """ [Get("/users/{user}")] Task Get(int? user); """); } } ================================================ FILE: Refit.GeneratorTests/Refit.GeneratorTests.csproj ================================================  net8.0;net9.0 enable enable false true $(NoWarn);CS1591;CA1819;CA2000;CA2007;CA1056;CA1707;CA1861;xUnit1031 %(RecursiveDir)\resources\%(Filename)%(Extension) Always ================================================ FILE: Refit.GeneratorTests/ReturnTypeTests.cs ================================================ namespace Refit.GeneratorTests; public class ReturnTypeTests { [Fact] public Task GenericTaskShouldWork() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get(); """); } [Fact] public Task ReturnNullableObject() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get(); """); } [Fact] public Task ReturnNullableValueType() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get(); """); } [Fact] public Task VoidTaskShouldWork() { return Fixture.VerifyForBody( """ [Post("/users")] Task Post(); """); } [Fact] public Task GenericConstraintReturnTask() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get() where T : class, IDisposable, new(); """); } [Fact] public Task GenericUnmanagedConstraintReturnTask() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get() where T : unmanaged; """); } [Fact] public Task GenericStructConstraintReturnTask() { return Fixture.VerifyForBody( """ [Get("/users")] Task Get() where T : struct """); } [Fact] public Task ReturnIObservable() { return Fixture.VerifyForBody( """ [Get("/users/{user}")] IObservable GetUser(string user); """); } [Fact] public Task ReturnUnsupportedType() { return Fixture.VerifyForBody( """ [Get("/users/{user}")] string GetUser(string user); """); } } ================================================ FILE: Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#Generated.g.verified.cs ================================================ //HintName: Generated.g.cs #pragma warning disable namespace Refit.Implementation { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] internal static partial class Generated { #if NET5_0_OR_GREATER [System.Runtime.CompilerServices.ModuleInitializer] [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(global::Refit.Implementation.Generated))] public static void Initialize() { } #endif } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#PreserveAttribute.g.verified.cs ================================================ //HintName: PreserveAttribute.g.cs #pragma warning disable namespace RefitInternalGenerated { [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.AttributeUsage (global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Enum | global::System.AttributeTargets.Constructor | global::System.AttributeTargets.Method | global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Event | global::System.AttributeTargets.Interface | global::System.AttributeTargets.Delegate)] sealed class PreserveAttribute : global::System.Attribute { // // Fields // public bool AllMembers; public bool Conditional; } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.ContainedInterfaceTest#IContainedInterface.g.verified.cs ================================================ //HintName: IContainedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestContainerTypeIContainedInterface : global::RefitGeneratorTest.ContainerType.IContainedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestContainerTypeIContainedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.DefaultInterfaceMethod#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedInterface : global::RefitGeneratorTest.IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.DerivedDefaultInterfaceMethod#IBaseInterface.g.verified.cs ================================================ //HintName: IBaseInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIBaseInterface : global::RefitGeneratorTest.IBaseInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIBaseInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task GetPosts() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetPosts", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.DerivedDefaultInterfaceMethod#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedInterface : global::RefitGeneratorTest.IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// async global::System.Threading.Tasks.Task global::RefitGeneratorTest.IBaseInterface.GetPosts() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetPosts", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.DisposableTest#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class IGeneratedInterface : global::IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public IGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// void global::System.IDisposable.Dispose() { Client?.Dispose(); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.GlobalNamespaceTest#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class IGeneratedInterface : global::IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public IGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfaceDerivedFromRefitBaseTest#IBaseInterface.g.verified.cs ================================================ //HintName: IBaseInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIBaseInterface : global::RefitGeneratorTest.IBaseInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIBaseInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfaceDerivedFromRefitBaseTest#IDerivedInterface.g.verified.cs ================================================ //HintName: IDerivedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIDerivedInterface : global::RefitGeneratorTest.IDerivedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIDerivedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// async global::System.Threading.Tasks.Task global::RefitGeneratorTest.IBaseInterface.Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfaceWithGenericConstraint#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class IGeneratedInterface : global::IGeneratedInterface where T1 : class where T2 : unmanaged where T3 : struct where T4 : notnull where T5 : class, global::System.IDisposable, new() { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public IGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfacesWithDifferentCasing#IApi.g.verified.cs ================================================ //HintName: IApi.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIApi : global::RefitGeneratorTest.IApi { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIApi(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfacesWithDifferentCasing#Iapi1.g.verified.cs ================================================ //HintName: Iapi1.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIapi : global::RefitGeneratorTest.Iapi { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIapi(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfacesWithDifferentSignature#IApi.g.verified.cs ================================================ //HintName: IApi.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIApi : global::RefitGeneratorTest.IApi { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIApi(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.InterfacesWithDifferentSignature#IApi1.g.verified.cs ================================================ //HintName: IApi1.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIApi : global::RefitGeneratorTest.IApi { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIApi(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.NestedNamespaceTest#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class NestedRefitGeneratorTestIGeneratedInterface : global::Nested.RefitGeneratorTest.IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public NestedRefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.NonRefitMethodShouldRaiseDiagnostic#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public void NonRefitMethod() { throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.NonRefitMethodShouldRaiseDiagnostic.verified.txt ================================================ { Diagnostics: [ { Location: /* void NonRefitMethod(); ^^^^^^^^^^^^^^ } */ : (16,5)-(16,19), Message: Method IGeneratedClient.NonRefitMethod either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument, Severity: Warning, WarningLevel: 1, Descriptor: { Id: RF001, Title: Refit types must have Refit HTTP method attributes, MessageFormat: Method {0}.{1} either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument, Category: Refit, DefaultSeverity: Warning, IsEnabledByDefault: true } } ] } ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromBaseTest#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedInterface : global::RefitGeneratorTest.IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// void global::RefitGeneratorTest.IBaseInterface.NonRefitMethod() { throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromBaseTest.verified.txt ================================================ { Diagnostics: [ { Location: /* { void NonRefitMethod(); ^^^^^^^^^^^^^^ } */ : (19,9)-(19,23), Message: Method IBaseInterface.NonRefitMethod either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument, Severity: Warning, WarningLevel: 1, Descriptor: { Id: RF001, Title: Refit types must have Refit HTTP method attributes, MessageFormat: Method {0}.{1} either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument, Category: Refit, DefaultSeverity: Warning, IsEnabledByDefault: true } } ] } ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromRefitBaseTest#IBaseInterface.g.verified.cs ================================================ //HintName: IBaseInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIBaseInterface : global::RefitGeneratorTest.IBaseInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIBaseInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task GetPosts() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetPosts", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromRefitBaseTest#IGeneratedInterface.g.verified.cs ================================================ //HintName: IGeneratedInterface.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedInterface : global::RefitGeneratorTest.IGeneratedInterface { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// async global::System.Threading.Tasks.Task global::RefitGeneratorTest.IBaseInterface.GetPosts() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetPosts", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/MethodTests.MethodsWithGenericConstraints#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() where T1 : class where T2 : unmanaged where T3 : struct where T4 : notnull where T5 : class, global::System.IDisposable, new() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty(), new global::System.Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) } ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public void NonRefitMethod() where T1 : class where T2 : unmanaged where T3 : struct where T4 : notnull where T5 : class, global::System.IDisposable, new() { throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/MethodTests.MethodsWithGenericConstraints.verified.txt ================================================ { Diagnostics: [ { Location: /* void NonRefitMethod() ^^^^^^^^^^^^^^ where T1 : class */ : (21,5)-(21,19), Message: Method IGeneratedClient.NonRefitMethod either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument, Severity: Warning, WarningLevel: 1, Descriptor: { Id: RF001, Title: Refit types must have Refit HTTP method attributes, MessageFormat: Method {0}.{1} either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument, Category: Refit, DefaultSeverity: Warning, IsEnabledByDefault: true } } ] } ================================================ FILE: Refit.GeneratorTests/_snapshots/ParameterTests.NullableRouteParameter#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task Get(string? @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", ______typeParameters ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ParameterTests.NullableValueTypeRouteParameter#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(int?) }; /// public async global::System.Threading.Tasks.Task Get(int? @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", ______typeParameters ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ParameterTests.RouteParameter#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task Get(string @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", ______typeParameters ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ParameterTests.ValueTypeRouteParameter#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(int) }; /// public async global::System.Threading.Tasks.Task Get(int @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", ______typeParameters ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.GenericConstraintReturnTask#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() where T : class, global::System.IDisposable, new() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty(), new global::System.Type[] { typeof(T) } ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.GenericStructConstraintReturnTask#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() where T : struct { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty(), new global::System.Type[] { typeof(T) } ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.GenericTaskShouldWork#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.GenericUnmanagedConstraintReturnTask#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() where T : unmanaged { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty(), new global::System.Type[] { typeof(T) } ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.ReturnIObservable#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable GetUser(string @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUser", ______typeParameters ); return (global::System.IObservable)______func(this.Client, ______arguments); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.ReturnNullableObject#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.ReturnNullableValueType#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Get() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Get", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.ReturnUnsupportedType#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(string) }; /// public string GetUser(string @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUser", ______typeParameters ); return (string)______func(this.Client, ______arguments); } } } } #pragma warning restore ================================================ FILE: Refit.GeneratorTests/_snapshots/ReturnTypeTests.VoidTaskShouldWork#IGeneratedClient.g.verified.cs ================================================ //HintName: IGeneratedClient.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitGeneratorTestIGeneratedClient : global::RefitGeneratorTest.IGeneratedClient { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task Post() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("Post", global::System.Array.Empty() ); await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.HttpClientFactory/HttpClientFactoryCore.cs ================================================  using System; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; namespace Refit { /// /// HttpClientFactoryExtensions /// internal static class HttpClientFactoryCore { internal static IHttpClientBuilder AddRefitClientCore( IServiceCollection services, Type refitInterfaceType, Func? settings, string? httpClientName ) { if (services == null) throw new ArgumentNullException(nameof(services)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); // register settings var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType); services.AddSingleton( settingsType, provider => Activator.CreateInstance( typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, settings?.Invoke(provider) )! ); // register RequestBuilder var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType); services.AddSingleton( requestBuilderType, provider => RequestBuilderGenericForTypeMethod .MakeGenericMethod(refitInterfaceType) .Invoke( null, [((ISettingsFor)provider.GetRequiredService(settingsType)).Settings] )! ); // create HttpClientBuilder var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType)); // configure message handler builder.ConfigureHttpMessageHandlerBuilder(builderConfig => { var handler = CreateInnerHandlerIfProvided( ((ISettingsFor)builderConfig.Services.GetRequiredService(settingsType)).Settings ); if (handler != null) { builderConfig.PrimaryHandler = handler; } }); // add typed client (register transient that resolves HttpClient from IHttpClientFactory and creates Refit client) builder.Services.AddTransient( refitInterfaceType, s => { var httpClientFactory = s.GetRequiredService(); var httpClient = httpClientFactory.CreateClient(builder.Name); return RestService.For( refitInterfaceType, httpClient, (IRequestBuilder)s.GetRequiredService(requestBuilderType) ); } ); return builder; } internal static IHttpClientBuilder AddKeyedRefitClientCore( IServiceCollection services, Type refitInterfaceType, object? serviceKey, Func? settings, string? httpClientName ) { if (services == null) throw new ArgumentNullException(nameof(services)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); // register settings var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType); services.AddKeyedSingleton( settingsType, serviceKey, (provider, _) => Activator.CreateInstance( typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, settings?.Invoke(provider) )! ); // register RequestBuilder var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType); services.AddKeyedSingleton( requestBuilderType, serviceKey, (provider, _) => RequestBuilderGenericForTypeMethod .MakeGenericMethod(refitInterfaceType) .Invoke( null, [((ISettingsFor)provider.GetRequiredKeyedService(settingsType, serviceKey)).Settings] )! ); // create HttpClientBuilder var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType, serviceKey)); // configure primary handler builder.ConfigurePrimaryHttpMessageHandler(serviceProvider => { var settingsInstance = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey); return settingsInstance.Settings?.HttpMessageHandlerFactory?.Invoke() ?? new HttpClientHandler(); }); // configure additional handlers builder.ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) => { var settingsInstance = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey); if (settingsInstance.Settings?.AuthorizationHeaderValueGetter is { } getToken) { handlers.Add(new AuthenticatedHttpClientHandler(null, getToken)); } }); // add keyed typed client (register keyed transient that resolves HttpClient and creates Refit client) builder.Services.AddKeyedTransient( refitInterfaceType, serviceKey, (s, _) => { var httpClientFactory = s.GetRequiredService(); var httpClient = httpClientFactory.CreateClient(builder.Name); return RestService.For( refitInterfaceType, httpClient, (IRequestBuilder)s.GetRequiredKeyedService(requestBuilderType, serviceKey) ); } ); return builder; } internal static IHttpClientBuilder AddRefitClientCore( IServiceCollection services, Type refitInterfaceType, Func? settings, string? httpClientName ) where T : class { if (services == null) throw new ArgumentNullException(nameof(services)); // register settings services.AddSingleton(provider => new SettingsFor(settings?.Invoke(provider))); // register RequestBuilder services.AddSingleton(provider => RequestBuilder.ForType(provider.GetRequiredService>().Settings) ); // create HttpClientBuilder var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType()); // configure message handler builder.ConfigureHttpMessageHandlerBuilder(builderConfig => { var handler = CreateInnerHandlerIfProvided( builderConfig.Services.GetRequiredService>().Settings ); if (handler != null) { builderConfig.PrimaryHandler = handler; } }); // add typed client using framework AddTypedClient return builder.AddTypedClient((client, serviceProvider) => RestService.For( client, serviceProvider.GetRequiredService>() ) ); } internal static IHttpClientBuilder AddKeyedRefitClientCore( IServiceCollection services, Type refitInterfaceType, object? serviceKey, Func? settings, string? httpClientName ) where T : class { if (services == null) throw new ArgumentNullException(nameof(services)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); // register settings services.AddKeyedSingleton( serviceKey, (provider, _) => new SettingsFor(settings?.Invoke(provider)) ); // register RequestBuilder services.AddKeyedSingleton( serviceKey, (provider, _) => RequestBuilder.ForType( provider.GetRequiredKeyedService>(serviceKey).Settings ) ); // create HttpClientBuilder var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(serviceKey)); // configure primary handler builder.ConfigurePrimaryHttpMessageHandler(serviceProvider => { var settingsInstance = serviceProvider.GetRequiredKeyedService>(serviceKey).Settings; return settingsInstance?.HttpMessageHandlerFactory?.Invoke() ?? new HttpClientHandler(); }); // configure additional handlers builder.ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) => { var settingsInstance = serviceProvider.GetRequiredKeyedService>(serviceKey).Settings; if (settingsInstance?.AuthorizationHeaderValueGetter is { } getToken) { handlers.Add(new AuthenticatedHttpClientHandler(null, getToken)); } }); // add keyed typed client (inline keyed registration) builder.Services.AddKeyedTransient( serviceKey, (s, _) => { var httpClientFactory = s.GetRequiredService(); var httpClient = httpClientFactory.CreateClient(builder.Name); return RestService.For( httpClient, s.GetRequiredKeyedService>(serviceKey) ); } ); return builder; } // helper - used by AddRefitClientCore and AddRefitClientCore private static HttpMessageHandler? CreateInnerHandlerIfProvided(RefitSettings? settings) { HttpMessageHandler? innerHandler = null; if (settings != null) { if (settings.HttpMessageHandlerFactory != null) { innerHandler = settings.HttpMessageHandlerFactory(); } if (settings.AuthorizationHeaderValueGetter != null) { innerHandler = new AuthenticatedHttpClientHandler( settings.AuthorizationHeaderValueGetter, innerHandler ); } } return innerHandler; } private static readonly MethodInfo RequestBuilderGenericForTypeMethod = typeof(RequestBuilder) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(z => z.IsGenericMethodDefinition && z.GetParameters().Length == 1); } } ================================================ FILE: Refit.HttpClientFactory/HttpClientFactoryExtensions.cs ================================================ using System; using System.Linq; using System.Net.Http; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; namespace Refit { /// /// HttpClientFactoryExtensions. /// public static class HttpClientFactoryExtensions { /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// container /// Optional. Settings to configure the instance with /// Optional. Allows users to change the HttpClient name. /// public static IHttpClientBuilder AddRefitClient( this IServiceCollection services, Type refitInterfaceType, RefitSettings? settings = null, string? httpClientName = null ) { if (services == null) throw new ArgumentNullException(nameof(services)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); return HttpClientFactoryCore.AddRefitClientCore(services, refitInterfaceType, _ => settings, httpClientName); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// container /// An optional key to associate with the specific Refit client instance /// Optional. Settings to configure the instance with /// Optional. Allows users to change the HttpClient name. /// public static IHttpClientBuilder AddKeyedRefitClient( this IServiceCollection services, Type refitInterfaceType, object? serviceKey, RefitSettings? settings = null, string? httpClientName = null ) { if (services == null) throw new ArgumentNullException(nameof(services)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(services, refitInterfaceType, serviceKey, _ => settings, httpClientName); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// container /// Optional. Settings to configure the instance with /// Optional. Allows users to change the HttpClient name. /// public static IHttpClientBuilder AddRefitClient( this IServiceCollection services, RefitSettings? settings = null, string? httpClientName = null ) where T : class { if (services == null) throw new ArgumentNullException(nameof(services)); return HttpClientFactoryCore.AddRefitClientCore(services, typeof(T), _ => settings, httpClientName); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// container /// An optional key to associate with the specific Refit client instance /// Optional. Settings to configure the instance with /// Optional. Allows users to change the HttpClient name. /// public static IHttpClientBuilder AddKeyedRefitClient( this IServiceCollection services, object? serviceKey, RefitSettings? settings = null, string? httpClientName = null ) where T : class { if (services == null) throw new ArgumentNullException(nameof(services)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(services, typeof(T), serviceKey, _ => settings, httpClientName); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// The HTTP client builder /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddRefitClient( this IHttpClientBuilder builder, Type refitInterfaceType, RefitSettings? settings = null ) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); return HttpClientFactoryCore.AddRefitClientCore(builder.Services, refitInterfaceType, _ => settings, builder.Name); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// The HTTP client builder /// An optional key to associate with the specific Refit client instance /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddKeyedRefitClient( this IHttpClientBuilder builder, Type refitInterfaceType, object? serviceKey, RefitSettings? settings = null ) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, refitInterfaceType, serviceKey, _ => settings, builder.Name); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// The HTTP client builder /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddRefitClient( this IHttpClientBuilder builder, RefitSettings? settings = null ) where T : class { if (builder == null) throw new ArgumentNullException(nameof(builder)); return HttpClientFactoryCore.AddRefitClientCore(builder.Services, typeof(T), _ => settings, builder.Name); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// The HTTP client builder /// An optional key to associate with the specific Refit client instance /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddKeyedRefitClient( this IHttpClientBuilder builder, object? serviceKey, RefitSettings? settings = null ) where T : class { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, typeof(T), serviceKey, _ => settings, builder.Name); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// container /// Optional. Action to configure refit settings. /// Optional. Allows users to change the HttpClient name. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddRefitClient( this IServiceCollection services, Type refitInterfaceType, Func? settingsAction, string? httpClientName = null ) { if (services == null) throw new ArgumentNullException(nameof(services)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); return HttpClientFactoryCore.AddRefitClientCore(services, refitInterfaceType, settingsAction, httpClientName); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// container /// An optional key to associate with the specific Refit client instance /// Optional. Action to configure refit settings. /// Optional. Allows users to change the HttpClient name. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddKeyedRefitClient( this IServiceCollection services, Type refitInterfaceType, object? serviceKey, Func? settingsAction, string? httpClientName = null ) { if (services == null) throw new ArgumentNullException(nameof(services)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(services, refitInterfaceType, serviceKey, settingsAction, httpClientName); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// container /// Optional. Action to configure refit settings. /// Optional. Allows users to change the HttpClient name. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddRefitClient( this IServiceCollection services, Func? settingsAction, string? httpClientName = null ) where T : class { if (services == null) throw new ArgumentNullException(nameof(services)); return HttpClientFactoryCore.AddRefitClientCore(services, typeof(T), settingsAction, httpClientName); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// container /// An optional key to associate with the specific Refit client instance /// Optional. Action to configure refit settings. /// Optional. Allows users to change the HttpClient name. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddKeyedRefitClient( this IServiceCollection services, object? serviceKey, Func? settingsAction, string? httpClientName = null ) where T : class { if (services == null) throw new ArgumentNullException(nameof(services)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(services, typeof(T), serviceKey, settingsAction, httpClientName); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// The HTTP client builder /// Optional. Action to configure refit settings. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddRefitClient( this IHttpClientBuilder builder, Type refitInterfaceType, Func? settingsAction ) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); return HttpClientFactoryCore.AddRefitClientCore(builder.Services, refitInterfaceType, settingsAction, builder.Name); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// The HTTP client builder /// An optional key to associate with the specific Refit client instance /// Optional. Action to configure refit settings. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddKeyedRefitClient( this IHttpClientBuilder builder, Type refitInterfaceType, object? serviceKey, Func? settingsAction ) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, refitInterfaceType, serviceKey, settingsAction, builder.Name); } /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// The HTTP client builder /// Optional. Action to configure refit settings. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddRefitClient( this IHttpClientBuilder builder, Func? settingsAction ) where T : class { if (builder == null) throw new ArgumentNullException(nameof(builder)); return HttpClientFactoryCore.AddRefitClientCore(builder.Services, typeof(T), settingsAction, builder.Name); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// The HTTP client builder /// An optional key to associate with the specific Refit client instance /// Optional. Action to configure refit settings. /// #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif public static IHttpClientBuilder AddKeyedRefitClient( this IHttpClientBuilder builder, object? serviceKey, Func? settingsAction ) where T : class { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, typeof(T), serviceKey, settingsAction, builder.Name); } } } ================================================ FILE: Refit.HttpClientFactory/HttpClientFactoryExtensions.tt ================================================ <#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> using System; using System.Linq; using System.Net.Http; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; namespace Refit { /// /// HttpClientFactoryExtensions. /// public static class HttpClientFactoryExtensions { <# // 位运算控制组合 for (int bits = 0; bits < 16; bits++) { bool hasKeyed = (bits & 1) != 0; // 0001: 是否有Keyed bool hasGeneric = (bits & 2) != 0; // 0010: 是否泛型 bool isBuilder = (bits & 4) != 0; // 0100: 是否builder扩展 bool hasAction = (bits & 8) != 0; // 1000: 是否有settingsAction // 生成XML注释 #> /// <# if (hasKeyed) { #> /// Adds a Refit client to the DI container with a specified service key <# } else { #> /// Adds a Refit client to the DI container <# } #> /// <# if (hasGeneric) { #> /// Type of the Refit interface <# } else { #> /// Type of the Refit interface <# } #> <# if (isBuilder) { #> /// The HTTP client builder <# } else { #> /// container <# } #> <# if (hasKeyed) { #> /// An optional key to associate with the specific Refit client instance <# } #> <# if (hasAction) { #> /// Optional. Action to configure refit settings. <# } else { #> /// Optional. Settings to configure the instance with <# } #> <# if (!isBuilder) { #> /// Optional. Allows users to change the HttpClient name. <# } #> /// <# if(hasAction) { #> #if NET9_0_OR_GREATER [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] #endif <# } #> <# // 生成方法签名 string methodName = (hasKeyed ? "AddKeyedRefitClient" : "AddRefitClient"); #> public static IHttpClientBuilder <#= methodName #><#= hasGeneric ? "" : "" #>( this <#= isBuilder ? "IHttpClientBuilder" : "IServiceCollection" #> <#= isBuilder ? "builder" : "services" #>, <# if (!hasGeneric) { #> Type refitInterfaceType, <# } #> <# if (hasKeyed) { #> object? serviceKey, <# } #> <# if (hasAction) { #> Func? settingsAction<#= !isBuilder ? "," : "" #> <# } else { #> RefitSettings? settings = null<#= !isBuilder ? "," : "" #> <# } #> <# if (!isBuilder) { #> string? httpClientName = null <# } #> ) <# if (hasGeneric) { #> where T : class <# } #> { <# // 生成参数验证 if (isBuilder) { #> if (builder == null) throw new ArgumentNullException(nameof(builder)); <# } else { #> if (services == null) throw new ArgumentNullException(nameof(services)); <# } if (!hasGeneric) { #> if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); <# } if (hasKeyed) { #> if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); <# } #> <# // 生成Core方法调用 string coreMethod = "HttpClientFactoryCore."; coreMethod += (hasKeyed ? "AddKeyedRefitClient" : "AddRefitClient") + "Core"; coreMethod += hasGeneric ? "" : ""; coreMethod += "("; // 第一个参数:services if (isBuilder) { coreMethod += "builder.Services"; } else { coreMethod += "services"; } // 第二个参数:Type if (hasGeneric) { coreMethod += ", typeof(T)"; } else { coreMethod += ", refitInterfaceType"; } // 第三个参数:serviceKey if (hasKeyed) { coreMethod += ", serviceKey"; } // 第四个参数:settingsAction if (hasAction) { coreMethod += ", settingsAction"; } else { coreMethod += ", _ => settings"; } // 第五个参数:httpClientName if (isBuilder) { // IHttpClientBuilder 版本使用 builder.Name coreMethod += ", builder.Name"; } else { // IServiceCollection 版本使用传入的 httpClientName coreMethod += ", httpClientName"; } coreMethod += ")"; #> return <#= coreMethod #>; } <# } #> } } ================================================ FILE: Refit.HttpClientFactory/Refit.HttpClientFactory.csproj ================================================  Refit HTTP Client Factory Extensions Refit HTTP Client Factory Extensions $(RefitTargets) enable true TextTemplatingFileGenerator HttpClientFactoryExtensions.cs True True HttpClientFactoryExtensions.tt ================================================ FILE: Refit.HttpClientFactory/SettingsFor.cs ================================================ namespace Refit { /// /// ISettingsFor /// public interface ISettingsFor { /// /// Gets the settings. /// /// /// The settings. /// RefitSettings? Settings { get; } } /// /// SettingsFor. /// /// /// /// /// Initializes a new instance of the class. /// /// The settings. public class SettingsFor(RefitSettings? settings) : ISettingsFor { /// /// Gets the settings. /// /// /// The settings. /// public RefitSettings? Settings { get; } = settings; } } ================================================ FILE: Refit.Newtonsoft.Json/NewtonsoftJsonContentSerializer.cs ================================================ using System.Net.Http; using System.Reflection; using System.Text; using Newtonsoft.Json; namespace Refit { /// /// A implementing using the Newtonsoft.Json APIs /// /// /// Creates a new instance with the specified parameters /// /// The serialization settings to use for the current instance public sealed class NewtonsoftJsonContentSerializer( JsonSerializerSettings? jsonSerializerSettings ) : IHttpContentSerializer { /// /// The instance providing the JSON serialization settings to use /// readonly Lazy jsonSerializerSettings = new( () => jsonSerializerSettings ?? JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings() ); /// /// Creates a new instance /// public NewtonsoftJsonContentSerializer() : this(null) { } /// public HttpContent ToHttpContent(T item) { var content = new StringContent( JsonConvert.SerializeObject(item, jsonSerializerSettings.Value), Encoding.UTF8, "application/json" ); return content; } /// public async Task FromHttpContentAsync( HttpContent content, CancellationToken cancellationToken = default ) { if (content == null) { return default; } var serializer = JsonSerializer.Create(jsonSerializerSettings.Value); var json = await content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); using var reader = new StringReader(json); using var jsonTextReader = new JsonTextReader(reader); return serializer.Deserialize(jsonTextReader); } /// /// Calculates what the field name should be for the given property. This may be affected by custom attributes the serializer understands /// /// A PropertyInfo object. /// /// The calculated field name. /// /// propertyInfo public string? GetFieldNameForProperty(PropertyInfo propertyInfo) { return propertyInfo switch { null => throw new ArgumentNullException(nameof(propertyInfo)), _ => propertyInfo .GetCustomAttributes(true) .Select(a => a.PropertyName) .FirstOrDefault() }; } } } ================================================ FILE: Refit.Newtonsoft.Json/Refit.Newtonsoft.Json.csproj ================================================  Refit Serializer for Newtonsoft.Json ($(TargetFramework)) Refit Serializers for Newtonsoft.Json $(RefitTargets) true Refit enable true ================================================ FILE: Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt ================================================ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.HttpClientFactory")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Newtonsoft.Json")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Xml")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName=".NET 8.0")] namespace Refit { [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class AliasAsAttribute : System.Attribute { public AliasAsAttribute(string name) { } public string Name { get; protected set; } } [System.Serializable] public class ApiException : System.Exception { protected ApiException(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } protected ApiException(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public string? Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] public bool HasContent { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } public System.Net.Http.HttpMethod HttpMethod { get; } public string? ReasonPhrase { get; } public Refit.RefitSettings RefitSettings { get; } public System.Net.Http.HttpRequestMessage RequestMessage { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Uri? Uri { get; } public System.Threading.Tasks.Task GetContentAsAsync() { } public static System.Threading.Tasks.Task Create(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public static System.Threading.Tasks.Task Create(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } } public sealed class ApiResponse : Refit.IApiResponse, Refit.IApiResponse, System.IDisposable { public ApiResponse(System.Net.Http.HttpResponseMessage response, T? content, Refit.RefitSettings settings, Refit.ApiException? error = null) { } public T Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + "herit from Refit.MultipartItem", false)] public class AttachmentNameAttribute : System.Attribute { public AttachmentNameAttribute(string name) { } public string Name { get; protected set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class AuthorizeAttribute : System.Attribute { public AuthorizeAttribute(string scheme = "Bearer") { } public string Scheme { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class BodyAttribute : System.Attribute { public BodyAttribute() { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod = 0) { } public BodyAttribute(bool buffered) { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod, bool buffered) { } public bool? Buffered { get; } public Refit.BodySerializationMethod SerializationMethod { get; } } public enum BodySerializationMethod { Default = 0, [System.Obsolete("Use BodySerializationMethod.Serialized instead", false)] Json = 1, UrlEncoded = 2, Serialized = 3, } public class ByteArrayPart : Refit.MultipartItem { public ByteArrayPart(byte[] value, string fileName, string? contentType = null, string? name = null) { } public byte[] Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public class CamelCaseUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public CamelCaseUrlParameterKeyFormatter() { } public string Format(string key) { } } public enum CollectionFormat { RefitParameterFormatter = 0, Csv = 1, Ssv = 2, Tsv = 3, Pipes = 4, Multi = 5, } public class DefaultApiExceptionFactory { public DefaultApiExceptionFactory(Refit.RefitSettings refitSettings) { } public System.Threading.Tasks.Task CreateAsync(System.Net.Http.HttpResponseMessage responseMessage) { } } public class DefaultFormUrlEncodedParameterFormatter : Refit.IFormUrlEncodedParameterFormatter { public DefaultFormUrlEncodedParameterFormatter() { } public virtual string? Format(object? parameterValue, string? formatString) { } } public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter { public DefaultUrlParameterFormatter() { } public void AddFormat(string format) { } public void AddFormat(string format) { } public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { } } public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public DefaultUrlParameterKeyFormatter() { } public virtual string Format(string key) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class DeleteAttribute : Refit.HttpMethodAttribute { public DeleteAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class FileInfoPart : Refit.MultipartItem { public FileInfoPart(System.IO.FileInfo value, string fileName, string? contentType = null, string? name = null) { } public System.IO.FileInfo Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class GetAttribute : Refit.HttpMethodAttribute { public GetAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class HeadAttribute : Refit.HttpMethodAttribute { public HeadAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderAttribute : System.Attribute { public HeaderAttribute(string header) { } public string Header { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderCollectionAttribute : System.Attribute { public HeaderCollectionAttribute() { } } [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Interface)] public class HeadersAttribute : System.Attribute { public HeadersAttribute(params string[] headers) { } public string[] Headers { get; } } public abstract class HttpMethodAttribute : System.Attribute { protected HttpMethodAttribute(string path) { } public abstract System.Net.Http.HttpMethod Method { get; } public virtual string Path { get; protected set; } } public static class HttpRequestMessageOptions { public static string InterfaceType { get; } public static string RestMethodInfo { get; } } public interface IApiResponse : System.IDisposable { System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } Refit.ApiException? Error { get; } System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } System.Version Version { get; } } public interface IApiResponse : Refit.IApiResponse, System.IDisposable { T Content { get; } new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { string? Format(object? value, string? formatString); } public interface IHttpContentSerializer { System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default); string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo); System.Net.Http.HttpContent ToHttpContent(T item); } public interface IRequestBuilder { System.Func BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null); } public interface IRequestBuilder : Refit.IRequestBuilder { } public interface IUrlParameterFormatter { string? Format(object? value, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type); } public interface IUrlParameterKeyFormatter { string Format(string key); } [System.Obsolete("Use NewtonsoftJsonContentSerializer in the Refit.Newtonsoft.Json package instead", true)] public class JsonContentSerializer : Refit.IHttpContentSerializer { public JsonContentSerializer() { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class MultipartAttribute : System.Attribute { public MultipartAttribute(string boundaryText = "----MyGreatBoundary") { } public string BoundaryText { get; } } public abstract class MultipartItem { protected MultipartItem(string fileName, string? contentType) { } public MultipartItem(string fileName, string? contentType, string? name) { } public string? ContentType { get; } public string FileName { get; } public string? Name { get; } protected abstract System.Net.Http.HttpContent CreateContent(); public System.Net.Http.HttpContent ToContent() { } } public class ObjectToInferredTypesConverter : System.Text.Json.Serialization.JsonConverter { public ObjectToInferredTypesConverter() { } public override object? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { } public override void Write(System.Text.Json.Utf8JsonWriter writer, object objectToWrite, System.Text.Json.JsonSerializerOptions options) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class OptionsAttribute : Refit.HttpMethodAttribute { public OptionsAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public enum ParameterType { Normal = 0, RoundTripping = 1, } [System.AttributeUsage(System.AttributeTargets.Method)] public class PatchAttribute : Refit.HttpMethodAttribute { public PatchAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PostAttribute : Refit.HttpMethodAttribute { public PostAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class ProblemDetails { public ProblemDetails() { } public string? Detail { get; set; } public System.Collections.Generic.Dictionary Errors { get; set; } [System.Text.Json.Serialization.JsonExtensionData] public System.Collections.Generic.IDictionary Extensions { get; set; } public string? Instance { get; set; } public int Status { get; set; } public string? Title { get; set; } public string? Type { get; set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class PropertyAttribute : System.Attribute { public PropertyAttribute() { } public PropertyAttribute(string key) { } public string? Key { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PutAttribute : Refit.HttpMethodAttribute { public PutAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class QueryAttribute : System.Attribute { public QueryAttribute() { } public QueryAttribute(Refit.CollectionFormat collectionFormat) { } public QueryAttribute(string delimiter) { } public QueryAttribute(string delimiter, string prefix) { } public QueryAttribute(string delimiter, string prefix, string format) { } public Refit.CollectionFormat CollectionFormat { get; set; } public string Delimiter { get; protected set; } public string? Format { get; set; } public bool IsCollectionFormatSpecified { get; } public string? Prefix { get; protected set; } public bool TreatAsString { get; set; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class QueryUriFormatAttribute : System.Attribute { public QueryUriFormatAttribute(System.UriFormat uriFormat) { } public System.UriFormat UriFormat { get; } } public class RefitSettings { public RefitSettings() { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter) { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter = null, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter = null, Refit.IUrlParameterKeyFormatter? urlParameterKeyFormatter = null) { } public System.Func>? AuthorizationHeaderValueGetter { get; set; } public bool Buffered { get; set; } public Refit.CollectionFormat CollectionFormat { get; set; } public Refit.IHttpContentSerializer ContentSerializer { get; set; } public System.Func>? DeserializationExceptionFactory { get; set; } public System.Func> ExceptionFactory { get; set; } public Refit.IFormUrlEncodedParameterFormatter FormUrlEncodedParameterFormatter { get; set; } public System.Func? HttpMessageHandlerFactory { get; set; } public System.Collections.Generic.Dictionary? HttpRequestMessageOptions { get; set; } public Refit.IUrlParameterFormatter UrlParameterFormatter { get; set; } public Refit.IUrlParameterKeyFormatter UrlParameterKeyFormatter { get; set; } public System.Version Version { get; set; } public System.Net.Http.HttpVersionPolicy VersionPolicy { get; set; } } public static class RequestBuilder { public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType) { } public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType, Refit.RefitSettings? settings) { } public static Refit.IRequestBuilder ForType() { } public static Refit.IRequestBuilder ForType(Refit.RefitSettings? settings) { } } public class RestMethodInfo : System.IEquatable { public RestMethodInfo(string Name, System.Type HostingType, System.Reflection.MethodInfo MethodInfo, string RelativePath, System.Type ReturnType) { } public System.Type HostingType { get; init; } public System.Reflection.MethodInfo MethodInfo { get; init; } public string Name { get; init; } public string RelativePath { get; init; } public System.Type ReturnType { get; init; } } public class RestMethodParameterInfo { public RestMethodParameterInfo(bool isObjectPropertyParameter, System.Reflection.ParameterInfo parameterInfo) { } public RestMethodParameterInfo(string name, System.Reflection.ParameterInfo parameterInfo) { } public bool IsObjectPropertyParameter { get; set; } public string? Name { get; set; } public System.Reflection.ParameterInfo ParameterInfo { get; set; } public System.Collections.Generic.List ParameterProperties { get; set; } public Refit.ParameterType Type { get; set; } } public class RestMethodParameterProperty { public RestMethodParameterProperty(string name, System.Reflection.PropertyInfo propertyInfo) { } public string Name { get; set; } public System.Reflection.PropertyInfo PropertyInfo { get; set; } } public static class RestService { public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { } public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client) { } public static object For(System.Type refitInterfaceType, string hostUrl) { } public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static object For(System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { } public static T For(System.Net.Http.HttpClient client) { } public static T For(string hostUrl) { } public static T For(System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static T For(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static T For(string hostUrl, Refit.RefitSettings? settings) { } } public class StreamPart : Refit.MultipartItem { public StreamPart(System.IO.Stream value, string fileName, string? contentType = null, string? name = null) { } public System.IO.Stream Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public sealed class SystemTextJsonContentSerializer : Refit.IHttpContentSerializer { public SystemTextJsonContentSerializer() { } public SystemTextJsonContentSerializer(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } public static System.Text.Json.JsonSerializerOptions GetDefaultJsonSerializerOptions() { } } [System.Serializable] public class ValidationApiException : Refit.ApiException { public new Refit.ProblemDetails? Content { get; } public static Refit.ValidationApiException Create(Refit.ApiException exception) { } } } ================================================ FILE: Refit.Tests/API/ApiApprovalTests.Refit.DotNet9_0.verified.txt ================================================ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.HttpClientFactory")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Newtonsoft.Json")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Xml")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] namespace Refit { [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class AliasAsAttribute : System.Attribute { public AliasAsAttribute(string name) { } public string Name { get; protected set; } } [System.Serializable] public class ApiException : System.Exception { protected ApiException(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } protected ApiException(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public string? Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] public bool HasContent { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } public System.Net.Http.HttpMethod HttpMethod { get; } public string? ReasonPhrase { get; } public Refit.RefitSettings RefitSettings { get; } public System.Net.Http.HttpRequestMessage RequestMessage { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Uri? Uri { get; } public System.Threading.Tasks.Task GetContentAsAsync() { } public static System.Threading.Tasks.Task Create(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public static System.Threading.Tasks.Task Create(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } } public sealed class ApiResponse : Refit.IApiResponse, Refit.IApiResponse, System.IDisposable { public ApiResponse(System.Net.Http.HttpResponseMessage response, T? content, Refit.RefitSettings settings, Refit.ApiException? error = null) { } public T Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + "herit from Refit.MultipartItem", false)] public class AttachmentNameAttribute : System.Attribute { public AttachmentNameAttribute(string name) { } public string Name { get; protected set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class AuthorizeAttribute : System.Attribute { public AuthorizeAttribute(string scheme = "Bearer") { } public string Scheme { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class BodyAttribute : System.Attribute { public BodyAttribute() { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod = 0) { } public BodyAttribute(bool buffered) { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod, bool buffered) { } public bool? Buffered { get; } public Refit.BodySerializationMethod SerializationMethod { get; } } public enum BodySerializationMethod { Default = 0, [System.Obsolete("Use BodySerializationMethod.Serialized instead", false)] Json = 1, UrlEncoded = 2, Serialized = 3, } public class ByteArrayPart : Refit.MultipartItem { public ByteArrayPart(byte[] value, string fileName, string? contentType = null, string? name = null) { } public byte[] Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public class CamelCaseUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public CamelCaseUrlParameterKeyFormatter() { } public string Format(string key) { } } public enum CollectionFormat { RefitParameterFormatter = 0, Csv = 1, Ssv = 2, Tsv = 3, Pipes = 4, Multi = 5, } public class DefaultApiExceptionFactory { public DefaultApiExceptionFactory(Refit.RefitSettings refitSettings) { } public System.Threading.Tasks.Task CreateAsync(System.Net.Http.HttpResponseMessage responseMessage) { } } public class DefaultFormUrlEncodedParameterFormatter : Refit.IFormUrlEncodedParameterFormatter { public DefaultFormUrlEncodedParameterFormatter() { } public virtual string? Format(object? parameterValue, string? formatString) { } } public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter { public DefaultUrlParameterFormatter() { } public void AddFormat(string format) { } public void AddFormat(string format) { } public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { } } public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public DefaultUrlParameterKeyFormatter() { } public virtual string Format(string key) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class DeleteAttribute : Refit.HttpMethodAttribute { public DeleteAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class FileInfoPart : Refit.MultipartItem { public FileInfoPart(System.IO.FileInfo value, string fileName, string? contentType = null, string? name = null) { } public System.IO.FileInfo Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class GetAttribute : Refit.HttpMethodAttribute { public GetAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class HeadAttribute : Refit.HttpMethodAttribute { public HeadAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderAttribute : System.Attribute { public HeaderAttribute(string header) { } public string Header { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderCollectionAttribute : System.Attribute { public HeaderCollectionAttribute() { } } [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Interface)] public class HeadersAttribute : System.Attribute { public HeadersAttribute(params string[] headers) { } public string[] Headers { get; } } public abstract class HttpMethodAttribute : System.Attribute { protected HttpMethodAttribute(string path) { } public abstract System.Net.Http.HttpMethod Method { get; } public virtual string Path { get; protected set; } } public static class HttpRequestMessageOptions { public static string InterfaceType { get; } public static string RestMethodInfo { get; } } public interface IApiResponse : System.IDisposable { System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } Refit.ApiException? Error { get; } System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } System.Version Version { get; } } public interface IApiResponse : Refit.IApiResponse, System.IDisposable { T Content { get; } new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { string? Format(object? value, string? formatString); } public interface IHttpContentSerializer { System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default); string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo); System.Net.Http.HttpContent ToHttpContent(T item); } public interface IRequestBuilder { System.Func BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null); } public interface IRequestBuilder : Refit.IRequestBuilder { } public interface IUrlParameterFormatter { string? Format(object? value, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type); } public interface IUrlParameterKeyFormatter { string Format(string key); } [System.Obsolete("Use NewtonsoftJsonContentSerializer in the Refit.Newtonsoft.Json package instead", true)] public class JsonContentSerializer : Refit.IHttpContentSerializer { public JsonContentSerializer() { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class MultipartAttribute : System.Attribute { public MultipartAttribute(string boundaryText = "----MyGreatBoundary") { } public string BoundaryText { get; } } public abstract class MultipartItem { protected MultipartItem(string fileName, string? contentType) { } public MultipartItem(string fileName, string? contentType, string? name) { } public string? ContentType { get; } public string FileName { get; } public string? Name { get; } protected abstract System.Net.Http.HttpContent CreateContent(); public System.Net.Http.HttpContent ToContent() { } } public class ObjectToInferredTypesConverter : System.Text.Json.Serialization.JsonConverter { public ObjectToInferredTypesConverter() { } public override object? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { } public override void Write(System.Text.Json.Utf8JsonWriter writer, object objectToWrite, System.Text.Json.JsonSerializerOptions options) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class OptionsAttribute : Refit.HttpMethodAttribute { public OptionsAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public enum ParameterType { Normal = 0, RoundTripping = 1, } [System.AttributeUsage(System.AttributeTargets.Method)] public class PatchAttribute : Refit.HttpMethodAttribute { public PatchAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PostAttribute : Refit.HttpMethodAttribute { public PostAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class ProblemDetails { public ProblemDetails() { } public string? Detail { get; set; } public System.Collections.Generic.Dictionary Errors { get; set; } [System.Text.Json.Serialization.JsonExtensionData] public System.Collections.Generic.IDictionary Extensions { get; set; } public string? Instance { get; set; } public int Status { get; set; } public string? Title { get; set; } public string? Type { get; set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class PropertyAttribute : System.Attribute { public PropertyAttribute() { } public PropertyAttribute(string key) { } public string? Key { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PutAttribute : Refit.HttpMethodAttribute { public PutAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class QueryAttribute : System.Attribute { public QueryAttribute() { } public QueryAttribute(Refit.CollectionFormat collectionFormat) { } public QueryAttribute(string delimiter) { } public QueryAttribute(string delimiter, string prefix) { } public QueryAttribute(string delimiter, string prefix, string format) { } public Refit.CollectionFormat CollectionFormat { get; set; } public string Delimiter { get; protected set; } public string? Format { get; set; } public bool IsCollectionFormatSpecified { get; } public string? Prefix { get; protected set; } public bool TreatAsString { get; set; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class QueryUriFormatAttribute : System.Attribute { public QueryUriFormatAttribute(System.UriFormat uriFormat) { } public System.UriFormat UriFormat { get; } } public class RefitSettings { public RefitSettings() { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter) { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter = null, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter = null, Refit.IUrlParameterKeyFormatter? urlParameterKeyFormatter = null) { } public System.Func>? AuthorizationHeaderValueGetter { get; set; } public bool Buffered { get; set; } public Refit.CollectionFormat CollectionFormat { get; set; } public Refit.IHttpContentSerializer ContentSerializer { get; set; } public System.Func>? DeserializationExceptionFactory { get; set; } public System.Func> ExceptionFactory { get; set; } public Refit.IFormUrlEncodedParameterFormatter FormUrlEncodedParameterFormatter { get; set; } public System.Func? HttpMessageHandlerFactory { get; set; } public System.Collections.Generic.Dictionary? HttpRequestMessageOptions { get; set; } public Refit.IUrlParameterFormatter UrlParameterFormatter { get; set; } public Refit.IUrlParameterKeyFormatter UrlParameterKeyFormatter { get; set; } public System.Version Version { get; set; } public System.Net.Http.HttpVersionPolicy VersionPolicy { get; set; } } public static class RequestBuilder { public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType) { } public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType, Refit.RefitSettings? settings) { } public static Refit.IRequestBuilder ForType() { } public static Refit.IRequestBuilder ForType(Refit.RefitSettings? settings) { } } public class RestMethodInfo : System.IEquatable { public RestMethodInfo(string Name, System.Type HostingType, System.Reflection.MethodInfo MethodInfo, string RelativePath, System.Type ReturnType) { } public System.Type HostingType { get; init; } public System.Reflection.MethodInfo MethodInfo { get; init; } public string Name { get; init; } public string RelativePath { get; init; } public System.Type ReturnType { get; init; } } public class RestMethodParameterInfo { public RestMethodParameterInfo(bool isObjectPropertyParameter, System.Reflection.ParameterInfo parameterInfo) { } public RestMethodParameterInfo(string name, System.Reflection.ParameterInfo parameterInfo) { } public bool IsObjectPropertyParameter { get; set; } public string? Name { get; set; } public System.Reflection.ParameterInfo ParameterInfo { get; set; } public System.Collections.Generic.List ParameterProperties { get; set; } public Refit.ParameterType Type { get; set; } } public class RestMethodParameterProperty { public RestMethodParameterProperty(string name, System.Reflection.PropertyInfo propertyInfo) { } public string Name { get; set; } public System.Reflection.PropertyInfo PropertyInfo { get; set; } } public static class RestService { public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { } public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client) { } public static object For(System.Type refitInterfaceType, string hostUrl) { } public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static object For(System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { } public static T For(System.Net.Http.HttpClient client) { } public static T For(string hostUrl) { } public static T For(System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static T For(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static T For(string hostUrl, Refit.RefitSettings? settings) { } } public class StreamPart : Refit.MultipartItem { public StreamPart(System.IO.Stream value, string fileName, string? contentType = null, string? name = null) { } public System.IO.Stream Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public sealed class SystemTextJsonContentSerializer : Refit.IHttpContentSerializer { public SystemTextJsonContentSerializer() { } public SystemTextJsonContentSerializer(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } public static System.Text.Json.JsonSerializerOptions GetDefaultJsonSerializerOptions() { } } [System.Serializable] public class ValidationApiException : Refit.ApiException { public new Refit.ProblemDetails? Content { get; } public static Refit.ValidationApiException Create(Refit.ApiException exception) { } } } ================================================ FILE: Refit.Tests/API/ApiApprovalTests.cs ================================================ // Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved. // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. #if !NET48 using System.Diagnostics.CodeAnalysis; namespace Refit.Tests.API; /// /// Checks to make sure that the API is consistent with previous releases, and new API changes are highlighted. /// [ExcludeFromCodeCoverage] public class ApiApprovalTests { /// /// Generates public API for the ReactiveUI API. /// /// A task to monitor the process. [Fact] public Task Refit() => typeof(ApiResponse).Assembly.CheckApproval(["Refit"]); } #endif ================================================ FILE: Refit.Tests/API/ApiExtensions.cs ================================================ // Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved. // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. #if !NET48 using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using PublicApiGenerator; using VerifyXunit; namespace Refit.Tests; /// /// A helper for doing API approvals. /// [ExcludeFromCodeCoverage] public static class ApiExtensions { /// /// Checks to make sure the API is approved. /// /// The assembly that is being checked. /// The namespaces. /// The caller file path. /// /// A Task. /// public static async Task CheckApproval(this Assembly assembly, string[] namespaces, [CallerFilePath] string filePath = "") { var generatorOptions = new ApiGeneratorOptions { AllowNamespacePrefixes = namespaces }; var apiText = assembly.GeneratePublicApi(generatorOptions); var result = await Verifier.Verify(apiText, null, filePath) .UniqueForRuntimeAndVersion() .ScrubEmptyLines() .ScrubLines(l => l.StartsWith("[assembly: AssemblyVersion(", StringComparison.InvariantCulture) || l.StartsWith("[assembly: AssemblyFileVersion(", StringComparison.InvariantCulture) || l.StartsWith("[assembly: AssemblyInformationalVersion(", StringComparison.InvariantCulture) || l.StartsWith("[assembly: System.Reflection.AssemblyMetadata(", StringComparison.InvariantCulture)); } } #endif ================================================ FILE: Refit.Tests/API/_snapshots/ApiApprovalTests.Refit.DotNet10_0.verified.txt ================================================ [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.HttpClientFactory")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Newtonsoft.Json")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Xml")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")] namespace Refit { [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class AliasAsAttribute : System.Attribute { public AliasAsAttribute(string name) { } public string Name { get; protected set; } } [System.Serializable] public class ApiException : System.Exception { protected ApiException(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } protected ApiException(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public string? Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] public bool HasContent { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } public System.Net.Http.HttpMethod HttpMethod { get; } public string? ReasonPhrase { get; } public Refit.RefitSettings RefitSettings { get; } public System.Net.Http.HttpRequestMessage RequestMessage { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Uri? Uri { get; } public System.Threading.Tasks.Task GetContentAsAsync() { } public static System.Threading.Tasks.Task Create(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public static System.Threading.Tasks.Task Create(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } } public sealed class ApiResponse : Refit.IApiResponse, Refit.IApiResponse, System.IDisposable { public ApiResponse(System.Net.Http.HttpResponseMessage response, T? content, Refit.RefitSettings settings, Refit.ApiException? error = null) { } public T Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + "herit from Refit.MultipartItem", false)] public class AttachmentNameAttribute : System.Attribute { public AttachmentNameAttribute(string name) { } public string Name { get; protected set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class AuthorizeAttribute : System.Attribute { public AuthorizeAttribute(string scheme = "Bearer") { } public string Scheme { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class BodyAttribute : System.Attribute { public BodyAttribute() { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod = 0) { } public BodyAttribute(bool buffered) { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod, bool buffered) { } public bool? Buffered { get; } public Refit.BodySerializationMethod SerializationMethod { get; } } public enum BodySerializationMethod { Default = 0, [System.Obsolete("Use BodySerializationMethod.Serialized instead", false)] Json = 1, UrlEncoded = 2, Serialized = 3, } public class ByteArrayPart : Refit.MultipartItem { public ByteArrayPart(byte[] value, string fileName, string? contentType = null, string? name = null) { } public byte[] Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public class CamelCaseUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public CamelCaseUrlParameterKeyFormatter() { } public string Format(string key) { } } public enum CollectionFormat { RefitParameterFormatter = 0, Csv = 1, Ssv = 2, Tsv = 3, Pipes = 4, Multi = 5, } public class DefaultApiExceptionFactory { public DefaultApiExceptionFactory(Refit.RefitSettings refitSettings) { } public System.Threading.Tasks.Task CreateAsync(System.Net.Http.HttpResponseMessage responseMessage) { } } public class DefaultFormUrlEncodedParameterFormatter : Refit.IFormUrlEncodedParameterFormatter { public DefaultFormUrlEncodedParameterFormatter() { } public virtual string? Format(object? parameterValue, string? formatString) { } } public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter { public DefaultUrlParameterFormatter() { } public void AddFormat(string format) { } public void AddFormat(string format) { } public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { } } public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public DefaultUrlParameterKeyFormatter() { } public virtual string Format(string key) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class DeleteAttribute : Refit.HttpMethodAttribute { public DeleteAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class FileInfoPart : Refit.MultipartItem { public FileInfoPart(System.IO.FileInfo value, string fileName, string? contentType = null, string? name = null) { } public System.IO.FileInfo Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class GetAttribute : Refit.HttpMethodAttribute { public GetAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class HeadAttribute : Refit.HttpMethodAttribute { public HeadAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderAttribute : System.Attribute { public HeaderAttribute(string header) { } public string Header { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderCollectionAttribute : System.Attribute { public HeaderCollectionAttribute() { } } [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Interface)] public class HeadersAttribute : System.Attribute { public HeadersAttribute(params string[] headers) { } public string[] Headers { get; } } public abstract class HttpMethodAttribute : System.Attribute { protected HttpMethodAttribute(string path) { } public abstract System.Net.Http.HttpMethod Method { get; } public virtual string Path { get; protected set; } } public static class HttpRequestMessageOptions { public static string InterfaceType { get; } public static string RestMethodInfo { get; } } public interface IApiResponse : System.IDisposable { System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } Refit.ApiException? Error { get; } System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } System.Version Version { get; } } public interface IApiResponse : Refit.IApiResponse, System.IDisposable { T Content { get; } new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { string? Format(object? value, string? formatString); } public interface IHttpContentSerializer { System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default); string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo); System.Net.Http.HttpContent ToHttpContent(T item); } public interface IRequestBuilder { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] System.Func BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null); } public interface IRequestBuilder : Refit.IRequestBuilder { } public interface IUrlParameterFormatter { string? Format(object? value, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type); } public interface IUrlParameterKeyFormatter { string Format(string key); } [System.Obsolete("Use NewtonsoftJsonContentSerializer in the Refit.Newtonsoft.Json package instead", true)] public class JsonContentSerializer : Refit.IHttpContentSerializer { public JsonContentSerializer() { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class MultipartAttribute : System.Attribute { public MultipartAttribute(string boundaryText = "----MyGreatBoundary") { } public string BoundaryText { get; } } public abstract class MultipartItem { protected MultipartItem(string fileName, string? contentType) { } public MultipartItem(string fileName, string? contentType, string? name) { } public string? ContentType { get; } public string FileName { get; } public string? Name { get; } protected abstract System.Net.Http.HttpContent CreateContent(); public System.Net.Http.HttpContent ToContent() { } } public class ObjectToInferredTypesConverter : System.Text.Json.Serialization.JsonConverter { public ObjectToInferredTypesConverter() { } public override object? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { } public override void Write(System.Text.Json.Utf8JsonWriter writer, object objectToWrite, System.Text.Json.JsonSerializerOptions options) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class OptionsAttribute : Refit.HttpMethodAttribute { public OptionsAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public enum ParameterType { Normal = 0, RoundTripping = 1, } [System.AttributeUsage(System.AttributeTargets.Method)] public class PatchAttribute : Refit.HttpMethodAttribute { public PatchAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PostAttribute : Refit.HttpMethodAttribute { public PostAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class ProblemDetails { public ProblemDetails() { } public string? Detail { get; set; } public System.Collections.Generic.Dictionary Errors { get; set; } [System.Text.Json.Serialization.JsonExtensionData] public System.Collections.Generic.IDictionary Extensions { get; set; } public string? Instance { get; set; } public int Status { get; set; } public string? Title { get; set; } public string? Type { get; set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class PropertyAttribute : System.Attribute { public PropertyAttribute() { } public PropertyAttribute(string key) { } public string? Key { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PutAttribute : Refit.HttpMethodAttribute { public PutAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class QueryAttribute : System.Attribute { public QueryAttribute() { } public QueryAttribute(Refit.CollectionFormat collectionFormat) { } public QueryAttribute(string delimiter) { } public QueryAttribute(string delimiter, string prefix) { } public QueryAttribute(string delimiter, string prefix, string format) { } public Refit.CollectionFormat CollectionFormat { get; set; } public string Delimiter { get; protected set; } public string? Format { get; set; } public bool IsCollectionFormatSpecified { get; } public string? Prefix { get; protected set; } public bool TreatAsString { get; set; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class QueryUriFormatAttribute : System.Attribute { public QueryUriFormatAttribute(System.UriFormat uriFormat) { } public System.UriFormat UriFormat { get; } } public class RefitSettings { public RefitSettings() { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter) { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter = null, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter = null, Refit.IUrlParameterKeyFormatter? urlParameterKeyFormatter = null) { } public System.Func>? AuthorizationHeaderValueGetter { get; set; } public bool Buffered { get; set; } public Refit.CollectionFormat CollectionFormat { get; set; } public Refit.IHttpContentSerializer ContentSerializer { get; set; } public System.Func>? DeserializationExceptionFactory { get; set; } public System.Func> ExceptionFactory { get; set; } public Refit.IFormUrlEncodedParameterFormatter FormUrlEncodedParameterFormatter { get; set; } public System.Func? HttpMessageHandlerFactory { get; set; } public System.Collections.Generic.Dictionary? HttpRequestMessageOptions { get; set; } public Refit.IUrlParameterFormatter UrlParameterFormatter { get; set; } public Refit.IUrlParameterKeyFormatter UrlParameterKeyFormatter { get; set; } public System.Version Version { get; set; } public System.Net.Http.HttpVersionPolicy VersionPolicy { get; set; } } public static class RequestBuilder { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] public static Refit.IRequestBuilder ForType([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, Refit.RefitSettings? settings) { } public static Refit.IRequestBuilder ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>() { } public static Refit.IRequestBuilder ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(Refit.RefitSettings? settings) { } } public class RestMethodInfo : System.IEquatable { public RestMethodInfo(string Name, System.Type HostingType, System.Reflection.MethodInfo MethodInfo, string RelativePath, System.Type ReturnType) { } public System.Type HostingType { get; init; } public System.Reflection.MethodInfo MethodInfo { get; init; } public string Name { get; init; } public string RelativePath { get; init; } public System.Type ReturnType { get; init; } } public class RestMethodParameterInfo { public RestMethodParameterInfo(bool isObjectPropertyParameter, System.Reflection.ParameterInfo parameterInfo) { } public RestMethodParameterInfo(string name, System.Reflection.ParameterInfo parameterInfo) { } public bool IsObjectPropertyParameter { get; set; } public string? Name { get; set; } public System.Reflection.ParameterInfo ParameterInfo { get; set; } public System.Collections.Generic.List ParameterProperties { get; set; } public Refit.ParameterType Type { get; set; } } public class RestMethodParameterProperty { public RestMethodParameterProperty(string name, System.Reflection.PropertyInfo propertyInfo) { } public string Name { get; set; } public System.Reflection.PropertyInfo PropertyInfo { get; set; } } public static class RestService { public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl, Refit.RefitSettings? settings) { } } public class StreamPart : Refit.MultipartItem { public StreamPart(System.IO.Stream value, string fileName, string? contentType = null, string? name = null) { } public System.IO.Stream Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public sealed class SystemTextJsonContentSerializer : Refit.IHttpContentSerializer { public SystemTextJsonContentSerializer() { } public SystemTextJsonContentSerializer(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } public static System.Text.Json.JsonSerializerOptions GetDefaultJsonSerializerOptions() { } } [System.Serializable] public class ValidationApiException : Refit.ApiException { public new Refit.ProblemDetails? Content { get; } public static Refit.ValidationApiException Create(Refit.ApiException exception) { } } } ================================================ FILE: Refit.Tests/API/_snapshots/ApiApprovalTests.Refit.DotNet8_0.verified.txt ================================================ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.HttpClientFactory")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Newtonsoft.Json")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Xml")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName=".NET 8.0")] namespace Refit { [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class AliasAsAttribute : System.Attribute { public AliasAsAttribute(string name) { } public string Name { get; protected set; } } [System.Serializable] public class ApiException : System.Exception { protected ApiException(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } protected ApiException(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public string? Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] public bool HasContent { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } public System.Net.Http.HttpMethod HttpMethod { get; } public string? ReasonPhrase { get; } public Refit.RefitSettings RefitSettings { get; } public System.Net.Http.HttpRequestMessage RequestMessage { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Uri? Uri { get; } public System.Threading.Tasks.Task GetContentAsAsync() { } public static System.Threading.Tasks.Task Create(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public static System.Threading.Tasks.Task Create(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } } public sealed class ApiResponse : Refit.IApiResponse, Refit.IApiResponse, System.IDisposable { public ApiResponse(System.Net.Http.HttpResponseMessage response, T? content, Refit.RefitSettings settings, Refit.ApiException? error = null) { } public T Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + "herit from Refit.MultipartItem", false)] public class AttachmentNameAttribute : System.Attribute { public AttachmentNameAttribute(string name) { } public string Name { get; protected set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class AuthorizeAttribute : System.Attribute { public AuthorizeAttribute(string scheme = "Bearer") { } public string Scheme { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class BodyAttribute : System.Attribute { public BodyAttribute() { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod = 0) { } public BodyAttribute(bool buffered) { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod, bool buffered) { } public bool? Buffered { get; } public Refit.BodySerializationMethod SerializationMethod { get; } } public enum BodySerializationMethod { Default = 0, [System.Obsolete("Use BodySerializationMethod.Serialized instead", false)] Json = 1, UrlEncoded = 2, Serialized = 3, } public class ByteArrayPart : Refit.MultipartItem { public ByteArrayPart(byte[] value, string fileName, string? contentType = null, string? name = null) { } public byte[] Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public class CamelCaseUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public CamelCaseUrlParameterKeyFormatter() { } public string Format(string key) { } } public enum CollectionFormat { RefitParameterFormatter = 0, Csv = 1, Ssv = 2, Tsv = 3, Pipes = 4, Multi = 5, } public class DefaultApiExceptionFactory { public DefaultApiExceptionFactory(Refit.RefitSettings refitSettings) { } public System.Threading.Tasks.Task CreateAsync(System.Net.Http.HttpResponseMessage responseMessage) { } } public class DefaultFormUrlEncodedParameterFormatter : Refit.IFormUrlEncodedParameterFormatter { public DefaultFormUrlEncodedParameterFormatter() { } public virtual string? Format(object? parameterValue, string? formatString) { } } public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter { public DefaultUrlParameterFormatter() { } public void AddFormat(string format) { } public void AddFormat(string format) { } public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { } } public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public DefaultUrlParameterKeyFormatter() { } public virtual string Format(string key) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class DeleteAttribute : Refit.HttpMethodAttribute { public DeleteAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class FileInfoPart : Refit.MultipartItem { public FileInfoPart(System.IO.FileInfo value, string fileName, string? contentType = null, string? name = null) { } public System.IO.FileInfo Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class GetAttribute : Refit.HttpMethodAttribute { public GetAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class HeadAttribute : Refit.HttpMethodAttribute { public HeadAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderAttribute : System.Attribute { public HeaderAttribute(string header) { } public string Header { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderCollectionAttribute : System.Attribute { public HeaderCollectionAttribute() { } } [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Interface)] public class HeadersAttribute : System.Attribute { public HeadersAttribute(params string[] headers) { } public string[] Headers { get; } } public abstract class HttpMethodAttribute : System.Attribute { protected HttpMethodAttribute(string path) { } public abstract System.Net.Http.HttpMethod Method { get; } public virtual string Path { get; protected set; } } public static class HttpRequestMessageOptions { public static string InterfaceType { get; } public static string RestMethodInfo { get; } } public interface IApiResponse : System.IDisposable { System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } Refit.ApiException? Error { get; } System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } System.Version Version { get; } } public interface IApiResponse : Refit.IApiResponse, System.IDisposable { T Content { get; } new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { string? Format(object? value, string? formatString); } public interface IHttpContentSerializer { System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default); string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo); System.Net.Http.HttpContent ToHttpContent(T item); } public interface IRequestBuilder { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] System.Func BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null); } public interface IRequestBuilder : Refit.IRequestBuilder { } public interface IUrlParameterFormatter { string? Format(object? value, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type); } public interface IUrlParameterKeyFormatter { string Format(string key); } [System.Obsolete("Use NewtonsoftJsonContentSerializer in the Refit.Newtonsoft.Json package instead", true)] public class JsonContentSerializer : Refit.IHttpContentSerializer { public JsonContentSerializer() { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class MultipartAttribute : System.Attribute { public MultipartAttribute(string boundaryText = "----MyGreatBoundary") { } public string BoundaryText { get; } } public abstract class MultipartItem { protected MultipartItem(string fileName, string? contentType) { } public MultipartItem(string fileName, string? contentType, string? name) { } public string? ContentType { get; } public string FileName { get; } public string? Name { get; } protected abstract System.Net.Http.HttpContent CreateContent(); public System.Net.Http.HttpContent ToContent() { } } public class ObjectToInferredTypesConverter : System.Text.Json.Serialization.JsonConverter { public ObjectToInferredTypesConverter() { } public override object? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { } public override void Write(System.Text.Json.Utf8JsonWriter writer, object objectToWrite, System.Text.Json.JsonSerializerOptions options) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class OptionsAttribute : Refit.HttpMethodAttribute { public OptionsAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public enum ParameterType { Normal = 0, RoundTripping = 1, } [System.AttributeUsage(System.AttributeTargets.Method)] public class PatchAttribute : Refit.HttpMethodAttribute { public PatchAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PostAttribute : Refit.HttpMethodAttribute { public PostAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class ProblemDetails { public ProblemDetails() { } public string? Detail { get; set; } public System.Collections.Generic.Dictionary Errors { get; set; } [System.Text.Json.Serialization.JsonExtensionData] public System.Collections.Generic.IDictionary Extensions { get; set; } public string? Instance { get; set; } public int Status { get; set; } public string? Title { get; set; } public string? Type { get; set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class PropertyAttribute : System.Attribute { public PropertyAttribute() { } public PropertyAttribute(string key) { } public string? Key { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PutAttribute : Refit.HttpMethodAttribute { public PutAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class QueryAttribute : System.Attribute { public QueryAttribute() { } public QueryAttribute(Refit.CollectionFormat collectionFormat) { } public QueryAttribute(string delimiter) { } public QueryAttribute(string delimiter, string prefix) { } public QueryAttribute(string delimiter, string prefix, string format) { } public Refit.CollectionFormat CollectionFormat { get; set; } public string Delimiter { get; protected set; } public string? Format { get; set; } public bool IsCollectionFormatSpecified { get; } public string? Prefix { get; protected set; } public bool TreatAsString { get; set; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class QueryUriFormatAttribute : System.Attribute { public QueryUriFormatAttribute(System.UriFormat uriFormat) { } public System.UriFormat UriFormat { get; } } public class RefitSettings { public RefitSettings() { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter) { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter = null, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter = null, Refit.IUrlParameterKeyFormatter? urlParameterKeyFormatter = null) { } public System.Func>? AuthorizationHeaderValueGetter { get; set; } public bool Buffered { get; set; } public Refit.CollectionFormat CollectionFormat { get; set; } public Refit.IHttpContentSerializer ContentSerializer { get; set; } public System.Func>? DeserializationExceptionFactory { get; set; } public System.Func> ExceptionFactory { get; set; } public Refit.IFormUrlEncodedParameterFormatter FormUrlEncodedParameterFormatter { get; set; } public System.Func? HttpMessageHandlerFactory { get; set; } public System.Collections.Generic.Dictionary? HttpRequestMessageOptions { get; set; } public Refit.IUrlParameterFormatter UrlParameterFormatter { get; set; } public Refit.IUrlParameterKeyFormatter UrlParameterKeyFormatter { get; set; } public System.Version Version { get; set; } public System.Net.Http.HttpVersionPolicy VersionPolicy { get; set; } } public static class RequestBuilder { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] public static Refit.IRequestBuilder ForType([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, Refit.RefitSettings? settings) { } public static Refit.IRequestBuilder ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>() { } public static Refit.IRequestBuilder ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(Refit.RefitSettings? settings) { } } public class RestMethodInfo : System.IEquatable { public RestMethodInfo(string Name, System.Type HostingType, System.Reflection.MethodInfo MethodInfo, string RelativePath, System.Type ReturnType) { } public System.Type HostingType { get; init; } public System.Reflection.MethodInfo MethodInfo { get; init; } public string Name { get; init; } public string RelativePath { get; init; } public System.Type ReturnType { get; init; } } public class RestMethodParameterInfo { public RestMethodParameterInfo(bool isObjectPropertyParameter, System.Reflection.ParameterInfo parameterInfo) { } public RestMethodParameterInfo(string name, System.Reflection.ParameterInfo parameterInfo) { } public bool IsObjectPropertyParameter { get; set; } public string? Name { get; set; } public System.Reflection.ParameterInfo ParameterInfo { get; set; } public System.Collections.Generic.List ParameterProperties { get; set; } public Refit.ParameterType Type { get; set; } } public class RestMethodParameterProperty { public RestMethodParameterProperty(string name, System.Reflection.PropertyInfo propertyInfo) { } public string Name { get; set; } public System.Reflection.PropertyInfo PropertyInfo { get; set; } } public static class RestService { public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl, Refit.RefitSettings? settings) { } } public class StreamPart : Refit.MultipartItem { public StreamPart(System.IO.Stream value, string fileName, string? contentType = null, string? name = null) { } public System.IO.Stream Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public sealed class SystemTextJsonContentSerializer : Refit.IHttpContentSerializer { public SystemTextJsonContentSerializer() { } public SystemTextJsonContentSerializer(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } public static System.Text.Json.JsonSerializerOptions GetDefaultJsonSerializerOptions() { } } [System.Serializable] public class ValidationApiException : Refit.ApiException { public new Refit.ProblemDetails? Content { get; } public static Refit.ValidationApiException Create(Refit.ApiException exception) { } } } ================================================ FILE: Refit.Tests/API/_snapshots/ApiApprovalTests.Refit.DotNet9_0.verified.txt ================================================ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.HttpClientFactory")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Newtonsoft.Json")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Refit.Xml")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] namespace Refit { [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class AliasAsAttribute : System.Attribute { public AliasAsAttribute(string name) { } public string Name { get; protected set; } } [System.Serializable] public class ApiException : System.Exception { protected ApiException(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } protected ApiException(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, string? content, System.Net.HttpStatusCode statusCode, string? reasonPhrase, System.Net.Http.Headers.HttpResponseHeaders headers, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public string? Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] public bool HasContent { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } public System.Net.Http.HttpMethod HttpMethod { get; } public string? ReasonPhrase { get; } public Refit.RefitSettings RefitSettings { get; } public System.Net.Http.HttpRequestMessage RequestMessage { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Uri? Uri { get; } public System.Threading.Tasks.Task GetContentAsAsync() { } public static System.Threading.Tasks.Task Create(System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } public static System.Threading.Tasks.Task Create(string exceptionMessage, System.Net.Http.HttpRequestMessage message, System.Net.Http.HttpMethod httpMethod, System.Net.Http.HttpResponseMessage response, Refit.RefitSettings refitSettings, System.Exception? innerException = null) { } } public sealed class ApiResponse : Refit.IApiResponse, Refit.IApiResponse, System.IDisposable { public ApiResponse(System.Net.Http.HttpResponseMessage response, T? content, Refit.RefitSettings settings, Refit.ApiException? error = null) { } public T Content { get; } public System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + "herit from Refit.MultipartItem", false)] public class AttachmentNameAttribute : System.Attribute { public AttachmentNameAttribute(string name) { } public string Name { get; protected set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class AuthorizeAttribute : System.Attribute { public AuthorizeAttribute(string scheme = "Bearer") { } public string Scheme { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class BodyAttribute : System.Attribute { public BodyAttribute() { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod = 0) { } public BodyAttribute(bool buffered) { } public BodyAttribute(Refit.BodySerializationMethod serializationMethod, bool buffered) { } public bool? Buffered { get; } public Refit.BodySerializationMethod SerializationMethod { get; } } public enum BodySerializationMethod { Default = 0, [System.Obsolete("Use BodySerializationMethod.Serialized instead", false)] Json = 1, UrlEncoded = 2, Serialized = 3, } public class ByteArrayPart : Refit.MultipartItem { public ByteArrayPart(byte[] value, string fileName, string? contentType = null, string? name = null) { } public byte[] Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public class CamelCaseUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public CamelCaseUrlParameterKeyFormatter() { } public string Format(string key) { } } public enum CollectionFormat { RefitParameterFormatter = 0, Csv = 1, Ssv = 2, Tsv = 3, Pipes = 4, Multi = 5, } public class DefaultApiExceptionFactory { public DefaultApiExceptionFactory(Refit.RefitSettings refitSettings) { } public System.Threading.Tasks.Task CreateAsync(System.Net.Http.HttpResponseMessage responseMessage) { } } public class DefaultFormUrlEncodedParameterFormatter : Refit.IFormUrlEncodedParameterFormatter { public DefaultFormUrlEncodedParameterFormatter() { } public virtual string? Format(object? parameterValue, string? formatString) { } } public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter { public DefaultUrlParameterFormatter() { } public void AddFormat(string format) { } public void AddFormat(string format) { } public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { } } public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter { public DefaultUrlParameterKeyFormatter() { } public virtual string Format(string key) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class DeleteAttribute : Refit.HttpMethodAttribute { public DeleteAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class FileInfoPart : Refit.MultipartItem { public FileInfoPart(System.IO.FileInfo value, string fileName, string? contentType = null, string? name = null) { } public System.IO.FileInfo Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class GetAttribute : Refit.HttpMethodAttribute { public GetAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class HeadAttribute : Refit.HttpMethodAttribute { public HeadAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderAttribute : System.Attribute { public HeaderAttribute(string header) { } public string Header { get; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class HeaderCollectionAttribute : System.Attribute { public HeaderCollectionAttribute() { } } [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Interface)] public class HeadersAttribute : System.Attribute { public HeadersAttribute(params string[] headers) { } public string[] Headers { get; } } public abstract class HttpMethodAttribute : System.Attribute { protected HttpMethodAttribute(string path) { } public abstract System.Net.Http.HttpMethod Method { get; } public virtual string Path { get; protected set; } } public static class HttpRequestMessageOptions { public static string InterfaceType { get; } public static string RestMethodInfo { get; } } public interface IApiResponse : System.IDisposable { System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } Refit.ApiException? Error { get; } System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } System.Version Version { get; } } public interface IApiResponse : Refit.IApiResponse, System.IDisposable { T Content { get; } new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { string? Format(object? value, string? formatString); } public interface IHttpContentSerializer { System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default); string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo); System.Net.Http.HttpContent ToHttpContent(T item); } public interface IRequestBuilder { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] System.Func BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null); } public interface IRequestBuilder : Refit.IRequestBuilder { } public interface IUrlParameterFormatter { string? Format(object? value, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type); } public interface IUrlParameterKeyFormatter { string Format(string key); } [System.Obsolete("Use NewtonsoftJsonContentSerializer in the Refit.Newtonsoft.Json package instead", true)] public class JsonContentSerializer : Refit.IHttpContentSerializer { public JsonContentSerializer() { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class MultipartAttribute : System.Attribute { public MultipartAttribute(string boundaryText = "----MyGreatBoundary") { } public string BoundaryText { get; } } public abstract class MultipartItem { protected MultipartItem(string fileName, string? contentType) { } public MultipartItem(string fileName, string? contentType, string? name) { } public string? ContentType { get; } public string FileName { get; } public string? Name { get; } protected abstract System.Net.Http.HttpContent CreateContent(); public System.Net.Http.HttpContent ToContent() { } } public class ObjectToInferredTypesConverter : System.Text.Json.Serialization.JsonConverter { public ObjectToInferredTypesConverter() { } public override object? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { } public override void Write(System.Text.Json.Utf8JsonWriter writer, object objectToWrite, System.Text.Json.JsonSerializerOptions options) { } } [System.AttributeUsage(System.AttributeTargets.Method)] public class OptionsAttribute : Refit.HttpMethodAttribute { public OptionsAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public enum ParameterType { Normal = 0, RoundTripping = 1, } [System.AttributeUsage(System.AttributeTargets.Method)] public class PatchAttribute : Refit.HttpMethodAttribute { public PatchAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PostAttribute : Refit.HttpMethodAttribute { public PostAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } public class ProblemDetails { public ProblemDetails() { } public string? Detail { get; set; } public System.Collections.Generic.Dictionary Errors { get; set; } [System.Text.Json.Serialization.JsonExtensionData] public System.Collections.Generic.IDictionary Extensions { get; set; } public string? Instance { get; set; } public int Status { get; set; } public string? Title { get; set; } public string? Type { get; set; } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public class PropertyAttribute : System.Attribute { public PropertyAttribute() { } public PropertyAttribute(string key) { } public string? Key { get; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class PutAttribute : Refit.HttpMethodAttribute { public PutAttribute(string path) { } public override System.Net.Http.HttpMethod Method { get; } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] public class QueryAttribute : System.Attribute { public QueryAttribute() { } public QueryAttribute(Refit.CollectionFormat collectionFormat) { } public QueryAttribute(string delimiter) { } public QueryAttribute(string delimiter, string prefix) { } public QueryAttribute(string delimiter, string prefix, string format) { } public Refit.CollectionFormat CollectionFormat { get; set; } public string Delimiter { get; protected set; } public string? Format { get; set; } public bool IsCollectionFormatSpecified { get; } public string? Prefix { get; protected set; } public bool TreatAsString { get; set; } } [System.AttributeUsage(System.AttributeTargets.Method)] public class QueryUriFormatAttribute : System.Attribute { public QueryUriFormatAttribute(System.UriFormat uriFormat) { } public System.UriFormat UriFormat { get; } } public class RefitSettings { public RefitSettings() { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter) { } public RefitSettings(Refit.IHttpContentSerializer contentSerializer, Refit.IUrlParameterFormatter? urlParameterFormatter = null, Refit.IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter = null, Refit.IUrlParameterKeyFormatter? urlParameterKeyFormatter = null) { } public System.Func>? AuthorizationHeaderValueGetter { get; set; } public bool Buffered { get; set; } public Refit.CollectionFormat CollectionFormat { get; set; } public Refit.IHttpContentSerializer ContentSerializer { get; set; } public System.Func>? DeserializationExceptionFactory { get; set; } public System.Func> ExceptionFactory { get; set; } public Refit.IFormUrlEncodedParameterFormatter FormUrlEncodedParameterFormatter { get; set; } public System.Func? HttpMessageHandlerFactory { get; set; } public System.Collections.Generic.Dictionary? HttpRequestMessageOptions { get; set; } public Refit.IUrlParameterFormatter UrlParameterFormatter { get; set; } public Refit.IUrlParameterKeyFormatter UrlParameterKeyFormatter { get; set; } public System.Version Version { get; set; } public System.Net.Http.HttpVersionPolicy VersionPolicy { get; set; } } public static class RequestBuilder { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " + "and DTOs are preserved when trimming.")] public static Refit.IRequestBuilder ForType([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, Refit.RefitSettings? settings) { } public static Refit.IRequestBuilder ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>() { } public static Refit.IRequestBuilder ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(Refit.RefitSettings? settings) { } } public class RestMethodInfo : System.IEquatable { public RestMethodInfo(string Name, System.Type HostingType, System.Reflection.MethodInfo MethodInfo, string RelativePath, System.Type ReturnType) { } public System.Type HostingType { get; init; } public System.Reflection.MethodInfo MethodInfo { get; init; } public string Name { get; init; } public string RelativePath { get; init; } public System.Type ReturnType { get; init; } } public class RestMethodParameterInfo { public RestMethodParameterInfo(bool isObjectPropertyParameter, System.Reflection.ParameterInfo parameterInfo) { } public RestMethodParameterInfo(string name, System.Reflection.ParameterInfo parameterInfo) { } public bool IsObjectPropertyParameter { get; set; } public string? Name { get; set; } public System.Reflection.ParameterInfo ParameterInfo { get; set; } public System.Collections.Generic.List ParameterProperties { get; set; } public Refit.ParameterType Type { get; set; } } public class RestMethodParameterProperty { public RestMethodParameterProperty(string name, System.Reflection.PropertyInfo propertyInfo) { } public string Name { get; set; } public System.Reflection.PropertyInfo PropertyInfo { get; set; } } public static class RestService { public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { } public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl, Refit.RefitSettings? settings) { } } public class StreamPart : Refit.MultipartItem { public StreamPart(System.IO.Stream value, string fileName, string? contentType = null, string? name = null) { } public System.IO.Stream Value { get; } protected override System.Net.Http.HttpContent CreateContent() { } } public sealed class SystemTextJsonContentSerializer : Refit.IHttpContentSerializer { public SystemTextJsonContentSerializer() { } public SystemTextJsonContentSerializer(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { } public System.Threading.Tasks.Task FromHttpContentAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default) { } public string? GetFieldNameForProperty(System.Reflection.PropertyInfo propertyInfo) { } public System.Net.Http.HttpContent ToHttpContent(T item) { } public static System.Text.Json.JsonSerializerOptions GetDefaultJsonSerializerOptions() { } } [System.Serializable] public class ValidationApiException : Refit.ApiException { public new Refit.ProblemDetails? Content { get; } public static Refit.ValidationApiException Create(Refit.ApiException exception) { } } } ================================================ FILE: Refit.Tests/App.config ================================================  ================================================ FILE: Refit.Tests/AuthenticatedClientHandlerTests.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Refit; // for the code gen using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public class AuthenticatedClientHandlerTests { public interface IMyAuthenticatedService { [Get("/unauth")] Task GetUnauthenticated(); [Get("/auth")] [Headers("Authorization: Bearer")] Task GetAuthenticated(); [Get("/auth")] Task GetAuthenticatedWithTokenInMethod([Authorize("Bearer")] string token); [Get("/auth")] Task GetAuthenticatedWithAuthorizeAttributeAndHeaderCollection( [Authorize("Bearer")] string token, [HeaderCollection] IDictionary headers ); [Get("/auth")] Task GetAuthenticatedWithTokenInHeaderCollection( [HeaderCollection] IDictionary headers ); [Post("/auth/{id}")] Task PostAuthenticatedWithTokenInHeaderCollection( int id, SomeRequestData content, [HeaderCollection] IDictionary headers ); } public interface IInheritedAuthenticatedServiceWithHeaders : IAuthenticatedServiceWithHeaders { [Get("/get-inherited-thing")] Task GetInheritedThing(); } public interface IInheritedAuthenticatedServiceWithHeadersCRLF : IAuthenticatedServiceWithHeaders { [Get("/get-inherited-thing\r\n\r\nGET /smuggled")] Task GetInheritedThing(); } [Headers("Authorization: Bearer")] public interface IAuthenticatedServiceWithHeaders { [Get("/get-base-thing")] Task GetThingFromBase(); } [Fact] public void DefaultHandlerIsHttpClientHandler() { var handler = new AuthenticatedHttpClientHandler(((_, _) => Task.FromResult(string.Empty))); Assert.IsType(handler.InnerHandler); } [Fact] public void DefaultHandlerIsNull() { var handler = new AuthenticatedHttpClientHandler(null, ((_, _) => Task.FromResult(string.Empty))); Assert.Null(handler.InnerHandler); } [Fact] public void NullTokenGetterThrows() { Assert.Throws(() => new AuthenticatedHttpClientHandler(null)); } [Fact] public async Task AuthenticatedHandlerIgnoresUnAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("tokenValue"), HttpMessageHandlerFactory = () => handler }; handler .Expect(HttpMethod.Get, "http://api/unauth") .With(msg => msg.Headers.Authorization == null) .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetUnauthenticated(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("tokenValue"), HttpMessageHandlerFactory = () => handler }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetAuthenticated(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerWithParamUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { AuthorizationHeaderValueGetter = (request, _) => Task.FromResult("tokenValue"), HttpMessageHandlerFactory = () => handler }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetAuthenticated(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerWithTokenInParameterUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetAuthenticatedWithTokenInMethod("tokenValue"); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerWithTokenInHeaderCollectionUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var headers = new Dictionary { { "User-Agent", "Refit" }, { "Authorization", "Bearer tokenValue" } }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders(headers) .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetAuthenticatedWithTokenInHeaderCollection(headers); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerWithAuthorizeAttributeAndHeaderCollectionUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var expectedHeaders = new Dictionary { { "Authorization", "Bearer tokenValue" }, { "User-Agent", "Refit" }, { "X-Forwarded-For", "Refit" } }; var headerCollectionHeaders = new Dictionary { { "User-Agent", "Refit" }, { "X-Forwarded-For", "Refit" } }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders(expectedHeaders) .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetAuthenticatedWithAuthorizeAttributeAndHeaderCollection( "tokenValue", headerCollectionHeaders ); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerWithDuplicatedAuthorizationHeaderUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var expectedHeaders = new Dictionary { { "Authorization", "Bearer tokenValue2" }, { "User-Agent", "Refit" }, { "X-Forwarded-For", "Refit" } }; var headerCollectionHeaders = new Dictionary { { "Authorization", "Bearer tokenValue2" }, { "User-Agent", "Refit" }, { "X-Forwarded-For", "Refit" } }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders(expectedHeaders) .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.GetAuthenticatedWithAuthorizeAttributeAndHeaderCollection( "tokenValue", headerCollectionHeaders ); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthenticatedHandlerPostTokenInHeaderCollectionUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var id = 1; var someRequestData = new SomeRequestData { ReadablePropertyName = 1 }; var headers = new Dictionary { { "Authorization", "Bearer tokenValue2" }, { "ThingId", id.ToString() } }; handler .Expect(HttpMethod.Post, $"http://api/auth/{id}") .WithHeaders(headers) .Respond("text/plain", "Ok"); var fixture = RestService.For("http://api", settings); var result = await fixture.PostAuthenticatedWithTokenInHeaderCollection( id, someRequestData, headers ); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthentictedMethodFromBaseClassWithHeadersAttributeUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("tokenValue"), HttpMessageHandlerFactory = () => handler }; handler .Expect(HttpMethod.Get, "http://api/get-base-thing") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); var fixture = RestService.For( "http://api", settings ); var result = await fixture.GetThingFromBase(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthentictedMethodFromInheritedClassWithHeadersAttributeUsesAuth() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("tokenValue"), HttpMessageHandlerFactory = () => handler }; handler .Expect(HttpMethod.Get, "http://api/get-inherited-thing") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); var fixture = RestService.For( "http://api", settings ); var result = await fixture.GetInheritedThing(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthentictedMethodFromInheritedClassWithHeadersAttributeUsesAuth_WithCRLFCheck() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("tokenValue"), HttpMessageHandlerFactory = () => handler, }; handler .Expect(HttpMethod.Get, "http://api/get-inherited-thing") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); await Assert.ThrowsAsync(async () => { var fixture = RestService.For( "http://api", settings ); var result = await fixture.GetInheritedThing(); }); } [Fact] public async Task AuthorizationHeaderValueGetterIsUsedWhenSupplyingHttpClient() { var handler = new MockHttpMessageHandler(); var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://api") }; var settings = new RefitSettings { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("tokenValue") }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders("Authorization", "Bearer tokenValue") .Respond("text/plain", "Ok"); var fixture = RestService.For(httpClient, settings); var result = await fixture.GetAuthenticated(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } [Fact] public async Task AuthorizationHeaderValueGetterDoesNotOverrideExplicitTokenWhenSupplyingHttpClient() { var handler = new MockHttpMessageHandler(); var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://api") }; var settings = new RefitSettings { AuthorizationHeaderValueGetter = (_, __) => Task.FromResult("token-from-getter") }; handler .Expect(HttpMethod.Get, "http://api/auth") .WithHeaders("Authorization", "Bearer token-from-parameter") .Respond("text/plain", "Ok"); var fixture = RestService.For(httpClient, settings); var result = await fixture.GetAuthenticatedWithTokenInMethod("token-from-parameter"); handler.VerifyNoOutstandingExpectation(); Assert.Equal("Ok", result); } } ================================================ FILE: Refit.Tests/CachedRequestBuilder.cs ================================================ using System.Net; using System.Net.Http; using System.Reflection; using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public interface IGeneralRequests { [Post("/foo")] Task Empty(); [Post("/foo")] Task SingleParameter(string id); [Post("/foo")] Task MultiParameter(string id, string name); [Post("/foo")] Task SingleGenericMultiParameter(string id, string name, TValue generic); } public interface IDuplicateNames { [Post("/foo")] Task SingleParameter(string id); [Post("/foo")] Task SingleParameter(int id); } public class CachedRequestBuilderTests { [Fact] public async Task CacheHasCorrectNumberOfElementsTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://bar", settings); // get internal dictionary to check count var requestBuilderField = fixture.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Single(x => x.Name == "requestBuilder"); var requestBuilder = requestBuilderField.GetValue(fixture) as CachedRequestBuilderImplementation; mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .Respond(HttpStatusCode.OK); await fixture.Empty(); Assert.Single(requestBuilder.MethodDictionary); mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .Respond(HttpStatusCode.OK); await fixture.SingleParameter("id"); Assert.Equal(2, requestBuilder.MethodDictionary.Count); mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .WithQueryString("name", "name") .Respond(HttpStatusCode.OK); await fixture.MultiParameter("id", "name"); Assert.Equal(3, requestBuilder.MethodDictionary.Count); mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .WithQueryString("name", "name") .WithQueryString("generic", "generic") .Respond(HttpStatusCode.OK); await fixture.SingleGenericMultiParameter("id", "name", "generic"); Assert.Equal(4, requestBuilder.MethodDictionary.Count); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task NoDuplicateEntriesTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://bar", settings); // get internal dictionary to check count var requestBuilderField = fixture.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Single(x => x.Name == "requestBuilder"); var requestBuilder = requestBuilderField.GetValue(fixture) as CachedRequestBuilderImplementation; // send the same request repeatedly to ensure that multiple dictionary entries are not created mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .Respond(HttpStatusCode.OK); await fixture.SingleParameter("id"); Assert.Single(requestBuilder.MethodDictionary); mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .Respond(HttpStatusCode.OK); await fixture.SingleParameter("id"); Assert.Single(requestBuilder.MethodDictionary); mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .Respond(HttpStatusCode.OK); await fixture.SingleParameter("id"); Assert.Single(requestBuilder.MethodDictionary); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task SameNameDuplicateEntriesTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://bar", settings); // get internal dictionary to check count var requestBuilderField = fixture.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Single(x => x.Name == "requestBuilder"); var requestBuilder = requestBuilderField.GetValue(fixture) as CachedRequestBuilderImplementation; // send the two different requests with the same name mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "id") .Respond(HttpStatusCode.OK); await fixture.SingleParameter("id"); Assert.Single(requestBuilder.MethodDictionary); mockHttp .Expect(HttpMethod.Post, "http://bar/foo") .WithQueryString("id", "10") .Respond(HttpStatusCode.OK); await fixture.SingleParameter(10); Assert.Equal(2, requestBuilder.MethodDictionary.Count); mockHttp.VerifyNoOutstandingExpectation(); } } ================================================ FILE: Refit.Tests/CamelCaseUrlParameterKeyFormatter.cs ================================================ using Xunit; namespace Refit.Tests; public class CamelCaselTestsRequest { public string alreadyCamelCased { get; set; } public string NOTCAMELCased { get; set; } } public class CamelCaseUrlParameterKeyFormatterTests { [Fact] public void Format_EmptyKey_ReturnsEmptyKey() { var urlParameterKeyFormatter = new CamelCaseUrlParameterKeyFormatter(); var output = urlParameterKeyFormatter.Format(string.Empty); Assert.Equal(string.Empty, output); } [Fact] public void FormatKey_Returns_ExpectedValue() { var urlParameterKeyFormatter = new CamelCaseUrlParameterKeyFormatter(); var refitSettings = new RefitSettings { UrlParameterKeyFormatter = urlParameterKeyFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexQueryObjectWithDictionary) ); var complexQuery = new CamelCaselTestsRequest { alreadyCamelCased = "value1", NOTCAMELCased = "value2" }; var output = factory([complexQuery]); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?alreadyCamelCased=value1¬camelCased=value2", uri.PathAndQuery); } } ================================================ FILE: Refit.Tests/DefaultUrlParameterFormatterTest.cs ================================================ using System.Globalization; using System.Reflection; using Xunit; namespace Refit.Tests; public class DefaultUrlParameterFormatterTests { class DefaultUrlParameterFormatterTestRequest { [Query(Format = "yyyy")] public DateTime? DateTimeWithAttributeFormatYear { get; set; } public DateTime? DateTime { get; set; } public IEnumerable DateTimeCollection { get; set; } public IDictionary DateTimeDictionary { get; set; } public IDictionary DateTimeKeyedDictionary { get; set; } } [Fact] public void NullParameterValue_ReturnsNull() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTime = null }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); var output = urlParameterFormatter.Format( parameters.DateTime, parameters.GetType().GetProperty(nameof(parameters.DateTime))!, parameters.GetType()); Assert.Null(output); } [Fact] public void NoFormatters_UseDefaultFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTime = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); var output = urlParameterFormatter.Format( parameters.DateTime, parameters.GetType().GetProperty(nameof(parameters.DateTime))!, parameters.GetType()); Assert.Equal("08/21/2023 00:00:00", output); } [Fact] public void QueryAttributeFormatOnly_UseQueryAttributeFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); var output = urlParameterFormatter.Format( parameters.DateTimeWithAttributeFormatYear, parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void QueryAttributeAndGeneralFormat_UseQueryAttributeFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy-MM-dd"); var output = urlParameterFormatter.Format( parameters.DateTimeWithAttributeFormatYear, parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void QueryAttributeAndSpecificFormat_UseQueryAttributeFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy-MM-dd"); var output = urlParameterFormatter.Format( parameters.DateTimeWithAttributeFormatYear, parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void AllFormats_UseQueryAttributeFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy-MM-dd"); urlParameterFormatter.AddFormat("yyyy-MM-dd"); var output = urlParameterFormatter.Format( parameters.DateTimeWithAttributeFormatYear, parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void GeneralFormatOnly_UseGeneralFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTime = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy"); var output = urlParameterFormatter.Format( parameters.DateTime, parameters.GetType().GetProperty(nameof(parameters.DateTime))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void SpecificFormatOnly_UseSpecificFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTime = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy"); var output = urlParameterFormatter.Format( parameters.DateTime, parameters.GetType().GetProperty(nameof(parameters.DateTime))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void GeneralAndSpecificFormats_UseSpecificFormat() { var parameters = new DefaultUrlParameterFormatterTestRequest { DateTime = new DateTime(2023, 8, 21) }; var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy-MM-dd"); urlParameterFormatter.AddFormat("yyyy"); var output = urlParameterFormatter.Format( parameters.DateTime, parameters.GetType().GetProperty(nameof(parameters.DateTime))!, parameters.GetType()); Assert.Equal("2023", output); } [Fact] public void RequestWithPlainDateTimeQueryParameter_ProducesCorrectQueryString() { var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy"); var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var parameters = new DefaultUrlParameterFormatterTestRequest { DateTime = new DateTime(2023, 8, 21), }; var output = factory([parameters]); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "?DateTime=2023", uri.Query ); } [Fact] public void RequestWithDateTimeCollectionQueryParameter_ProducesCorrectQueryString() { var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy"); var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeCollection = [new DateTime(2023, 8, 21), new DateTime(2024, 8, 21)], }; var output = factory([parameters]); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "?DateTimeCollection=2023%2C2024", uri.Query ); } [Fact] public void RequestWithDateTimeDictionaryQueryParameter_ProducesCorrectQueryString() { var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy"); var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeDictionary = new Dictionary { { 1, new DateTime(2023, 8, 21) }, { 2, new DateTime(2024, 8, 21) }, }, }; var output = factory([parameters]); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "?DateTimeDictionary.1=2023&DateTimeDictionary.2=2024", uri.Query ); } [Fact] public void RequestWithDateTimeKeyedDictionaryQueryParameter_ProducesCorrectQueryString() { var urlParameterFormatter = new DefaultUrlParameterFormatter(); urlParameterFormatter.AddFormat("yyyy"); var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var parameters = new DefaultUrlParameterFormatterTestRequest { DateTimeKeyedDictionary = new Dictionary { { new DateTime(2023, 8, 21), 1 }, { new DateTime(2024, 8, 21), 2 }, }, }; var output = factory([parameters]); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "?DateTimeKeyedDictionary.2023=1&DateTimeKeyedDictionary.2024=2", uri.Query ); } } ================================================ FILE: Refit.Tests/DeliminatorSeparatedPropertyNamesContractResolver.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Text; using Newtonsoft.Json.Serialization; namespace Refit.Tests; public class DeliminatorSeparatedPropertyNamesContractResolver : DefaultContractResolver { readonly string separator; protected DeliminatorSeparatedPropertyNamesContractResolver(char separator) { this.separator = separator.ToString(CultureInfo.InvariantCulture); } protected override string ResolvePropertyName(string propertyName) { var parts = new List(); var currentWord = new StringBuilder(); foreach (var c in propertyName.ToCharArray()) { if (Char.IsUpper(c) && currentWord.Length > 0) { parts.Add(currentWord.ToString()); currentWord.Clear(); } currentWord.Append(char.ToLower(c)); } if (currentWord.Length > 0) { parts.Add(currentWord.ToString()); } return String.Join(separator, parts.ToArray()); } } public class SnakeCasePropertyNamesContractResolver : DeliminatorSeparatedPropertyNamesContractResolver { public SnakeCasePropertyNamesContractResolver() : base('_') { } } ================================================ FILE: Refit.Tests/DeserializationExceptionFactoryTests.cs ================================================ using System.Net; using System.Net.Http; using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public class DeserializationExceptionFactoryTests { public interface IMyService { [Get("/get-with-result")] Task GetWithResult(); } [Fact] public async Task NoDeserializationExceptionFactory_WithSuccessfulDeserialization() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, }; var intContent = 123; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent($"{intContent}")); var fixture = RestService.For("http://api", settings); var result = await fixture.GetWithResult(); handler.VerifyNoOutstandingExpectation(); Assert.Equal(intContent, result); } [Fact] public async Task NoDeserializationExceptionFactory_WithUnsuccessfulDeserialization() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, }; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent("non-int-result")); var fixture = RestService.For("http://api", settings); var thrownException = await Assert.ThrowsAsync(() => fixture.GetWithResult()); Assert.Equal("An error occured deserializing the response.", thrownException.Message); handler.VerifyNoOutstandingExpectation(); } [Fact] public async Task ProvideFactoryWhichReturnsNull_WithSuccessfulDeserialization() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, DeserializationExceptionFactory = (_, _) => Task.FromResult(null) }; var intContent = 123; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent($"{intContent}")); var fixture = RestService.For("http://api", settings); var result = await fixture.GetWithResult(); handler.VerifyNoOutstandingExpectation(); Assert.Equal(intContent, result); } [Fact] public async Task ProvideFactoryWhichReturnsNull_WithUnsuccessfulDeserialization() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, DeserializationExceptionFactory = (_, _) => Task.FromResult(null) }; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent("non-int-result")); var fixture = RestService.For("http://api", settings); var result = await fixture.GetWithResult(); handler.VerifyNoOutstandingExpectation(); Assert.Equal(default, result); } [Fact] public async Task ProvideFactoryWhichReturnsException_WithUnsuccessfulDeserialization() { var handler = new MockHttpMessageHandler(); var exception = new Exception("Unsuccessful Deserialization Exception"); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, DeserializationExceptionFactory = (_, _) => Task.FromResult(exception) }; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent("non-int-result")); var fixture = RestService.For("http://api", settings); var thrownException = await Assert.ThrowsAsync(() => fixture.GetWithResult()); Assert.Equal(exception, thrownException); handler.VerifyNoOutstandingExpectation(); } [Fact] public async Task ProvideFactoryWhichReturnsException_WithSuccessfulDeserialization() { var handler = new MockHttpMessageHandler(); var exception = new Exception("Unsuccessful Deserialization Exception"); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, DeserializationExceptionFactory = (_, _) => Task.FromResult(exception) }; var intContent = 123; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent($"{intContent}")); var fixture = RestService.For("http://api", settings); var result = await fixture.GetWithResult(); handler.VerifyNoOutstandingExpectation(); Assert.Equal(intContent, result); } } ================================================ FILE: Refit.Tests/ExceptionFactoryTests.cs ================================================ using System; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Refit; // for the code gen using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public class ExceptionFactoryTests { public interface IMyService { [Get("/get-with-result")] Task GetWithResult(); [Put("/put-without-result")] Task PutWithoutResult(); } [Fact] public async Task ProvideFactoryWhichAlwaysReturnsNull_WithResult() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, ExceptionFactory = _ => Task.FromResult(null) }; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.NotFound, new StringContent("error-result")); var fixture = RestService.For("http://api", settings); var result = await fixture.GetWithResult(); handler.VerifyNoOutstandingExpectation(); Assert.Equal("error-result", result); } [Fact] public async Task ProvideFactoryWhichAlwaysReturnsNull_WithoutResult() { var handler = new MockHttpMessageHandler(); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, ExceptionFactory = _ => Task.FromResult(null) }; handler .Expect(HttpMethod.Put, "http://api/put-without-result") .Respond(HttpStatusCode.NotFound); var fixture = RestService.For("http://api", settings); await fixture.PutWithoutResult(); handler.VerifyNoOutstandingExpectation(); } [Fact] public async Task ProvideFactoryWhichAlwaysReturnsException_WithResult() { var handler = new MockHttpMessageHandler(); var exception = new Exception("I like to fail"); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, ExceptionFactory = _ => Task.FromResult(exception) }; handler .Expect(HttpMethod.Get, "http://api/get-with-result") .Respond(HttpStatusCode.OK, new StringContent("success-result")); var fixture = RestService.For("http://api", settings); var thrownException = await Assert.ThrowsAsync(() => fixture.GetWithResult()); Assert.Equal(exception, thrownException); handler.VerifyNoOutstandingExpectation(); } [Fact] public async Task ProvideFactoryWhichAlwaysReturnsException_WithoutResult() { var handler = new MockHttpMessageHandler(); var exception = new Exception("I like to fail"); var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, ExceptionFactory = _ => Task.FromResult(exception) }; handler.Expect(HttpMethod.Put, "http://api/put-without-result").Respond(HttpStatusCode.OK); var fixture = RestService.For("http://api", settings); var thrownException = await Assert.ThrowsAsync(() => fixture.PutWithoutResult()); Assert.Equal(exception, thrownException); handler.VerifyNoOutstandingExpectation(); } } ================================================ FILE: Refit.Tests/ExplicitInterfaceRefitTests.cs ================================================ using System.Net.Http; using System.Threading.Tasks; using Refit; using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public class ExplicitInterfaceRefitTests { public interface IFoo { int Bar(); } // Internal interface with a default implementation of IFoo.Bar that calls an internal Refit method internal interface IInternalFoo : IFoo { int IFoo.Bar() => InternalBar() + 1; [Get("/bar")] internal int InternalBar(); } // Derived interface that explicitly implements IFoo.Bar and marks it as a Refit method public interface IRemoteFoo2 : IFoo { [Get("/bar")] abstract int IFoo.Bar(); } [Fact] public void DefaultInterfaceImplementation_calls_internal_refit_method() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "http://foo/bar") .Respond("application/json", "41"); var fixture = RestService.For("http://foo", settings); var result = ((IFoo)fixture).Bar(); Assert.Equal(42, result); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public void Explicit_interface_member_with_refit_attribute_is_invoked() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "http://foo/bar") .Respond("application/json", "41"); var fixture = RestService.For("http://foo", settings); var result = ((IFoo)fixture).Bar(); Assert.Equal(41, result); mockHttp.VerifyNoOutstandingExpectation(); } } ================================================ FILE: Refit.Tests/FormValueMultimapTests.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text.Json.Serialization; using Newtonsoft.Json; using Xunit; namespace Refit.Tests; public class FormValueMultimapTests { readonly RefitSettings settings = new(); [Fact] public void EmptyIfNullPassedIn() { var target = new FormValueMultimap(null, settings); Assert.Empty(target); } [Fact] public void LoadsFromDictionary() { var source = new Dictionary { { "foo", "bar" }, { "xyz", "123" } }; var target = new FormValueMultimap(source, settings); Assert.Equal(source, target); } [Fact] public void LoadsFromObject() { var source = new ObjectTestClass { A = "1", B = "2" }; var expected = new Dictionary { { "A", "1" }, { "B", "2" }, }; var actual = new FormValueMultimap(source, settings); Assert.Equal(expected, actual); } [Fact] public void LoadFromObjectWithCollections() { var source = new ObjectWithRepeatedFieldsTestClass { A = new List { 1, 2 }, B = new HashSet { "set1", "set2" }, C = new HashSet { 1, 2 }, D = new List { 0.1, 1.0 }, E = new List { true, false } }; var expected = new List> { new KeyValuePair("A", "01"), new KeyValuePair("A", "02"), new KeyValuePair("B", "set1,set2"), new KeyValuePair("C", "01 02"), new KeyValuePair("D", "0.10\t1.00"), // The default behavior is to capitalize booleans. This is not a requirement. new KeyValuePair("E", "True|False") }; var actual = new FormValueMultimap(source, settings); Assert.Equal(expected, actual); } [Fact] public void DefaultCollectionFormatCanBeSpecifiedInSettings_Multi() { var settingsWithCollectionFormat = new RefitSettings { CollectionFormat = CollectionFormat.Multi }; var source = new ObjectWithRepeatedFieldsTestClass { // Members have explicit CollectionFormat A = new List { 1, 2 }, B = new HashSet { "set1", "set2" }, C = new HashSet { 1, 2 }, D = new List { 0.1, 1.0 }, E = new List { true, false }, // Member has no explicit CollectionFormat F = new[] { 1, 2, 3 } }; var expected = new List> { new KeyValuePair("A", "01"), new KeyValuePair("A", "02"), new KeyValuePair("B", "set1,set2"), new KeyValuePair("C", "01 02"), new KeyValuePair("D", "0.10\t1.00"), new KeyValuePair("E", "True|False"), new KeyValuePair("F", "1"), new KeyValuePair("F", "2"), new KeyValuePair("F", "3"), }; var actual = new FormValueMultimap(source, settingsWithCollectionFormat); Assert.Equal(expected, actual); } [Theory] [InlineData(CollectionFormat.Csv, "1,2,3")] [InlineData(CollectionFormat.Pipes, "1|2|3")] [InlineData(CollectionFormat.Ssv, "1 2 3")] [InlineData(CollectionFormat.Tsv, "1\t2\t3")] public void DefaultCollectionFormatCanBeSpecifiedInSettings( CollectionFormat format, string expectedFormat ) { var settingsWithCollectionFormat = new RefitSettings { CollectionFormat = format }; var source = new ObjectWithRepeatedFieldsTestClass { // Members have explicit CollectionFormat A = new List { 1, 2 }, B = new HashSet { "set1", "set2" }, C = new HashSet { 1, 2 }, D = new List { 0.1, 1.0 }, E = new List { true, false }, // Member has no explicit CollectionFormat F = new[] { 1, 2, 3 } }; var expected = new List> { new KeyValuePair("A", "01"), new KeyValuePair("A", "02"), new KeyValuePair("B", "set1,set2"), new KeyValuePair("C", "01 02"), new KeyValuePair("D", "0.10\t1.00"), new KeyValuePair("E", "True|False"), new KeyValuePair("F", expectedFormat), }; var actual = new FormValueMultimap(source, settingsWithCollectionFormat); Assert.Equal(expected, actual); } public class ObjectTestClass { public string A { get; set; } public string B { get; set; } public string C { get; set; } } public class ObjectWithRepeatedFieldsTestClass { [Query(CollectionFormat.Multi, Format = "00")] public IList A { get; set; } [Query(CollectionFormat.Csv)] public ISet B { get; set; } [Query(CollectionFormat.Ssv, Format = "00")] public HashSet C { get; set; } [Query(CollectionFormat.Tsv, Format = "0.00")] public IList D { get; set; } [Query(CollectionFormat.Pipes)] public IList E { get; set; } [Query] public int[] F { get; set; } } [Fact] public void ExcludesPropertiesWithInaccessibleGetters() { var source = new ClassWithInaccessibleGetters { A = "Foo", B = "Bar" }; var expected = new Dictionary { { "C", "FooBar" } }; var actual = new FormValueMultimap(source, settings); Assert.Equal(expected, actual); } public class ClassWithInaccessibleGetters { public string A { internal get; set; } public string B { private get; set; } public string C => A + B; } [Fact] public void LoadsFromAnonymousType() { var source = new { foo = "bar", xyz = 123 }; var expected = new Dictionary { { "foo", "bar" }, { "xyz", "123" } }; var actual = new FormValueMultimap(source, settings); Assert.Equal(expected, actual); } [Fact] public void UsesAliasAsAttribute() { var source = new AliasingTestClass { Foo = "abc" }; var target = new FormValueMultimap(source, settings); Assert.DoesNotContain("Foo", target.Keys); Assert.Contains("f", target.Keys); Assert.Equal("abc", target.FirstOrDefault(entry => entry.Key == "f").Value); } [Fact] public void UsesJsonPropertyAttribute() { var source = new AliasingTestClass { Bar = "xyz" }; var target = new FormValueMultimap(source, settings); Assert.DoesNotContain("Bar", target.Keys); Assert.Contains("b", target.Keys); Assert.Equal("xyz", target.FirstOrDefault(entry => entry.Key == "b").Value); } [Fact] public void UsesQueryPropertyAttribute() { var source = new AliasingTestClass { Frob = 4 }; var target = new FormValueMultimap(source, settings); Assert.DoesNotContain("Bar", target.Keys); Assert.Contains("prefix-fr", target.Keys); Assert.Equal("4.0", target.FirstOrDefault(entry => entry.Key == "prefix-fr").Value); } [Fact] public void GivesPrecedenceToAliasAs() { var source = new AliasingTestClass { Baz = "123" }; var target = new FormValueMultimap(source, settings); Assert.DoesNotContain("Bar", target.Keys); Assert.DoesNotContain("z", target.Keys); Assert.Contains("a", target.Keys); Assert.Equal("123", target.FirstOrDefault(entry => entry.Key == "a").Value); } [Fact] public void SkipsNullValuesFromDictionary() { var source = new Dictionary { { "foo", "bar" }, { "xyz", null } }; var target = new FormValueMultimap(source, settings); Assert.Single(target); Assert.Contains("foo", target.Keys); } [Fact] public void SerializesEnumWithEnumMemberAttribute() { var source = new Dictionary() { { "A", EnumWithEnumMember.A }, { "B", EnumWithEnumMember.B } }; var expected = new Dictionary { { "A", "A" }, { "B", "b" } }; var actual = new FormValueMultimap(source, settings); Assert.Equal(expected, actual); } public class AliasingTestClass { [AliasAs("f")] public string Foo { get; set; } [JsonProperty(PropertyName = "b")] [JsonPropertyName("b")] public string Bar { get; set; } [AliasAs("a")] [JsonProperty(PropertyName = "z")] [JsonPropertyName("z")] public string Baz { get; set; } [Query("-", "prefix", "0.0")] [AliasAs("fr")] public int? Frob { get; set; } } public enum EnumWithEnumMember { A, [EnumMember(Value = "b")] B } } ================================================ FILE: Refit.Tests/GitHubApi.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this using static System.Math; // This is here to verify https://github.com/reactiveui/refit/issues/283 namespace Refit.Tests; public record User { public string Login { get; set; } public int Id { get; set; } public string AvatarUrl { get; set; } public string GravatarId { get; set; } public string Url { get; set; } public string HtmlUrl { get; set; } public string FollowersUrl { get; set; } public string FollowingUrl { get; set; } public string GistsUrl { get; set; } public string StarredUrl { get; set; } public string SubscriptionsUrl { get; set; } public string OrganizationsUrl { get; set; } public string ReposUrl { get; set; } public string EventsUrl { get; set; } public string ReceivedEventsUrl { get; set; } public string Type { get; set; } public string Name { get; set; } public string Company { get; set; } public string Blog { get; set; } public string Location { get; set; } public string Email { get; set; } public bool? Hireable { get; set; } public string Bio { get; set; } public int PublicRepos { get; set; } public int Followers { get; set; } public int Following { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } public int PublicGists { get; set; } } public class UserSearchResult { public int TotalCount { get; set; } public bool IncompleteResults { get; set; } public IList Items { get; set; } } [Headers("User-Agent: Refit Integration Tests")] public interface IGitHubApi { [Get("/users/{username}")] Task GetUser(string userName); [Get("/users/{username}")] IObservable GetUserObservable(string userName); [Get("/users/{userName}")] IObservable GetUserCamelCase(string userName); [Get("/orgs/{orgname}/members")] Task> GetOrgMembers(string orgName, CancellationToken cancellationToken = default); [Get("/search/users")] Task FindUsers(string q); [Get("/")] Task GetIndex(); [Get("/")] IObservable GetIndexObservable(); [Get("/give-me-some-404-action")] Task NothingToSeeHere(); [Get("/give-me-some-404-action")] Task> NothingToSeeHereWithMetadata(); [Get("/users/{username}")] Task> GetUserWithMetadata(string userName); [Get("/users/{username}")] IObservable> GetUserObservableWithMetadata(string userName); [Get("/users/{username}")] IObservable> GetUserIApiResponseObservableWithMetadata(string userName); [Post("/users")] Task CreateUser(User user); [Post("/users")] Task> CreateUserWithMetadata(User user); } public interface IGitHubApiDisposable : IDisposable { [Get("whatever")] Task RefitMethod(); } public class TestNested { [Headers("User-Agent: Refit Integration Tests")] public interface INestedGitHubApi { [Get("/users/{username}")] Task GetUser(string userName); [Get("/users/{username}")] IObservable GetUserObservable(string userName); [Get("/users/{userName}")] IObservable GetUserCamelCase(string userName); [Get("/orgs/{orgname}/members")] Task> GetOrgMembers(string orgName); [Get("/search/users")] Task FindUsers(string q); [Get("/")] Task GetIndex(); [Get("/")] IObservable GetIndexObservable(); [Get("/give-me-some-404-action")] Task NothingToSeeHere(); } } ================================================ FILE: Refit.Tests/GlobalSuppressions.cs ================================================ // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage( "CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "", Scope = "member", Target = "~T:Refit.Tests.RequestBuilderTests" )] [assembly: SuppressMessage( "CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "", Scope = "member", Target = "~T:Refit.Tests.MultipartTests" )] ================================================ FILE: Refit.Tests/HttpClientFactoryExtensionsTests.cs ================================================ using System.Globalization; using System.Net.Http; using System.Reflection; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Refit.Implementation; using Xunit; namespace Refit.Tests; public class HttpClientFactoryExtensionsTests { class User { } class Role { } [Fact] public void GenericHttpClientsAreAssignedUniqueNames() { var services = new ServiceCollection(); var userClientName = services.AddRefitClient>().Name; var roleClientName = services.AddRefitClient>().Name; Assert.NotEqual(userClientName, roleClientName); } [Fact] public void HttpClientServicesAreDifferentThanKeyedServices() { var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient(); serviceCollection.AddKeyedRefitClient("keyed"); var serviceProvider = serviceCollection.BuildServiceProvider(); var nonKeyedService = serviceProvider.GetService(); var keyedService = serviceProvider.GetKeyedService("keyed"); Assert.NotNull(nonKeyedService); Assert.NotNull(keyedService); Assert.NotSame(nonKeyedService, keyedService); var nonKeyedSettings = serviceProvider.GetService>(); var keyedSettings = serviceProvider.GetKeyedService>("keyed"); Assert.NotSame(nonKeyedSettings, keyedSettings); var nonKeyedRequestBuilder = serviceProvider.GetService>(); var keyedRequestBuilder = serviceProvider.GetKeyedService>("keyed"); Assert.NotSame(nonKeyedRequestBuilder, keyedRequestBuilder); } [Fact] public void HttpClientServicesAreAddedCorrectlyGivenGenericArgument() { var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient(); Assert.Contains( serviceCollection, z => z.ServiceType == typeof(SettingsFor) ); Assert.Contains( serviceCollection, z => z.ServiceType == typeof(IRequestBuilder) ); } [Fact] public void HttpClientServicesAreAddedCorrectlyGivenTypeArgument() { var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute)); Assert.Contains( serviceCollection, z => z.ServiceType == typeof(SettingsFor) ); Assert.Contains( serviceCollection, z => z.ServiceType == typeof(IRequestBuilder) ); } [Fact] public void HttpClientReturnsClientGivenGenericArgument() { var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient(); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.NotNull(serviceProvider.GetService()); } [Fact] public void HttpClientReturnsClientGivenTypeArgument() { var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute)); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.NotNull(serviceProvider.GetService()); } [Fact] public void HttpClientSettingsAreInjectableGivenGenericArgument() { var serviceCollection = new ServiceCollection().Configure( o => o.Serializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()) ); serviceCollection.AddRefitClient( _ => new RefitSettings() { ContentSerializer = _.GetRequiredService< IOptions >().Value.Serializer } ); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.Same( serviceProvider.GetRequiredService>().Value.Serializer, serviceProvider .GetRequiredService>() .Settings!.ContentSerializer ); } [Fact] public void HttpClientSettingsAreInjectableGivenTypeArgument() { var serviceCollection = new ServiceCollection().Configure( o => o.Serializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()) ); serviceCollection.AddRefitClient( typeof(IFooWithOtherAttribute), _ => new RefitSettings() { ContentSerializer = _.GetRequiredService< IOptions >().Value.Serializer } ); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.Same( serviceProvider.GetRequiredService>().Value.Serializer, serviceProvider .GetRequiredService>() .Settings!.ContentSerializer ); } [Fact] public void HttpClientSettingsCanBeProvidedStaticallyGivenGenericArgument() { var contentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()); var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient( new RefitSettings() { ContentSerializer = contentSerializer } ); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.Same( contentSerializer, serviceProvider .GetRequiredService>() .Settings!.ContentSerializer ); } [Fact] public void HttpClientSettingsCanBeProvidedStaticallyGivenTypeArgument() { var contentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()); var serviceCollection = new ServiceCollection(); serviceCollection.AddRefitClient( new RefitSettings() { ContentSerializer = contentSerializer } ); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.Same( contentSerializer, serviceProvider .GetRequiredService>() .Settings!.ContentSerializer ); } [Fact] public void ProvidedHttpClientIsUsedAsNamedClient() { var baseUri = new Uri("https://0:1337"); var services = new ServiceCollection(); services.AddHttpClient("MyHttpClient", client => { client.BaseAddress = baseUri; client.DefaultRequestHeaders.Add("X-Powered-By", Environment.OSVersion.VersionString); }); services.AddRefitClient(settingsAction: null, "MyHttpClient"); var sp = services.BuildServiceProvider(); var httpClientFactory = sp.GetRequiredService(); var httpClient = httpClientFactory.CreateClient("MyHttpClient"); var gitHubApi = sp.GetRequiredService(); var memberInfos = typeof(Generated).GetMember("RefitTestsIGitHubApi", BindingFlags.NonPublic); var genApi = Convert.ChangeType(gitHubApi, (Type)memberInfos[0], CultureInfo.InvariantCulture); var genApiProperty = genApi.GetType().GetProperty("Client")!; var genApiClient = (HttpClient)genApiProperty.GetValue(genApi)!; Assert.NotSame(httpClient, genApiClient); Assert.Equal(httpClient.BaseAddress, genApiClient.BaseAddress); Assert.Equal(baseUri, genApiClient.BaseAddress); Assert.Contains( new KeyValuePair>("X-Powered-By", new[] { Environment.OSVersion.VersionString }), genApiClient.DefaultRequestHeaders); } class ClientOptions { public SystemTextJsonContentSerializer Serializer { get; set; } } } ================================================ FILE: Refit.Tests/IDefaultInterfaceMethodTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public interface IHaveDims { [Get("")] internal Task GetInternal(); // DIMs require C# 8.0 which requires .NET Core 3.x or .NET Standard 2.1 #if NETCOREAPP3_1_OR_GREATER private Task GetPrivate() { return GetInternal(); } Task GetDim() { return GetPrivate(); } static string GetStatic() { return nameof(IHaveDims); } #endif } // DIMs require C# 8.0 which requires .NET Core 3.x or .NET Standard 2.1 #if NETCOREAPP3_1_OR_GREATER public class DefaultInterfaceMethodTests { [Fact] public async Task InternalInterfaceMemberTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/") .Respond(HttpStatusCode.OK, "text/html", "OK"); var fixture = RestService.For("https://httpbin.org/", settings); var plainText = await fixture.GetInternal(); Assert.True(!string.IsNullOrWhiteSpace(plainText)); } [Fact] public async Task DimTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/") .Respond(HttpStatusCode.OK, "text/html", "OK"); var fixture = RestService.For("https://httpbin.org/", settings); var plainText = await fixture.GetDim(); Assert.True(!string.IsNullOrWhiteSpace(plainText)); } [Fact] public async Task InternalDimTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/") .Respond(HttpStatusCode.OK, "text/html", "OK"); var fixture = RestService.For("https://httpbin.org/", settings); var plainText = await fixture.GetInternal(); Assert.True(!string.IsNullOrWhiteSpace(plainText)); } [Fact] public void StaticInterfaceMethodTest() { var plainText = IHaveDims.GetStatic(); Assert.True(!string.IsNullOrWhiteSpace(plainText)); } } #endif ================================================ FILE: Refit.Tests/IFooWithOtherAttribute.cs ================================================ using System.ComponentModel; using System.Threading.Tasks; using Refit; interface IFooWithOtherAttribute { [Get("/")] Task GetRoot(); [DisplayName("/")] Task PostRoot(); } ================================================ FILE: Refit.Tests/IInterfaceWithoutRefit.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Refit.Tests; public interface IInterfaceWithoutRefit { Task DoSomething(); } ================================================ FILE: Refit.Tests/IServiceWithoutNamespace.cs ================================================ using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Refit; interface IServiceWithoutNamespace { [Get("/")] Task GetRoot(); [Post("/")] Task PostRoot(); } ================================================ FILE: Refit.Tests/InheritedGenericInterfacesApi.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Newtonsoft.Json; using Refit; // InterfaceStubGenerator looks for this using static System.Math; // This is here to verify https://github.com/reactiveui/refit/issues/283 namespace Refit.Tests; public class DataEntity { } public interface IDataApiA : IDataCrudApi { [Get("")] Task PingA(); } public interface IDataApiB : IDataCrudApi { [Get("")] Task PingB(); } public interface IDataCrudApi : IDataCrudApi where T : class { [Post("")] Task Copy([Body] T payload); } public interface IDataCrudApi where T : class { [Post("")] Task Create([Body] T payload); [Get("")] Task> ReadAll() where TFoo : new(); [Get("")] Task> ReadAll() where TFoo : new() where TBar : struct; [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body] T payload); [Delete("/{key}")] Task Delete(TKey key); [Get("")] Task ReadAllClasses() where TFoo : class, new(); } public class DatasetQueryItem where TResultRow : class, new() { [JsonProperty("global_id")] public long GlobalId { get; set; } public long Number { get; set; } [JsonProperty("Cells")] public TResultRow Value { get; set; } } public interface IDataMosApi { [Get("/datasets/{dataSet}/rows")] Task[]> GetDataSetItems() where TResulRow : class, new(); } ================================================ FILE: Refit.Tests/InheritedInterfacesApi.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this using Refit.Tests.SeparateNamespaceWithModel; using static System.Math; // This is here to verify https://github.com/reactiveui/refit/issues/283 namespace Refit.Tests { [Headers("User-Agent: Refit Integration Tests")] public interface IAmInterface : IAmInterfaceB, IAmInterfaceA { [Get("/get?result=Pang")] Task Pang(); } [Headers("User-Agent: Refit Integration Tests")] public interface IAmInterfaceA { [Get("/get?result=Ping")] Task Ping(); } [Headers("User-Agent: Refit Integration Tests")] public interface IAmInterfaceB : IAmInterfaceD { [Get("/get?result=Pong")] Task Pong(); } [Headers("User-Agent: Refit Integration Tests")] public interface IAmInterfaceC : IAmInterfaceB, IAmInterfaceA { [Get("/get?result=Pang")] Task Pang(); } public interface IAmInterfaceD { [Get("/get?result=Test")] Task Test(); } public interface IAmInterfaceF_RequireUsing { [Get("/get-requiring-using")] Task Get(List guids); } public interface IContainAandB : IAmInterfaceB, IAmInterfaceA { } public interface IAmInterfaceEWithNoRefit { public Task DoSomething(T parameter); public Task DoSomethingElse(); } public interface IImplementTheInterfaceAndUseRefit : IAmInterfaceEWithNoRefit { #pragma warning disable CS0108 // Member hides inherited member; missing new keyword [Get("/doSomething")] public Task DoSomething(int parameter); #pragma warning restore CS0108 // Member hides inherited member; missing new keyword [Get("/DoSomethingElse")] public new Task DoSomethingElse(); } public interface IImplementTheInterfaceAndDontUseRefit : IAmInterfaceD { #pragma warning disable CS0108 // Member hides inherited member; missing new keyword Task Test(); #pragma warning restore CS0108 // Member hides inherited member; missing new keyword } public interface IMyClient { [Get("/")] Task MyMethodAsync(string ex); } } namespace Refit.Tests.SeparateNamespaceWithModel { public class ResponseModel { } } ================================================ FILE: Refit.Tests/InheritedInterfacesInSeparateFileApi.cs ================================================ using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests.SeparateNamespace; public interface InheritedInterfacesInSeparateFileApi : IAmInterfaceF_RequireUsing { [Get("/get")] Task Get(int i); } ================================================ FILE: Refit.Tests/IntegrationTestHelper.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Refit.Tests; public static class IntegrationTestHelper { public static string GetPath(params string[] paths) { var ret = GetIntegrationTestRootDirectory(); return (new FileInfo(paths.Aggregate(ret, Path.Combine))).FullName; } public static string GetIntegrationTestRootDirectory([CallerFilePath] string filePath = default) { // XXX: This is an evil hack, but it's okay for a unit test // We can't use Assembly.Location because unit test runners love // to move stuff to temp directories var di = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(filePath))); return di.FullName; } } ================================================ FILE: Refit.Tests/InterfaceStubGenerator.cs ================================================ using System.Collections.Immutable; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using Refit.Generator; using VerifyTests.DiffPlex; using Task = System.Threading.Tasks.Task; namespace Refit.Tests; public class InterfaceStubGeneratorTests { static readonly MetadataReference RefitAssembly = MetadataReference.CreateFromFile( typeof(GetAttribute).Assembly.Location, documentation: XmlDocumentationProvider.CreateFromFile( Path.ChangeExtension(typeof(GetAttribute).Assembly.Location, ".xml") ) ); static readonly ReferenceAssemblies ReferenceAssemblies; static InterfaceStubGeneratorTests() { #if NET6_0 ReferenceAssemblies = ReferenceAssemblies.Net.Net60; #elif NET8_0 ReferenceAssemblies = ReferenceAssemblies.Net.Net80; #elif NET9_0 ReferenceAssemblies = ReferenceAssemblies.Net.Net90; #else ReferenceAssemblies = ReferenceAssemblies.Default.AddPackages( ImmutableArray.Create(new PackageIdentity("System.Text.Json", "7.0.2")) ); #endif #if NET48 ReferenceAssemblies = ReferenceAssemblies .AddAssemblies(ImmutableArray.Create("System.Web")) .AddPackages(ImmutableArray.Create(new PackageIdentity("System.Net.Http", "4.3.4"))); #endif } public static async Task VerifyGenerator(string input) { var assemblies = await ReferenceAssemblies.ResolveAsync(null, default); string[] inputs = [input]; var compilation = CSharpCompilation.Create( "compilation", inputs.Select(source => CSharpSyntaxTree.ParseText(File.ReadAllText(source))), assemblies.Add(RefitAssembly), new CSharpCompilationOptions(OutputKind.ConsoleApplication) ); #if NET48 var generator = new InterfaceStubGenerator(); #else var generator = new InterfaceStubGeneratorV2(); #endif var driver = CSharpGeneratorDriver.Create(generator); var ranDriver = driver.RunGenerators(compilation); return await Verify(ranDriver).ToTask(); } [Fact(Skip = "Generator in test issue")] public void GenerateInterfaceStubsSmokeTest() { var fixture = new InterfaceStubGenerator(); var driver = CSharpGeneratorDriver.Create(fixture); var inputCompilation = CreateCompilation( IntegrationTestHelper.GetPath("RestService.cs"), IntegrationTestHelper.GetPath("GitHubApi.cs"), IntegrationTestHelper.GetPath("InheritedInterfacesApi.cs"), IntegrationTestHelper.GetPath("InheritedGenericInterfacesApi.cs") ); var diags = inputCompilation.GetDiagnostics(); // Make sure we don't have any errors Assert.Empty(diags.Where(d => d.Severity == DiagnosticSeverity.Error)); var rundriver = driver.RunGeneratorsAndUpdateCompilation( inputCompilation, out var outputCompiliation, out var diagnostics ); var runResult = rundriver.GetRunResult(); var generated = runResult.Results[0]; var text = generated.GeneratedSources.First().SourceText.ToString(); Assert.Contains("IGitHubApi", text); Assert.Contains("IAmInterfaceC", text); } static CSharpCompilation CreateCompilation(params string[] sourceFiles) { var keyReferences = new[] { typeof(Binder), typeof(GetAttribute), typeof(RichardSzalay.MockHttp.MockHttpMessageHandler), typeof(System.Reactive.Unit), typeof(System.Linq.Enumerable), typeof(Newtonsoft.Json.JsonConvert), typeof(Xunit.FactAttribute), typeof(System.Net.Http.HttpContent), typeof(ModelObject), typeof(Attribute) }; return CSharpCompilation.Create( "compilation", sourceFiles.Select(source => CSharpSyntaxTree.ParseText(File.ReadAllText(source))), keyReferences.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)), new CSharpCompilationOptions(OutputKind.ConsoleApplication) ); } [Fact] public async Task NoRefitInterfacesSmokeTest() { var path = IntegrationTestHelper.GetPath("IInterfaceWithoutRefit.cs"); await VerifyGenerator(path); } [Fact] public async Task FindInterfacesSmokeTest() { var path = IntegrationTestHelper.GetPath("GitHubApi.cs"); await VerifyGenerator(path); } [Fact] public async Task GenerateInterfaceStubsWithoutNamespaceSmokeTest() { var path = IntegrationTestHelper.GetPath("IServiceWithoutNamespace.cs"); await VerifyGenerator(path); } } public static class ThisIsDumbButMightHappen { public const string PeopleDoWeirdStuff = "But we don't let them"; } public interface IAmARefitInterfaceButNobodyUsesMe { [Get("whatever")] Task RefitMethod(); [Refit.GetAttribute("something-else")] Task AnotherRefitMethod(); [Get(ThisIsDumbButMightHappen.PeopleDoWeirdStuff)] Task NoConstantsAllowed(); [Get("spaces-shouldnt-break-me")] Task SpacesShouldntBreakMe(); // We don't need an explicit test for this because if it isn't supported we can't compile [Get("anything")] Task ReservedWordsForParameterNames(int @int, string @string, float @long); } public interface IAmNotARefitInterface { Task NotARefitMethod(); } public interface IBoringCrudApi where T : class { [Post("")] Task Create([Body] T paylod); [Get("")] Task> ReadAll(); [Get("/{key}")] Task ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body] T payload); [Delete("/{key}")] Task Delete(TKey key); } public interface INonGenericInterfaceWithGenericMethod { [Post("")] Task PostMessage([Body] T message) where T : IMessage; [Post("")] Task PostMessage([Body] T message, U param1, V param2) where T : IMessage where U : T; } public interface IMessage; ================================================ FILE: Refit.Tests/MethodOverloads.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Refit; using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public interface IUseOverloadedMethods { [Get("")] Task Get(); [Get("/status/{httpstatuscode}")] Task Get(int httpstatuscode); } public interface IUseOverloadedGenericMethods where TResponse : class where THeader : struct { [Get("")] Task Get(); [Get("/get")] Task Get(TParam param, [Header("X-Refit")] THeader header); [Get("/get")] Task Get(THeader param, [Header("X-Refit")] TParam header); [Get("/status/{httpstatuscode}")] Task Get(int httpstatuscode); [Get("/get")] Task Get(int someVal); [Get("/get")] Task Get(TInput input); [Get("/get")] Task Get(TInput1 input1, TInput2 input2); } public class MethodOverladTests { [Fact] public async Task BasicMethodOverloadTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/") .Respond(HttpStatusCode.OK, "text/html", "OK"); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/status/403") .Respond(HttpStatusCode.Forbidden); var fixture = RestService.For("https://httpbin.org/", settings); var plainText = await fixture.Get(); var resp = await fixture.Get(403); Assert.True(!string.IsNullOrWhiteSpace(plainText)); Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode); } [Fact] public async Task GenericMethodOverloadTest1() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/") .Respond(HttpStatusCode.OK, "text/html", "OK"); var fixture = RestService.For>( "https://httpbin.org/", settings ); var plainText = await fixture.Get(); Assert.True(!string.IsNullOrWhiteSpace(plainText)); } [Fact] public async Task GenericMethodOverloadTest2() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/status/403") .Respond(HttpStatusCode.Forbidden); var fixture = RestService.For>( "https://httpbin.org/", settings ); var resp = await fixture.Get(403); Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode); } [Fact] public async Task GenericMethodOverloadTest3() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithQueryString("someVal", "201") .Respond("application/json", "some-T-value"); var fixture = RestService.For>( "https://httpbin.org/", settings ); var result = await fixture.Get(201); Assert.Equal("some-T-value", result); } [Fact] public async Task GenericMethodOverloadTest4() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithHeaders("X-Refit", "99") .WithQueryString("param", "foo") .Respond( "application/json", "{\"url\": \"https://httpbin.org/get\", \"args\": {\"param\": \"foo\"}}" ); var fixture = RestService.For>( "https://httpbin.org/", settings ); var result = await fixture.Get("foo", 99); Assert.Equal("foo", result.Args["param"]); } [Fact] public async Task GenericMethodOverloadTest5() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithHeaders("X-Refit", "foo") .WithQueryString("param", "99") .Respond( "application/json", "{\"url\": \"https://httpbin.org/get\", \"args\": {\"param\": \"99\"}}" ); var fixture = RestService.For>( "https://httpbin.org/", settings ); var result = await fixture.Get(99, "foo"); Assert.Equal("99", result.Args["param"]); } [Fact] public async Task GenericMethodOverloadTest6() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithQueryString("input", "99") .Respond("application/json", "generic-output"); var fixture = RestService.For>( "https://httpbin.org/", settings ); var result = await fixture.Get(99); Assert.Equal("generic-output", result); } [Fact] public async Task GenericMethodOverloadTest7() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithQueryString( new Dictionary() { { "input1", "str" }, { "input2", "3" } } ) .Respond("application/json", "Ok"); var fixture = RestService.For>( "https://httpbin.org/", settings ); await fixture.Get("str", 3); mockHttp.VerifyNoOutstandingExpectation(); } } ================================================ FILE: Refit.Tests/ModuleInitializer.cs ================================================ using System.Runtime.CompilerServices; using VerifyTests.DiffPlex; namespace Refit.Tests; static class ModuleInitializer { // ModuleInitializer should only be used in apps #pragma warning disable CA2255 [ModuleInitializer] #pragma warning restore CA2255 public static void Init() { DerivePathInfo((file, _, type, method) => new(Path.Combine(Path.GetDirectoryName(file), "_snapshots"), type.Name, method.Name)); VerifySourceGenerators.Initialize(); VerifyDiffPlex.Initialize(OutputType.Compact); } } ================================================ FILE: Refit.Tests/ModuleInitializerAttribute.cs ================================================ // ReSharper disable once CheckNamespace namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] sealed class ModuleInitializerAttribute : Attribute; ================================================ FILE: Refit.Tests/MultipartTests.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Refit; using Xunit; namespace Refit.Tests; public interface IRunscopeApi { [Multipart] [Post("/")] Task UploadStream(Stream stream); [Multipart("-----SomeCustomBoundary")] [Post("/")] Task UploadStreamWithCustomBoundary(Stream stream); [Multipart] [Post("/")] Task UploadStreamPart(StreamPart stream); [Multipart] [Post("/")] Task UploadStreamPart( [Query] ModelObject someQueryParams, StreamPart stream ); [Multipart] [Post("/")] Task UploadBytes(byte[] bytes); [Multipart] [Post("/")] Task UploadBytesPart( [AliasAs("ByteArrayPartParamAlias")] ByteArrayPart bytes ); [Multipart] [Post("/")] Task UploadString([AliasAs("SomeStringAlias")] string someString); [Multipart] [Post("/")] Task UploadStringWithHeaderAndRequestProperty( [Header("Authorization")] string authorization, [Property("SomeProperty")] string someProperty, [AliasAs("SomeStringAlias")] string someString ); [Multipart] [Post("/")] Task UploadFileInfo(IEnumerable fileInfos, FileInfo anotherFile); [Multipart] [Post("/")] Task UploadFileInfoPart( IEnumerable fileInfos, FileInfoPart anotherFile ); [Multipart] [Post("/")] Task UploadJsonObject(ModelObject theObject); [Multipart] [Post("/")] Task UploadJsonObjects(IEnumerable theObjects); [Multipart] [Post("/")] Task UploadMixedObjects( IEnumerable theObjects, AnotherModel anotherModel, FileInfo aFile, AnEnum anEnum, string aString, int anInt ); [Multipart] [Post("/")] Task UploadHttpContent(HttpContent content); } public class ModelObject { public string Property1 { get; set; } public string Property2 { get; set; } } public class AnotherModel { public string[] Foos { get; set; } } public enum AnEnum { Val1, Val2 } public class MultipartTests { const string BaseAddress = "https://api/"; [Fact] public async Task MultipartUploadShouldWorkWithStream() { var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("stream", parts[0].Headers.ContentDisposition.Name); Assert.Equal("stream", parts[0].Headers.ContentDisposition.FileName); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using var stream = GetTestFileStream("Test Files/Test.pdf"); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadStream(stream); } [Fact] public async Task MultipartUploadShouldWorkWithStreamAndCustomBoundary() { var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("stream", parts[0].Headers.ContentDisposition.Name); Assert.Equal("stream", parts[0].Headers.ContentDisposition.FileName); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using (var stream = GetTestFileStream("Test Files/Test.pdf")) { var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadStreamWithCustomBoundary(stream); } var input = typeof(IRunscopeApi); var methodFixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == "UploadStreamWithCustomBoundary") ); Assert.Equal("-----SomeCustomBoundary", methodFixture.MultipartBoundary); } [Fact] public async Task MultipartUploadShouldWorkWithByteArray() { var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("bytes", parts[0].Headers.ContentDisposition.Name); Assert.Equal("bytes", parts[0].Headers.ContentDisposition.FileName); Assert.Null(parts[0].Headers.ContentType); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using var stream = GetTestFileStream("Test Files/Test.pdf"); using var reader = new BinaryReader(stream); var bytes = reader.ReadBytes((int)stream.Length); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadBytes(bytes); } [Fact] public async Task MultipartUploadShouldWorkWithFileInfo() { var fileName = Path.GetTempFileName(); var name = Path.GetFileName(fileName); var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Equal(3, parts.Count); Assert.Equal("fileInfos", parts[0].Headers.ContentDisposition.Name); Assert.Equal(name, parts[0].Headers.ContentDisposition.FileName); Assert.Null(parts[0].Headers.ContentType); using (var str = await parts[0].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } Assert.Equal("fileInfos", parts[1].Headers.ContentDisposition.Name); Assert.Equal(name, parts[1].Headers.ContentDisposition.FileName); Assert.Null(parts[1].Headers.ContentType); using (var str = await parts[1].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } Assert.Equal("anotherFile", parts[2].Headers.ContentDisposition.Name); Assert.Equal(name, parts[2].Headers.ContentDisposition.FileName); Assert.Null(parts[2].Headers.ContentType); using (var str = await parts[2].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; try { using var stream = GetTestFileStream("Test Files/Test.pdf"); using var outStream = File.OpenWrite(fileName); await stream.CopyToAsync(outStream); await outStream.FlushAsync(); outStream.Close(); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadFileInfo( new[] { new FileInfo(fileName), new FileInfo(fileName) }, new FileInfo(fileName) ); } finally { File.Delete(fileName); } } [Fact] public async Task MultipartUploadShouldWorkWithString() { const string text = "This is random text"; var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("SomeStringAlias", parts[0].Headers.ContentDisposition.Name); Assert.Null(parts[0].Headers.ContentDisposition.FileName); Assert.Equal("text/plain", parts[0].Headers.ContentType.MediaType); Assert.Equal("utf-8", parts[0].Headers.ContentType.CharSet); var str = await parts[0].ReadAsStringAsync(); Assert.Equal(text, str); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadString(text); } [Fact] public async Task MultipartUploadShouldWorkWithHeaderAndRequestProperty() { const string text = "This is random text"; const string someHeader = "someHeader"; const string someProperty = "someProperty"; var handler = new MockHttpMessageHandler { RequestAsserts = message => { Assert.Equal(someHeader, message.Headers.Authorization.ToString()); #if NET6_0_OR_GREATER Assert.Equal(3, message.Options.Count()); Assert.Equal( someProperty, ((IDictionary)message.Options)["SomeProperty"] ); #endif #pragma warning disable CS0618 // Type or member is obsolete Assert.Equal(3, message.Properties.Count); Assert.Equal(someProperty, message.Properties["SomeProperty"]); #pragma warning restore CS0618 // Type or member is obsolete }, Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("SomeStringAlias", parts[0].Headers.ContentDisposition.Name); Assert.Null(parts[0].Headers.ContentDisposition.FileName); Assert.Equal("text/plain", parts[0].Headers.ContentType.MediaType); Assert.Equal("utf-8", parts[0].Headers.ContentType.CharSet); var str = await parts[0].ReadAsStringAsync(); Assert.Equal(text, str); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadStringWithHeaderAndRequestProperty( someHeader, someProperty, text ); } [Fact] public async Task MultipartUploadShouldWorkWithStreamPart() { var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("stream", parts[0].Headers.ContentDisposition.Name); Assert.Equal("test-streampart.pdf", parts[0].Headers.ContentDisposition.FileName); Assert.Equal("application/pdf", parts[0].Headers.ContentType.MediaType); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using var stream = GetTestFileStream("Test Files/Test.pdf"); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadStreamPart( new StreamPart(stream, "test-streampart.pdf", "application/pdf") ); } [Fact] public async Task MultipartUploadShouldWorkWithStreamPartWithNamedMultipart() { var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("test-stream", parts[0].Headers.ContentDisposition.Name); Assert.Equal("test-streampart.pdf", parts[0].Headers.ContentDisposition.FileName); Assert.Equal("application/pdf", parts[0].Headers.ContentType.MediaType); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using var stream = GetTestFileStream("Test Files/Test.pdf"); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadStreamPart( new StreamPart(stream, "test-streampart.pdf", "application/pdf", "test-stream") ); } [Fact] public async Task MultipartUploadShouldWorkWithStreamPartAndQuery() { var handler = new MockHttpMessageHandler { RequestAsserts = request => { Assert.Equal("?Property1=test&Property2=test2", request.RequestUri.Query); }, Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("stream", parts[0].Headers.ContentDisposition.Name); Assert.Equal("test-streampart.pdf", parts[0].Headers.ContentDisposition.FileName); Assert.Equal("application/pdf", parts[0].Headers.ContentType.MediaType); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using var stream = GetTestFileStream("Test Files/Test.pdf"); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadStreamPart( new ModelObject() { Property1 = "test", Property2 = "test2" }, new StreamPart(stream, "test-streampart.pdf", "application/pdf") ); } [Fact] public async Task MultipartUploadShouldWorkWithByteArrayPart() { var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("ByteArrayPartParamAlias", parts[0].Headers.ContentDisposition.Name); Assert.Equal( "test-bytearraypart.pdf", parts[0].Headers.ContentDisposition.FileName ); Assert.Equal("application/pdf", parts[0].Headers.ContentType.MediaType); using var str = await parts[0].ReadAsStreamAsync(); using var src = GetTestFileStream("Test Files/Test.pdf"); Assert.True(StreamsEqual(src, str)); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; using var stream = GetTestFileStream("Test Files/Test.pdf"); using var reader = new BinaryReader(stream); var bytes = reader.ReadBytes((int)stream.Length); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadBytesPart( new ByteArrayPart(bytes, "test-bytearraypart.pdf", "application/pdf") ); } [Fact] public async Task MultipartUploadShouldWorkWithFileInfoPart() { var fileName = Path.GetTempFileName(); var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Equal(3, parts.Count); Assert.Equal("fileInfos", parts[0].Headers.ContentDisposition.Name); Assert.Equal("test-fileinfopart.pdf", parts[0].Headers.ContentDisposition.FileName); Assert.Equal("application/pdf", parts[0].Headers.ContentType.MediaType); using (var str = await parts[0].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } Assert.Equal("fileInfos", parts[1].Headers.ContentDisposition.Name); Assert.Equal( "test-fileinfopart2.pdf", parts[1].Headers.ContentDisposition.FileName ); Assert.Null(parts[1].Headers.ContentType); using (var str = await parts[1].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } Assert.Equal("anotherFile", parts[2].Headers.ContentDisposition.Name); Assert.Equal("additionalfile.pdf", parts[2].Headers.ContentDisposition.FileName); Assert.Equal("application/pdf", parts[2].Headers.ContentType.MediaType); using (var str = await parts[2].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; try { using var stream = GetTestFileStream("Test Files/Test.pdf"); using var outStream = File.OpenWrite(fileName); await stream.CopyToAsync(outStream); await outStream.FlushAsync(); outStream.Close(); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadFileInfoPart( new[] { new FileInfoPart( new FileInfo(fileName), "test-fileinfopart.pdf", "application/pdf" ), new FileInfoPart( new FileInfo(fileName), "test-fileinfopart2.pdf", contentType: null ) }, new FileInfoPart( new FileInfo(fileName), fileName: "additionalfile.pdf", contentType: "application/pdf" ) ); } finally { File.Delete(fileName); } } [Theory] [InlineData(typeof(NewtonsoftJsonContentSerializer), "application/json")] [InlineData(typeof(SystemTextJsonContentSerializer), "application/json")] [InlineData(typeof(XmlContentSerializer), "application/xml")] public async Task MultipartUploadShouldWorkWithAnObject( Type contentSerializerType, string mediaType ) { if ( Activator.CreateInstance(contentSerializerType) is not IHttpContentSerializer serializer ) { throw new ArgumentException( $"{contentSerializerType.FullName} does not implement {nameof(IHttpContentSerializer)}" ); } var model1 = new ModelObject { Property1 = "M1.prop1", Property2 = "M1.prop2" }; var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("theObject", parts[0].Headers.ContentDisposition.Name); Assert.Null(parts[0].Headers.ContentDisposition.FileName); Assert.Equal(mediaType, parts[0].Headers.ContentType.MediaType); var result0 = await serializer .FromHttpContentAsync(parts[0]) .ConfigureAwait(false); Assert.Equal(model1.Property1, result0.Property1); Assert.Equal(model1.Property2, result0.Property2); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, ContentSerializer = serializer }; var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadJsonObject(model1); } [Theory] [InlineData(typeof(NewtonsoftJsonContentSerializer), "application/json")] [InlineData(typeof(SystemTextJsonContentSerializer), "application/json")] [InlineData(typeof(XmlContentSerializer), "application/xml")] public async Task MultipartUploadShouldWorkWithObjects( Type contentSerializerType, string mediaType ) { if ( Activator.CreateInstance(contentSerializerType) is not IHttpContentSerializer serializer ) { throw new ArgumentException( $"{contentSerializerType.FullName} does not implement {nameof(IHttpContentSerializer)}" ); } var model1 = new ModelObject { Property1 = "M1.prop1", Property2 = "M1.prop2" }; var model2 = new ModelObject { Property1 = "M2.prop1" }; var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Equal(2, parts.Count); Assert.Equal("theObjects", parts[0].Headers.ContentDisposition.Name); Assert.Null(parts[0].Headers.ContentDisposition.FileName); Assert.Equal(mediaType, parts[0].Headers.ContentType.MediaType); var result0 = await serializer .FromHttpContentAsync(parts[0]) .ConfigureAwait(false); Assert.Equal(model1.Property1, result0.Property1); Assert.Equal(model1.Property2, result0.Property2); Assert.Equal("theObjects", parts[1].Headers.ContentDisposition.Name); Assert.Null(parts[1].Headers.ContentDisposition.FileName); Assert.Equal(mediaType, parts[1].Headers.ContentType.MediaType); var result1 = await serializer .FromHttpContentAsync(parts[1]) .ConfigureAwait(false); Assert.Equal(model2.Property1, result1.Property1); Assert.Equal(model2.Property2, result1.Property2); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler, ContentSerializer = serializer }; var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadJsonObjects(new[] { model1, model2 }); } [Fact] public async Task MultipartUploadShouldWorkWithMixedTypes() { var fileName = Path.GetTempFileName(); var name = Path.GetFileName(fileName); var model1 = new ModelObject { Property1 = "M1.prop1", Property2 = "M1.prop2" }; var model2 = new ModelObject { Property1 = "M2.prop1" }; var anotherModel = new AnotherModel { Foos = new[] { "bar1", "bar2" } }; var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Equal(7, parts.Count); Assert.Equal("theObjects", parts[0].Headers.ContentDisposition.Name); Assert.Null(parts[0].Headers.ContentDisposition.FileName); Assert.Equal("application/json", parts[0].Headers.ContentType.MediaType); var result0 = JsonConvert.DeserializeObject( await parts[0].ReadAsStringAsync().ConfigureAwait(false) ); Assert.Equal(model1.Property1, result0.Property1); Assert.Equal(model1.Property2, result0.Property2); Assert.Equal("theObjects", parts[1].Headers.ContentDisposition.Name); Assert.Null(parts[1].Headers.ContentDisposition.FileName); Assert.Equal("application/json", parts[1].Headers.ContentType.MediaType); var result1 = JsonConvert.DeserializeObject( await parts[1].ReadAsStringAsync().ConfigureAwait(false) ); Assert.Equal(model2.Property1, result1.Property1); Assert.Equal(model2.Property2, result1.Property2); Assert.Equal("anotherModel", parts[2].Headers.ContentDisposition.Name); Assert.Null(parts[2].Headers.ContentDisposition.FileName); Assert.Equal("application/json", parts[2].Headers.ContentType.MediaType); var result2 = JsonConvert.DeserializeObject( await parts[2].ReadAsStringAsync().ConfigureAwait(false) ); Assert.Equal(2, result2.Foos.Length); Assert.Equal("bar1", result2.Foos[0]); Assert.Equal("bar2", result2.Foos[1]); Assert.Equal("aFile", parts[3].Headers.ContentDisposition.Name); Assert.Equal(name, parts[3].Headers.ContentDisposition.FileName); Assert.Null(parts[3].Headers.ContentType); using (var str = await parts[3].ReadAsStreamAsync()) using (var src = GetTestFileStream("Test Files/Test.pdf")) { Assert.True(StreamsEqual(src, str)); } Assert.Equal("anEnum", parts[4].Headers.ContentDisposition.Name); Assert.Null(parts[4].Headers.ContentDisposition.FileName); Assert.Equal("application/json", parts[4].Headers.ContentType.MediaType); var result4 = JsonConvert.DeserializeObject( await parts[4].ReadAsStringAsync().ConfigureAwait(false) ); Assert.Equal(AnEnum.Val2, result4); Assert.Equal("aString", parts[5].Headers.ContentDisposition.Name); Assert.Null(parts[5].Headers.ContentDisposition.FileName); Assert.Equal("text/plain", parts[5].Headers.ContentType.MediaType); Assert.Equal("utf-8", parts[5].Headers.ContentType.CharSet); Assert.Equal("frob", await parts[5].ReadAsStringAsync()); Assert.Equal("anInt", parts[6].Headers.ContentDisposition.Name); Assert.Null(parts[6].Headers.ContentDisposition.FileName); Assert.Equal("application/json", parts[6].Headers.ContentType.MediaType); var result6 = JsonConvert.DeserializeObject( await parts[6].ReadAsStringAsync().ConfigureAwait(false) ); Assert.Equal(42, result6); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; try { using var stream = GetTestFileStream("Test Files/Test.pdf"); using var outStream = File.OpenWrite(fileName); await stream.CopyToAsync(outStream); await outStream.FlushAsync(); outStream.Close(); var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadMixedObjects( new[] { model1, model2 }, anotherModel, new FileInfo(fileName), AnEnum.Val2, "frob", 42 ); } finally { File.Delete(fileName); } } [Fact] public async Task MultipartUploadShouldWorkWithHttpContent() { var httpContent = new StringContent("some text", Encoding.ASCII, "application/custom"); httpContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { Name = "myName", FileName = "myFileName", }; var handler = new MockHttpMessageHandler { Asserts = async content => { var parts = content.ToList(); Assert.Single(parts); Assert.Equal("myName", parts[0].Headers.ContentDisposition.Name); Assert.Equal("myFileName", parts[0].Headers.ContentDisposition.FileName); Assert.Equal("application/custom", parts[0].Headers.ContentType.MediaType); var result0 = await parts[0].ReadAsStringAsync().ConfigureAwait(false); Assert.Equal("some text", result0); } }; var settings = new RefitSettings() { HttpMessageHandlerFactory = () => handler }; var fixture = RestService.For(BaseAddress, settings); var result = await fixture.UploadHttpContent(httpContent); } [Fact] public void MultiPartConstructorShouldThrowArgumentNullExceptionWhenNoFileName() { Assert.Throws(() => { var byteArrayPart = new ByteArrayPart(Array.Empty(), null, "application/pdf"); }); } [Fact] public void FileInfoPartConstructorShouldThrowArgumentNullExceptionWhenNoFileInfo() { Assert.Throws(() => { var fileInfoPart = new FileInfoPart(null, "file.pdf", "application/pdf"); }); } internal static Stream GetTestFileStream(string relativeFilePath) { const char namespaceSeparator = '.'; // get calling assembly var assembly = Assembly.GetCallingAssembly(); // compute resource name suffix var relativeName = "." + relativeFilePath .Replace('\\', namespaceSeparator) .Replace('/', namespaceSeparator) .Replace(' ', '_'); // get resource stream var fullName = assembly .GetManifestResourceNames() .FirstOrDefault(name => name.EndsWith(relativeName, StringComparison.InvariantCulture)); if (fullName == null) { throw new Exception( $"Unable to find resource for path \"{relativeFilePath}\". Resource with name ending on \"{relativeName}\" was not found in assembly." ); } var stream = assembly.GetManifestResourceStream(fullName); if (stream == null) { throw new Exception( $"Unable to find resource for path \"{relativeFilePath}\". Resource named \"{fullName}\" was not found in assembly." ); } return stream; } static bool StreamsEqual(Stream a, Stream b) { if (a == null && b == null) return true; if (a == null || b == null) { throw new ArgumentNullException(a == null ? "a" : "b"); } if (a.Length < b.Length) return false; if (a.Length > b.Length) return false; for (var i = 0; i < a.Length; i++) { var aByte = a.ReadByte(); var bByte = b.ReadByte(); if (aByte != bByte) return false; } return true; } class MockHttpMessageHandler : HttpMessageHandler { public Action RequestAsserts { get; set; } public Func Asserts { get; set; } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { RequestAsserts?.Invoke(request); var content = request.Content as MultipartFormDataContent; Assert.IsType(content); Assert.NotNull(Asserts); await Asserts(content); return new HttpResponseMessage(HttpStatusCode.OK); } } } ================================================ FILE: Refit.Tests/MyQueryParams.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace Refit.Tests; public record MySimpleQueryParams { public string FirstName { get; set; } [AliasAs("lName")] public string LastName { get; set; } } public class MyComplexQueryParams { public string FirstName { get; set; } public string LastName { get; set; } [AliasAs("Addr")] public Address Address { get; set; } = new Address(); public Dictionary MetaData { get; set; } = new Dictionary(); public List Other { get; set; } = new List(); } public record Address { [AliasAs("Zip")] public int Postcode { get; set; } public string Street { get; set; } } ================================================ FILE: Refit.Tests/NamespaceCollisionApi.cs ================================================ using System.Threading.Tasks; using CollisionB; using Refit; // InterfaceStubGenerator looks for this using SomeType = CollisionA.SomeType; namespace Refit.Tests { public interface INamespaceCollisionApi { [Get("/")] Task SomeRequest(); } public static class NamespaceCollisionApi { public static INamespaceCollisionApi Create() { return RestService.For("http://somewhere.com"); } } } namespace CollisionA { public class SomeType { } public interface INamespaceCollisionApi { [Get("/")] Task SomeRequest(); } } namespace CollisionB { public class SomeType { } public interface INamespaceCollisionApi { [Get("/")] Task SomeRequest(); } } ================================================ FILE: Refit.Tests/NamespaceOverlapApi.cs ================================================ using System; using System.Threading.Tasks; using Common.Helper; // InterfaceStubGenerator looks for this using Refit; using Refit.Tests.Common; namespace Refit.Tests { [SomeHelper] public interface INamespaceOverlapApi { [Get("/")] Task SomeRequest(); } public static class NamespaceOverlapApi { public static INamespaceOverlapApi Create() { return RestService.For("http://somewhere.com"); } } } namespace Common.Helper { public class SomeHelperAttribute : Attribute { } } namespace Refit.Tests.Common { public class SomeOtherType { } } ================================================ FILE: Refit.Tests/NamespaceWithGlobalAliasApi.cs ================================================ using global::System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests { using global::Refit.Tests.SomeNamespace; public interface NamespaceWithGlobalAliasApi { [Get("/")] Task SomeRequest(); } } namespace Refit.Tests.SomeNamespace { public class SomeType { } } ================================================ FILE: Refit.Tests/NullableReferenceTypes.cs ================================================ #nullable enable using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests; interface IGenericWithResultService { [Get("/")] Task Get(); } interface IGenericWithNullableValueService { [Get("/")] Task Get(); } interface IGenericNullableReferenceService { [Get("/")] Task? Get(); } interface IGenericNullableValueService { [Get("/")] ValueTask? Get(); } interface IGenericNullableWithNullableReferenceService { [Get("/")] Task? Get(); } interface IGenericNullableWithNullableValueService { [Get("/")] ValueTask? Get(); } interface INullableReferenceService { [Get("/")] string? Get(); } interface INullableValueService { [Get("/")] int? Get(); } interface IReferenceAndValueParametersService { [Get("/")] Task Get(string? reference, int? value); } interface IGenericNullableReferenceParameterService { [Get("/")] Task Get(System.Collections.Generic.List? reference); } interface IGenericWithNullableReferenceParameterService { [Get("/")] Task Get(System.Collections.Generic.List reference); } interface IGenericNullableWithNullableReferenceParameterService { [Get("/")] Task Get(System.Collections.Generic.List? reference); } interface ICustomNullableReferenceService { [Get("/")] CustomReferenceType? Get(); } interface ICustomNullableValueService { [Get("/")] CustomValueType? Get(); } interface ICustomReferenceAndValueParametersService { [Get("/")] Task Get(CustomReferenceType? reference, CustomValueType? value); } class CustomReferenceType { } class CustomValueType { } ================================================ FILE: Refit.Tests/PartialInterfacesApi.First.cs ================================================ using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests; public partial interface PartialInterfacesApi { [Get("/get?result=First")] Task First(); } ================================================ FILE: Refit.Tests/PartialInterfacesApi.Second.cs ================================================ using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests; public partial interface PartialInterfacesApi { [Get("/get?result=Second")] Task Second(); } ================================================ FILE: Refit.Tests/ReducedUsingInsideNamespaceApi.cs ================================================ using System.Threading.Tasks; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests { using ModelNamespace; public interface IReducedUsingInsideNamespaceApi { [Get("/")] Task SomeRequest(); } } namespace Refit.Tests.ModelNamespace { public class SomeType { } } ================================================ FILE: Refit.Tests/Refit.Tests.csproj ================================================  $(RefitTestTargets) false https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json $(NoWarn);CS1591;CA1819;CA2000;CA2007;CA1056;CA1707;CA1861;CA1515;xUnit1031 6.0.0 false %(RecursiveDir)\resources\%(Filename)%(Extension) Always ================================================ FILE: Refit.Tests/RefitSettings.cs ================================================ using Xunit; namespace Refit.Tests; public class RefitSettingsTests { [Fact] public void Can_CreateRefitSettings_WithoutException() { var contentSerializer = new NewtonsoftJsonContentSerializer(); var urlParameterFormatter = new DefaultUrlParameterFormatter(); var urlParameterKeyFormatter = new CamelCaseUrlParameterKeyFormatter(); var formUrlEncodedParameterFormatter = new DefaultFormUrlEncodedParameterFormatter(); var exception = Record.Exception(() => new RefitSettings()); Assert.Null(exception); exception = Record.Exception(() => new RefitSettings(contentSerializer)); Assert.Null(exception); exception = Record.Exception( () => new RefitSettings(contentSerializer, urlParameterFormatter) ); Assert.Null(exception); exception = Record.Exception( () => new RefitSettings( contentSerializer, urlParameterFormatter, formUrlEncodedParameterFormatter ) ); Assert.Null(exception); exception = Record.Exception( () => new RefitSettings( contentSerializer, urlParameterFormatter, formUrlEncodedParameterFormatter, urlParameterKeyFormatter ) ); Assert.Null(exception); } } ================================================ FILE: Refit.Tests/ReflectionTests.cs ================================================ using System.Net.Http; using System.Reflection; using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; public interface IBasicApi { [Get("/{value}")] Task GetParam(string value); [Get("/{value}")] Task GetDerivedParam(BaseRecord value); [Get("/{value.PropValue}")] Task GetPropertyParam(MyParams value); [Get("/{value}")] Task GetGenericParam(T value); [Get("/")] Task GetQuery(string queryKey); [Get("/")] Task GetGenericQuery(T queryKey); [Get("/")] Task GetPropertyQuery(BaseRecord queryKey); [Get("/")] Task GetEnumerableQuery(IEnumerable enums); [Get("/")] Task GetEnumerablePropertyQuery(MyEnumerableParams enums); [Get("/")] Task GetDictionaryQuery(IDictionary dict); } public record DerivedRecordWithProperty(string Name) : BaseRecord("value"); public record DerivedRecord(string Value) : BaseRecord(Value); public record BaseRecord(string Value); public record MyParams(string PropValue); public record MyEnumerableParams(int[] Enumerable); public class TestUrlFormatter : IUrlParameterFormatter { private readonly ICustomAttributeProvider[] expectedAttributeProviders; private readonly Type[] expectedTypes; private int index; public TestUrlFormatter(ICustomAttributeProvider expectedAttributeProvider, Type expectedType) { expectedAttributeProviders = [expectedAttributeProvider]; expectedTypes = [expectedType]; } public TestUrlFormatter( ICustomAttributeProvider[] expectedAttributeProviders, Type[] expectedTypes ) { this.expectedAttributeProviders = expectedAttributeProviders; this.expectedTypes = expectedTypes; } public string Format(object value, ICustomAttributeProvider attributeProvider, Type type) { Assert.Equal(expectedAttributeProviders[index], attributeProvider); Assert.Equal(expectedTypes[index], type); index++; return value!.ToString(); } public void AssertNoOutstandingAssertions() { Assert.Equal(expectedAttributeProviders.Length, index); Assert.Equal(expectedTypes.Length, index); } } public sealed class ReflectionTests : IDisposable { readonly MockHttpMessageHandler mockHandler = new(); [Fact] public async Task UrlParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/bar") .Respond("application/json", nameof(IBasicApi.GetParam)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetParam))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetParam("bar"); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task DerivedUrlParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/DerivedRecord%20%7B%20Value%20%3D%20Derived%20%7D") .Respond("application/json", nameof(IBasicApi.GetDerivedParam)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetDerivedParam))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter(parameterInfo, typeof(BaseRecord)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetDerivedParam(new DerivedRecord("Derived")); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task PropertyParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/propVal") .Respond("application/json", nameof(IBasicApi.GetPropertyParam)); var propertyInfo = typeof(MyParams).GetProperties()[0]; var formatter = new TestUrlFormatter(propertyInfo, typeof(string)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetPropertyParam(new MyParams("propVal")); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task GenericParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/genericVal") .Respond("application/json", nameof(IBasicApi.GetGenericParam)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetGenericParam))!; var stringMethod = methodInfo.MakeGenericMethod(typeof(string)); var parameterInfo = stringMethod.GetParameters()[0]; var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetGenericParam("genericVal"); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task QueryParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString( new[] { new KeyValuePair("queryKey", "queryValue"), } ) .Respond("application/json", nameof(IBasicApi.GetQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetQuery))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetQuery("queryValue"); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task QueryPropertyParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString(new[] { new KeyValuePair("Value", "queryVal"), }) .Respond("application/json", nameof(IBasicApi.GetPropertyQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetPropertyQuery))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter(parameterInfo, typeof(BaseRecord)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetPropertyQuery(new BaseRecord("queryVal")); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task DerivedQueryPropertyParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString( new[] { new KeyValuePair("Name", "queryName"), new KeyValuePair("Value", "value"), } ) .Respond("application/json", nameof(IBasicApi.GetPropertyQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetPropertyQuery))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter( [parameterInfo, parameterInfo], [typeof(BaseRecord), typeof(BaseRecord)] ); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetPropertyQuery(new DerivedRecordWithProperty("queryName")); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task GenericQueryParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString( new[] { new KeyValuePair("queryKey", "queryValue"), } ) .Respond("application/json", nameof(IBasicApi.GetGenericQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetGenericQuery))!; var stringMethod = methodInfo.MakeGenericMethod(typeof(string)); var parameterInfo = stringMethod.GetParameters()[0]; var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetGenericQuery("queryValue"); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task EnumerableQueryParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString(new[] { new KeyValuePair("enums", "k0,k1"), }) .Respond("application/json", nameof(IBasicApi.GetEnumerableQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetEnumerableQuery))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter( [parameterInfo, parameterInfo], [typeof(IEnumerable), typeof(IEnumerable)] ); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetEnumerableQuery(["k0", "k1"]); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task EnumerablePropertyQueryParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString(new[] { new KeyValuePair("Enumerable", "0,1"), }) .Respond("application/json", nameof(IBasicApi.GetEnumerablePropertyQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetEnumerablePropertyQuery))!; var parameterInfo = methodInfo.GetParameters()[0]; var propertyInfo = typeof(MyEnumerableParams).GetProperties()[0]; var formatter = new TestUrlFormatter( [propertyInfo, propertyInfo, parameterInfo], [typeof(int[]), typeof(int[]), typeof(MyEnumerableParams)] ); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); await service.GetEnumerablePropertyQuery(new MyEnumerableParams([0, 1])); formatter.AssertNoOutstandingAssertions(); } [Fact] public async Task QueryDictionaryParameterShouldBeExpectedReflection() { mockHandler .Expect(HttpMethod.Get, "https://foo/") .WithExactQueryString( new[] { new KeyValuePair("key0", "1"), new KeyValuePair("key1", "2"), } ) .Respond("application/json", nameof(IBasicApi.GetDictionaryQuery)); var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetDictionaryQuery))!; var parameterInfo = methodInfo.GetParameters()[0]; var formatter = new TestUrlFormatter( [typeof(string), typeof(string), parameterInfo, parameterInfo], [ typeof(string), typeof(string), typeof(IDictionary), typeof(IDictionary) ] ); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, UrlParameterFormatter = formatter }; var service = RestService.For("https://foo", settings); var dict = new Dictionary { { "key0", 1 }, { "key1", 2 } }; await service.GetDictionaryQuery(dict); formatter.AssertNoOutstandingAssertions(); } public void Dispose() { mockHandler?.Dispose(); } } ================================================ FILE: Refit.Tests/RequestBuilder.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.WebUtilities; using Xunit; namespace Refit.Tests { [Headers("User-Agent: RefitTestClient", "Api-Version: 1")] public interface IRestMethodInfoTests { [Get("@)!@_!($_!@($\\\\|||::::")] Task GarbagePath(); [Get("/foo/bar/{id}")] Task FetchSomeStuffMissingParameters(); [Get("/foo/bar/{id}")] Task FetchSomeStuff(int id); [Get("/foo/bar/{id}?param1={id}¶m2={id}")] Task FetchSomeStuffWithTheSameId(int id); [Get("/foo/bar?param=first {id} and second {id}")] Task FetchSomeStuffWithTheIdInAParameterMultipleTimes(int id); [Get("/foo/bar/{**path}/{id}")] Task FetchSomeStuffWithRoundTrippingParam(string path, int id); [Get("/foo/bar/{**path}/{id}")] Task FetchSomeStuffWithNonStringRoundTrippingParam(int path, int id); [Get("/foo/bar/{id}?baz=bamf")] Task FetchSomeStuffWithHardcodedQueryParam(int id); [Get("/foo/bar/{id}?baz=bamf")] Task FetchSomeStuffWithQueryParam(int id, string search); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithAlias([AliasAs("id")] int anId); [Get("/foo/bar/{width}x{height}")] Task FetchAnImage(int width, int height); [Get("/foo/bar/{id}")] IObservable FetchSomeStuffWithBody( [AliasAs("id")] int anId, [Body] Dictionary theData ); [Post("/foo/bar/{id}")] IObservable PostSomeUrlEncodedStuff( [AliasAs("id")] int anId, [Body(BodySerializationMethod.UrlEncoded)] Dictionary theData ); [Get("/foo/bar/{id}")] IObservable FetchSomeStuffWithAuthorizationSchemeSpecified( [AliasAs("id")] int anId, [Authorize("Bearer")] string token ); [Get("/foo/bar/{id}")] [Headers("Api-Version: 2", "Accept: application/json")] Task FetchSomeStuffWithHardcodedHeaders(int id); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeader( int id, [Header("Authorization")] string authorization ); [Get("/foo")] Task FetchSomeStuffWithDynamicHeaderQueryParamAndArrayQueryParam( [Header("Authorization")] string authorization, int id, [Query(CollectionFormat.Multi)] string[] someArray, [Property("SomeProperty")] object someValue ); #region [HeaderCollection] interface methods [Get("/foo/bar/{id}")] [Headers( "Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json" )] Task FetchSomeStuffWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithCustomHeaderCollection( int id, [Body] object body, [HeaderCollection] IDictionary headers ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithCustomHeaderCollection( int id, [Body] object body, [HeaderCollection] IDictionary headers ); [Patch("/foo/bar/{id}")] Task PatchSomeStuffWithCustomHeaderCollection( int id, [Body] object body, [HeaderCollection] IDictionary headers ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithoutBodyAndCustomHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithoutBodyAndCustomHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Patch("/foo/bar/{id}")] Task PatchSomeStuffWithoutBodyAndCustomHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithInferredBodyAndWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers, object inferredBody ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithInferredBodyAndWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers, object inferredBody ); [Patch("/foo/bar/{id}")] Task PatchSomeStuffWithInferredBodyAndWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers, object inferredBody ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeaderCollectionAndAuthorize( int id, [Authorize] string value, [HeaderCollection] IDictionary headers ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithDynamicHeaderCollectionAndAuthorize( int id, [Authorize] string value, [HeaderCollection] IDictionary headers ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader( int id, [Header("Authorization")] string value, [HeaderCollection] IDictionary headers ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithDynamicHeaderCollectionAndDynamicHeader( int id, [Header("Authorization")] string value, [HeaderCollection] IDictionary headers ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeaderOrderFlipped( int id, [HeaderCollection] IDictionary headers, [Header("Authorization")] string value ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection( [Header("X-PathMember")] int id, [HeaderCollection] IDictionary headers ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection( [Header("X-PathMember")] int id, [HeaderCollection] IDictionary headers ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithHeaderCollection( int id, [HeaderCollection] IDictionary headers, int baz ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithHeaderCollection( int id, [HeaderCollection] IDictionary headers, int baz ); [Get("/foo/bar")] Task FetchSomeStuffWithDuplicateHeaderCollection( [HeaderCollection] IDictionary headers, [HeaderCollection] IDictionary headers2 ); [Post("/foo/bar")] Task PostSomeStuffWithDuplicateHeaderCollection( [HeaderCollection] IDictionary headers, [HeaderCollection] IDictionary headers2 ); [Get("/foo")] Task FetchSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam( [HeaderCollection] IDictionary headers, int id, [Query(CollectionFormat.Multi)] string[] someArray, [Property("SomeProperty")] object someValue ); [Post("/foo")] Task PostSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam( [HeaderCollection] IDictionary headers, int id, [Query(CollectionFormat.Multi)] string[] someArray, [Property("SomeProperty")] object someValue ); [Get("/foo")] Task FetchSomeStuffWithHeaderCollectionOfUnsupportedType( [HeaderCollection] string headers ); [Post("/foo")] Task PostSomeStuffWithHeaderCollectionOfUnsupportedType( [HeaderCollection] string headers ); #endregion #region [Property] interface methods [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithDynamicRequestProperty( int id, [Body] object body, [Property("SomeProperty")] object someValue ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithDynamicRequestProperties( int id, [Body] object body, [Property("SomeProperty")] object someValue, [Property("SomeOtherProperty")] object someOtherValue ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithoutBodyAndWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithoutBodyAndWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue ); [Patch("/foo/bar/{id}")] Task PatchSomeStuffWithoutBodyAndWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithInferredBodyAndWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue, object inferredBody ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithInferredBodyAndWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue, object inferredBody ); [Patch("/foo/bar/{id}")] Task PatchSomeStuffWithInferredBodyAndWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someValue, object inferredBody ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithDuplicateKey( int id, [Property("SomeProperty")] object someValue1, [Property("SomeProperty")] object someValue2 ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithoutKey( int id, [Property] object someValue, [Property("")] object someOtherValue ); #endregion [Post("/foo/{id}")] Task OhYeahValueTypes(int id, [Body(buffered: true)] int whatever); [Post("/foo/{id}")] Task OhYeahValueTypesUnbuffered(int id, [Body(buffered: false)] int whatever); [Post("/foo/{id}")] Task PullStreamMethod(int id, [Body(buffered: true)] Dictionary theData); [Post("/foo/{id}")] Task VoidPost(int id); [Post("/foo/{id}")] string AsyncOnlyBuddy(int id); [Patch("/foo/{id}")] IObservable PatchSomething(int id, [Body] string someAttribute); [Options("/foo/{id}")] Task SendOptions(int id, [Body] string someAttribute); [Post("/foo/{id}")] Task> PostReturnsApiResponse(int id); [Post("/foo/{id}")] Task PostReturnsNonApiResponse(int id); [Post("/foo")] Task PostWithBodyDetected(Dictionary theData); [Get("/foo")] Task GetWithBodyDetected(Dictionary theData); [Put("/foo")] Task PutWithBodyDetected(Dictionary theData); [Patch("/foo")] Task PatchWithBodyDetected(Dictionary theData); [Post("/foo")] Task TooManyComplexTypes(Dictionary theData, Dictionary theData1); [Post("/foo")] Task ManyComplexTypes( Dictionary theData, [Body] Dictionary theData1 ); [Post("/foo")] Task PostWithDictionaryQuery([Query] Dictionary theData); [Post("/foo")] Task PostWithComplexTypeQuery([Query] ComplexQueryObject queryParams); [Post("/foo")] Task ImpliedComplexQueryType( ComplexQueryObject queryParams, [Body] Dictionary theData1 ); [Get("/api/{id}")] Task MultipleQueryAttributes( int id, [Query] string text = null, [Query] int? optionalId = null, [Query(CollectionFormat = CollectionFormat.Multi)] string[] filters = null ); [Get("/api/{id}")] Task NullableValues( int id, string text = null, int? optionalId = null, [Query(CollectionFormat = CollectionFormat.Multi)] string[] filters = null ); [Get("/api/{id}")] Task IEnumerableThrowingError([Query(CollectionFormat.Multi)] IEnumerable values); [Get("/foo")] List InvalidGenericReturnType(); } public enum TestEnum { A, B, C } public class ComplexQueryObject { [AliasAs("test-query-alias")] public string TestAlias1 { get; set; } public string TestAlias2 { get; set; } public IEnumerable TestCollection { get; set; } [AliasAs("test-dictionary-alias")] public Dictionary TestAliasedDictionary { get; set; } public Dictionary TestDictionary { get; set; } [AliasAs("listOfEnumMulti")] [Query(CollectionFormat.Multi)] public List EnumCollectionMulti { get; set; } [Query(CollectionFormat.Multi)] public List ObjectCollectionMulti { get; set; } [Query(CollectionFormat.Csv)] public List EnumCollectionCsv { get; set; } [AliasAs("listOfObjectsCsv")] [Query(CollectionFormat.Csv)] public List ObjectCollectionCcv { get; set; } [IgnoreDataMember] public string InternalUseOnlyIgnoredByDataMember { get; set; } [System.Text.Json.Serialization.JsonIgnore] public string InternalUseOnlyIgnoredBySystemTextJson { get; set; } } public class RestMethodInfoTests { [Fact] public void TooManyComplexTypesThrows() { var input = typeof(IRestMethodInfoTests); Assert.Throws(() => { var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.TooManyComplexTypes)) ); }); } [Fact] public void ManyComplexTypes() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.ManyComplexTypes)) ); Assert.Single(fixture.QueryParameterMap); Assert.NotNull(fixture.BodyParameterInfo); Assert.Equal(1, fixture.BodyParameterInfo.Item3); } [Theory] [InlineData(nameof(IRestMethodInfoTests.PutWithBodyDetected))] [InlineData(nameof(IRestMethodInfoTests.PostWithBodyDetected))] [InlineData(nameof(IRestMethodInfoTests.PatchWithBodyDetected))] public void DefaultBodyParameterDetected(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Empty(fixture.QueryParameterMap); Assert.NotNull(fixture.BodyParameterInfo); } [Fact] public void DefaultBodyParameterNotDetectedForGet() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.GetWithBodyDetected)) ); Assert.Single(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void PostWithDictionaryQueryParameter() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.PostWithDictionaryQuery)) ); Assert.Single(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void PostWithObjectQueryParameterHasSingleQueryParameterValue() { var input = typeof(IRestMethodInfoTests); var fixtureParams = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.PostWithComplexTypeQuery)) ); Assert.Single(fixtureParams.QueryParameterMap); Assert.Equal("queryParams", fixtureParams.QueryParameterMap[0]); Assert.Null(fixtureParams.BodyParameterInfo); } [Fact] public void PostWithObjectQueryParameterHasCorrectQuerystring() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var param = new ComplexQueryObject { TestAlias1 = "one", TestAlias2 = "two" }; var output = factory(new object[] { param }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?test-query-alias=one&TestAlias2=two", uri.PathAndQuery); } [Fact] public void PostWithObjectQueryParameterSkipsIgnoredProperties() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var param = new ComplexQueryObject { TestAlias1 = "one", InternalUseOnlyIgnoredByDataMember = "nope", InternalUseOnlyIgnoredBySystemTextJson = "nope" }; var output = factory(new object[] { param }); Assert.Equal("/foo?test-query-alias=one", output.RequestUri.PathAndQuery); } [Fact] public void PostWithObjectQueryParameterWithEnumList_Multi() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var param = new ComplexQueryObject { EnumCollectionMulti = new List { TestEnum.A, TestEnum.B } }; var output = factory(new object[] { param }); Assert.Equal( "/foo?listOfEnumMulti=A&listOfEnumMulti=B", output.RequestUri.PathAndQuery ); } [Fact] public void PostWithObjectQueryParameterWithObjectListWithProvidedEnumValues_Multi() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var param = new ComplexQueryObject { ObjectCollectionMulti = new List { TestEnum.A, TestEnum.B } }; var output = factory(new object[] { param }); Assert.Equal( "/foo?ObjectCollectionMulti=A&ObjectCollectionMulti=B", output.RequestUri.PathAndQuery ); } [Fact] public void PostWithObjectQueryParameterWithEnumList_Csv() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var param = new ComplexQueryObject { EnumCollectionCsv = new List { TestEnum.A, TestEnum.B } }; var output = factory(new object[] { param }); Assert.Equal("/foo?EnumCollectionCsv=A%2CB", output.RequestUri.PathAndQuery); } [Fact] public void PostWithObjectQueryParameterWithObjectListWithProvidedEnumValues_Csv() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.PostWithComplexTypeQuery) ); var param = new ComplexQueryObject { ObjectCollectionCcv = new List { TestEnum.A, TestEnum.B } }; var output = factory(new object[] { param }); Assert.Equal("/foo?listOfObjectsCsv=A%2CB", output.RequestUri.PathAndQuery); } [Fact] public void ObjectQueryParameterWithInnerCollectionHasCorrectQuerystring() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexTypeQueryWithInnerCollection) ); var param = new ComplexQueryObject { TestCollection = new[] { 1, 2, 3 } }; var output = factory(new object[] { param }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?TestCollection=1%2C2%2C3", uri.PathAndQuery); } [Fact] public void MultipleQueryAttributesWithNulls() { var input = typeof(IRestMethodInfoTests); var fixtureParams = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.MultipleQueryAttributes)) ); Assert.Equal(3, fixtureParams.QueryParameterMap.Count); } [Fact] public void GarbagePathsShouldThrow() { var shouldDie = true; try { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.GarbagePath)) ); } catch (ArgumentException) { shouldDie = false; } Assert.False(shouldDie); } [Fact] public void MissingParametersShouldBlowUp() { var shouldDie = true; try { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffMissingParameters) ) ); } catch (ArgumentException) { shouldDie = false; } Assert.False(shouldDie); } [Fact] public void ParameterMappingSmokeTest() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuff)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void ParameterMappingWithTheSameIdInAFewPlaces() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithTheSameId)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void ParameterMappingWithTheSameIdInTheQueryParameter() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithTheIdInAParameterMultipleTimes ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void ParameterMappingWithRoundTrippingSmokeTest() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithRoundTrippingParam) ) ); Assert.Equal("path", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.RoundTripping, fixture.ParameterMap[0].Type); Assert.Equal("id", fixture.ParameterMap[1].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[1].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void ParameterMappingWithNonStringRoundTrippingShouldThrow() { var input = typeof(IRestMethodInfoTests); Assert.Throws(() => { var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithNonStringRoundTrippingParam ) ) ); }); } [Fact] public void ParameterMappingWithQuerySmokeTest() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithQueryParam)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Equal("search", fixture.QueryParameterMap[1]); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void ParameterMappingWithHardcodedQuerySmokeTest() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithHardcodedQueryParam) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void AliasMappingShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithAlias)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void MultipleParametersPerSegmentShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchAnImage)) ); Assert.Equal("width", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Equal("height", fixture.ParameterMap[1].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[1].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); } [Fact] public void FindTheBodyParameter() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithBody)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.NotNull(fixture.BodyParameterInfo); Assert.Empty(fixture.QueryParameterMap); Assert.Equal(1, fixture.BodyParameterInfo.Item3); } [Fact] public void FindTheAuthorizeParameter() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithAuthorizationSchemeSpecified ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.NotNull(fixture.AuthorizeParameterInfo); Assert.Empty(fixture.QueryParameterMap); Assert.Equal(1, fixture.AuthorizeParameterInfo.Item2); } [Fact] public void AllowUrlEncodedContent() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.PostSomeUrlEncodedStuff)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.NotNull(fixture.BodyParameterInfo); Assert.Empty(fixture.QueryParameterMap); Assert.Equal(BodySerializationMethod.UrlEncoded, fixture.BodyParameterInfo.Item1); } [Fact] public void HardcodedHeadersShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithHardcodedHeaders) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.True( fixture.Headers.ContainsKey("Api-Version"), "Headers include Api-Version header" ); Assert.Equal("2", fixture.Headers["Api-Version"]); Assert.True( fixture.Headers.ContainsKey("User-Agent"), "Headers include User-Agent header" ); Assert.Equal("RefitTestClient", fixture.Headers["User-Agent"]); Assert.True(fixture.Headers.ContainsKey("Accept"), "Headers include Accept header"); Assert.Equal("application/json", fixture.Headers["Accept"]); Assert.Equal(3, fixture.Headers.Count); } [Fact] public void DynamicHeadersShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeader) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Equal("Authorization", fixture.HeaderParameterMap[1]); Assert.True( fixture.Headers.ContainsKey("User-Agent"), "Headers include User-Agent header" ); Assert.Equal("RefitTestClient", fixture.Headers["User-Agent"]); Assert.Equal(2, fixture.Headers.Count); } #region [HeaderCollection] Tests [Fact] public void DynamicHeaderCollectionShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollection ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.True( fixture.Headers.ContainsKey("Authorization"), "Headers include Authorization header" ); Assert.Equal( "SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", fixture.Headers["Authorization"] ); Assert.True(fixture.Headers.ContainsKey("Accept"), "Headers include Accept header"); Assert.Equal("application/json", fixture.Headers["Accept"]); Assert.True( fixture.Headers.ContainsKey("User-Agent"), "Headers include User-Agent header" ); Assert.Equal("RefitTestClient", fixture.Headers["User-Agent"]); Assert.True( fixture.Headers.ContainsKey("Api-Version"), "Headers include Api-Version header" ); Assert.Equal("1", fixture.Headers["Api-Version"]); Assert.Equal(4, fixture.Headers.Count); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(1)); } [Theory] [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithCustomHeaderCollection))] [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithCustomHeaderCollection))] [InlineData(nameof(IRestMethodInfoTests.PatchSomeStuffWithCustomHeaderCollection))] public void DynamicHeaderCollectionShouldWorkWithBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Empty(fixture.PropertyParameterMap); Assert.NotNull(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(2)); } [Theory] [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithoutBodyAndCustomHeaderCollection))] [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithoutBodyAndCustomHeaderCollection))] [InlineData( nameof(IRestMethodInfoTests.PatchSomeStuffWithoutBodyAndCustomHeaderCollection) )] public void DynamicHeaderCollectionShouldWorkWithoutBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(1)); } [Theory] [InlineData( nameof(IRestMethodInfoTests.PutSomeStuffWithInferredBodyAndWithDynamicHeaderCollection) )] [InlineData( nameof(IRestMethodInfoTests.PostSomeStuffWithInferredBodyAndWithDynamicHeaderCollection) )] [InlineData( nameof( IRestMethodInfoTests.PatchSomeStuffWithInferredBodyAndWithDynamicHeaderCollection ) )] public void DynamicHeaderCollectionShouldWorkWithInferredBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Empty(fixture.PropertyParameterMap); Assert.NotNull(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(1)); Assert.Equal(2, fixture.BodyParameterInfo.Item3); } [Theory] [InlineData( nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndAuthorize) )] [InlineData( nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicHeaderCollectionAndAuthorize) )] public void DynamicHeaderCollectionShouldWorkWithAuthorize(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.NotNull(fixture.AuthorizeParameterInfo); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(2)); } [Theory] [InlineData( nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader) )] [InlineData( nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicHeaderCollectionAndDynamicHeader) )] public void DynamicHeaderCollectionShouldWorkWithDynamicHeader(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.AuthorizeParameterInfo); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Single(fixture.HeaderParameterMap); Assert.Equal("Authorization", fixture.HeaderParameterMap[1]); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(2)); input = typeof(IRestMethodInfoTests); fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeaderOrderFlipped ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.AuthorizeParameterInfo); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Single(fixture.HeaderParameterMap); Assert.Equal("Authorization", fixture.HeaderParameterMap[2]); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(1)); } [Theory] [InlineData( nameof( IRestMethodInfoTests.FetchSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection ) )] [InlineData( nameof( IRestMethodInfoTests.PostSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection ) )] public void DynamicHeaderCollectionShouldWorkWithPathMemberDynamicHeader( string interfaceMethodName ) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Null(fixture.AuthorizeParameterInfo); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Single(fixture.HeaderParameterMap); Assert.Equal("X-PathMember", fixture.HeaderParameterMap[0]); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(1)); } [Theory] [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollection))] [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithHeaderCollection))] public void DynamicHeaderCollectionInMiddleOfParamsShouldWork(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Null(fixture.AuthorizeParameterInfo); Assert.Empty(fixture.PropertyParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Equal("baz", fixture.QueryParameterMap[2]); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(1)); } [Theory] [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithDuplicateHeaderCollection))] [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithDuplicateHeaderCollection))] public void DynamicHeaderCollectionShouldOnlyAllowOne(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); Assert.Throws( () => new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ) ); } [Theory] [InlineData( nameof( IRestMethodInfoTests.FetchSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam ) )] [InlineData( nameof( IRestMethodInfoTests.PostSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam ) )] public void DynamicHeaderCollectionShouldWorkWithProperty(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Null(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.Equal(2, fixture.QueryParameterMap.Count); Assert.Equal("id", fixture.QueryParameterMap[1]); Assert.Equal("someArray", fixture.QueryParameterMap[2]); Assert.Single(fixture.PropertyParameterMap); Assert.True(fixture.HasHeaderCollection); Assert.True(fixture.HeaderCollectionAt(0)); } [Theory] [InlineData( nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollectionOfUnsupportedType) )] [InlineData( nameof(IRestMethodInfoTests.PostSomeStuffWithHeaderCollectionOfUnsupportedType) )] public void DynamicHeaderCollectionShouldOnlyWorkWithSupportedSemantics( string interfaceMethodName ) { var input = typeof(IRestMethodInfoTests); Assert.Throws( () => new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ) ); } #endregion #region [Property] Tests [Fact] public void DynamicRequestPropertiesShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicRequestProperty) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); } [Fact] public void DynamicRequestPropertyShouldWorkWithBody() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicRequestProperty) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.NotNull(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.False(fixture.HasHeaderCollection); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[2]); } [Fact] public void DynamicRequestPropertiesShouldWorkWithBody() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.PostSomeStuffWithDynamicRequestProperties ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.NotNull(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.False(fixture.HasHeaderCollection); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[2]); Assert.Equal("SomeOtherProperty", fixture.PropertyParameterMap[3]); } [Theory] [InlineData( nameof(IRestMethodInfoTests.PutSomeStuffWithoutBodyAndWithDynamicRequestProperty) )] [InlineData( nameof(IRestMethodInfoTests.PostSomeStuffWithoutBodyAndWithDynamicRequestProperty) )] [InlineData( nameof(IRestMethodInfoTests.PatchSomeStuffWithoutBodyAndWithDynamicRequestProperty) )] public void DynamicRequestPropertyShouldWorkWithoutBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.False(fixture.HasHeaderCollection); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); } [Theory] [InlineData( nameof(IRestMethodInfoTests.PutSomeStuffWithInferredBodyAndWithDynamicRequestProperty) )] [InlineData( nameof(IRestMethodInfoTests.PostSomeStuffWithInferredBodyAndWithDynamicRequestProperty) )] [InlineData( nameof(IRestMethodInfoTests.PatchSomeStuffWithInferredBodyAndWithDynamicRequestProperty) )] public void DynamicRequestPropertyShouldWorkWithInferredBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == interfaceMethodName) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.NotNull(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); Assert.False(fixture.HasHeaderCollection); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); Assert.Equal(2, fixture.BodyParameterInfo.Item3); } [Fact] public void DynamicRequestPropertiesWithoutKeysShouldDefaultKeyToParameterName() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithDynamicRequestPropertyWithoutKey ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Equal("someValue", fixture.PropertyParameterMap[1]); Assert.Equal("someOtherValue", fixture.PropertyParameterMap[2]); } [Fact] public void DynamicRequestPropertiesWithDuplicateKeysDontBlowUp() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithDynamicRequestPropertyWithDuplicateKey ) ) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Empty(fixture.HeaderParameterMap); Assert.Null(fixture.BodyParameterInfo); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); Assert.Equal("SomeProperty", fixture.PropertyParameterMap[2]); } #endregion [Fact] public void ValueTypesDontBlowUpBuffered() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.OhYeahValueTypes)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Equal(BodySerializationMethod.Default, fixture.BodyParameterInfo.Item1); Assert.True(fixture.BodyParameterInfo.Item2); // buffered default Assert.Equal(1, fixture.BodyParameterInfo.Item3); Assert.Equal(typeof(bool), fixture.ReturnResultType); } [Fact] public void ValueTypesDontBlowUpUnBuffered() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.OhYeahValueTypesUnbuffered)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Equal(BodySerializationMethod.Default, fixture.BodyParameterInfo.Item1); Assert.False(fixture.BodyParameterInfo.Item2); // unbuffered specified Assert.Equal(1, fixture.BodyParameterInfo.Item3); Assert.Equal(typeof(bool), fixture.ReturnResultType); } [Fact] public void StreamMethodPullWorks() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.PullStreamMethod)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); Assert.Equal(BodySerializationMethod.Default, fixture.BodyParameterInfo.Item1); Assert.True(fixture.BodyParameterInfo.Item2); Assert.Equal(1, fixture.BodyParameterInfo.Item3); Assert.Equal(typeof(bool), fixture.ReturnResultType); } [Fact] public void ReturningTaskShouldWork() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.VoidPost)) ); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Equal(typeof(Task), fixture.ReturnType); Assert.Equal(typeof(void), fixture.ReturnResultType); } [Fact] public void SyncMethodsShouldThrow() { var shouldDie = true; try { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.AsyncOnlyBuddy)) ); } catch (ArgumentException) { shouldDie = false; } Assert.False(shouldDie); } [Fact] public void UsingThePatchAttributeSetsTheCorrectMethod() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PatchSomething)) ); Assert.Equal("PATCH", fixture.HttpMethod.Method); } [Fact] public void UsingOptionsAttribute() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input.GetMethods().First(x => x.Name == nameof(IDummyHttpApi.SendOptions)) ); Assert.Equal("OPTIONS", fixture.HttpMethod.Method); } [Fact] public void ApiResponseShouldBeSet() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.PostReturnsApiResponse)) ); Assert.True(fixture.IsApiResponse); } [Fact] public void ApiResponseShouldNotBeSet() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First(x => x.Name == nameof(IRestMethodInfoTests.PostReturnsNonApiResponse)) ); Assert.False(fixture.IsApiResponse); } [Fact] public void ParameterMappingWithHeaderQueryParamAndQueryArrayParam() { var input = typeof(IRestMethodInfoTests); var fixture = new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof( IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderQueryParamAndArrayQueryParam ) ) ); Assert.Equal("GET", fixture.HttpMethod.Method); Assert.Equal(2, fixture.QueryParameterMap.Count); Assert.Single(fixture.HeaderParameterMap); Assert.Single(fixture.PropertyParameterMap); } [Fact] public void GenericReturnTypeIsNotTaskOrObservableShouldThrow() { var input = typeof(IRestMethodInfoTests); Assert.Throws( () => new RestMethodInfoInternal( input, input .GetMethods() .First( x => x.Name == nameof(IRestMethodInfoTests.InvalidGenericReturnType) ) ) ); } } [Headers("User-Agent: RefitTestClient", "Api-Version: 1")] public interface IDummyHttpApi { [Get("/foo/bar/{id}")] Task> FetchSomeStringWithMetadata(int id); [Get("/foo/bar/{id}")] Task FetchSomeStuff(int id); [Get("/foo/bar/{**path}/{id}")] Task FetchSomeStuffWithRoundTrippingParam(string path, int id); [Get("/foo/bar/{id}?baz=bamf")] Task FetchSomeStuffWithHardcodedQueryParameter(int id); [Get("/foo/bar/{id}?baz=bamf")] Task FetchSomeStuffWithHardcodedAndOtherQueryParameters( int id, [AliasAs("search_for")] string searchQuery ); [Get("/{id}/{width}x{height}/foo")] Task FetchSomethingWithMultipleParametersPerSegment(int id, int width, int height); [Get("/foo/bar/{id}")] [Headers("Api-Version: 2", "Accept: application/json")] Task FetchSomeStuffWithHardcodedHeaders(int id); [Get("/foo/bar/{id}")] [Headers("Api-Version")] Task FetchSomeStuffWithNullHardcodedHeader(int id); [Get("/foo/bar/{id}")] [Headers("Api-Version: ")] Task FetchSomeStuffWithEmptyHardcodedHeader(int id); [Get("/foo/bar/{id}?param1={id}¶m2={id}")] Task FetchSomeStuffWithTheSameId(int id); [Get("/foo/bar?param=first {id} and second {id}")] Task FetchSomeStuffWithTheIdInAParameterMultipleTimes(int id); [Get("/foo?q=app_metadata.id:\"{id}\"")] Task FetchSomeStuffWithDoubleQuotesInUrl(int id); [Get("/foo/bar/({id})")] Task GetWithTrainingParenthesis(int id); [Get("/foo/bar/{id}/")] Task GetWithTrailingSlash(int id); [Post("/foo/bar/{id}")] [Headers("Content-Type: literally/anything")] Task PostSomeStuffWithHardCodedContentTypeHeader(int id, [Body] string content); [Get("/foo/bar/{id}")] [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==")] Task FetchSomeStuffWithDynamicHeader( int id, [Header("Authorization")] string authorization ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithCustomHeader(int id, [Header("X-Emoji")] string custom); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithPathMemberInCustomHeader( [Header("X-PathMember")] int id, [Header("X-Emoji")] string custom ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithCustomHeader( int id, [Body] object body, [Header("X-Emoji")] string emoji ); [Get("/foo/bar/{id}")] [Headers( "Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json" )] Task FetchSomeStuffWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Delete("/foo/bar/{id}")] [Headers( "Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json" )] Task DeleteSomeStuffWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Put("/foo/bar/{id}")] [Headers( "Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json" )] Task PutSomeStuffWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Post("/foo/bar/{id}")] [Headers( "Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json" )] Task PostSomeStuffWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Patch("/foo/bar/{id}")] [Headers( "Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json" )] Task PatchSomeStuffWithDynamicHeaderCollection( int id, [HeaderCollection] IDictionary headers ); [Get("/foo/bar/{id}")] [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader( int id, [Header("Authorization")] string value, [HeaderCollection] IDictionary headers ); [Get("/foo/bar/{id}")] [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeaderOrderFlipped( int id, [HeaderCollection] IDictionary headers, [Header("Authorization")] string value ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someProperty ); [Delete("/foo/bar/{id}")] Task DeleteSomeStuffWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someProperty ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someProperty ); [Post("/foo/bar/{id}")] Task PostSomeStuffWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someProperty ); [Patch("/foo/bar/{id}")] Task PatchSomeStuffWithDynamicRequestProperty( int id, [Property("SomeProperty")] object someProperty ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithDuplicateKey( int id, [Property("SomeProperty")] object someValue1, [Property("SomeProperty")] object someValue2 ); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithoutKey( int id, [Property] object someValue, [Property("")] object someOtherValue ); [Get("/string")] Task FetchSomeStuffWithoutFullPath(); [Get("/void")] Task FetchSomeStuffWithVoid(); [Get("/void/{id}/path")] Task FetchSomeStuffWithVoidAndQueryAlias( string id, [AliasAs("a")] string valueA, [AliasAs("b")] string valueB ); [Get("/foo")] Task FetchSomeStuffWithNonFormattableQueryParams(bool b, char c); [Post("/foo/bar/{id}")] Task PostSomeUrlEncodedStuff( int id, [Body(BodySerializationMethod.UrlEncoded)] object content ); [Post("/foo/bar/{id}")] Task PostSomeAliasedUrlEncodedStuff( int id, [Body(BodySerializationMethod.UrlEncoded)] SomeRequestData content ); string SomeOtherMethod(); [Put("/foo/bar/{id}")] Task PutSomeContentWithAuthorization( int id, [Body] object content, [Header("Authorization")] string authorization ); [Put("/foo/bar/{id}")] Task PutSomeStuffWithDynamicContentType( int id, [Body] string content, [Header("Content-Type")] string contentType ); [Post("/foo/bar/{id}")] Task PostAValueType(int id, [Body] Guid? content); [Patch("/foo/bar/{id}")] IObservable PatchSomething(int id, [Body] string someAttribute); [Options("/foo/bar/{id}")] Task SendOptions(int id, [Body] string someAttribute); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithQueryFormat([Query(Format = "0.0")] int id); [Get("/query")] Task QueryWithEnumerable(IEnumerable numbers); [Get("/query")] Task QueryWithArray(int[] numbers); [Get("/query?q1={param1}&q2={param2}")] Task QueryWithExplicitParameters(string param1, string param2); [Get("/query")] Task QueryWithArrayFormattedAsMulti([Query(CollectionFormat.Multi)] int[] numbers); [Get("/query")] Task QueryWithArrayFormattedAsCsv([Query(CollectionFormat.Csv)] int[] numbers); [Get("/query")] Task QueryWithArrayFormattedAsSsv([Query(CollectionFormat.Ssv)] int[] numbers); [Get("/query")] Task QueryWithArrayFormattedAsTsv([Query(CollectionFormat.Tsv)] int[] numbers); [Get("/query")] Task QueryWithArrayFormattedAsPipes([Query(CollectionFormat.Pipes)] int[] numbers); [Get("/foo")] Task ComplexQueryObjectWithDictionary([Query] ComplexQueryObject query); [Get("/foo")] Task QueryWithDictionaryWithEnumKey([Query] IDictionary query); [Get("/foo")] Task QueryWithDictionaryWithPrefix( [Query(".", "dictionary")] IDictionary query ); [Get("/foo")] Task QueryWithDictionaryWithNumericKey([Query] IDictionary query); [Get("/query")] Task QueryWithEnumerableFormattedAsMulti( [Query(CollectionFormat.Multi)] IEnumerable lines ); [Get("/query")] Task QueryWithEnumerableFormattedAsCsv( [Query(CollectionFormat.Csv)] IEnumerable lines ); [Get("/query")] Task QueryWithEnumerableFormattedAsSsv( [Query(CollectionFormat.Ssv)] IEnumerable lines ); [Get("/query")] Task QueryWithEnumerableFormattedAsTsv( [Query(CollectionFormat.Tsv)] IEnumerable lines ); [Get("/query")] Task QueryWithEnumerableFormattedAsPipes( [Query(CollectionFormat.Pipes)] IEnumerable lines ); [Get("/query")] Task QueryWithObjectWithPrivateGetters(Person person); [Multipart] [Post("/foo?&name={name}")] Task PostWithQueryStringParameters(FileInfo source, string name); [Get("/query")] Task QueryWithEnum(FooWithEnumMember foo); [Get("/query")] Task QueryWithTypeWithEnum(TypeFooWithEnumMember foo); [Get("/api/{id}")] Task QueryWithOptionalParameters( int id, [Query] string text = null, [Query] int? optionalId = null, [Query(TreatAsString = true)] Foo foo = null, [Query(CollectionFormat = CollectionFormat.Multi)] string[] filters = null ); [Delete("/api/bar")] Task ClearWithEnumMember([Query] FooWithEnumMember foo); [Delete("/api/v1/video")] Task Clear([Query] int playerIndex); [Multipart] [Post("/blobstorage/{**filepath}")] Task Blob_Post_Byte(string filepath, [AliasAs("attachment")] ByteArrayPart byteArray); [Multipart] [Post("/companies/{companyId}/{path}")] Task> UploadFile( int companyId, string path, [AliasAs("file")] StreamPart stream, [Header("Authorization")] string authorization, bool overwrite = false, [AliasAs("fileMetadata")] string metadata = null ); [Post("/foo")] Task PostWithComplexTypeQuery([Query] ComplexQueryObject queryParams); [Get("/foo")] Task ComplexTypeQueryWithInnerCollection([Query] ComplexQueryObject queryParams); [Get("/api/{obj.someProperty}")] Task QueryWithOptionalParametersPathBoundObject( PathBoundObject obj, [Query] string text = null, [Query] int? optionalId = null, [Query(CollectionFormat = CollectionFormat.Multi)] string[] filters = null ); [Headers("Accept:application/json", "X-API-V: 125")] [Get("/api/someModule/deviceList?controlId={control_id}")] Task QueryWithHeadersBeforeData( [Header("Authorization")] string authorization, [Header("X-Lng")] string twoLetterLang, string search, [AliasAs("control_id")] string controlId, string secret ); [Get("/query")] [QueryUriFormat(UriFormat.Unescaped)] Task UnescapedQueryParams(string q); [Get("/query")] [QueryUriFormat(UriFormat.Unescaped)] Task UnescapedQueryParamsWithFilter(string q, string filter); [Get("/api/foo/{id}/file_{id}?query={id}")] Task SomeApiThatUsesParameterMoreThanOnceInTheUrl(string id); } interface ICancellableMethods { [Get("/foo")] Task GetWithCancellation(CancellationToken token = default); [Get("/foo")] Task GetWithCancellationAndReturn(CancellationToken token = default); } interface IAuthenticatedCancellableMethods { [Headers("Authorization: Bearer")] [Get("/foo")] Task GetWithAuthorizationAndCancellation(CancellationToken token = default); } public enum FooWithEnumMember { A, [EnumMember(Value = "b")] B } public class TypeFooWithEnumMember { [AliasAs("foo")] public FooWithEnumMember Foo { get; set; } } public class SomeRequestData { [AliasAs("rpn")] public int ReadablePropertyName { get; set; } } public class Person { public string FirstName { private get; set; } public string LastName { private get; set; } public string FullName => $"{FirstName} {LastName}"; } public class TestHttpMessageHandler : HttpMessageHandler { public HttpRequestMessage RequestMessage { get; private set; } public int MessagesSent { get; set; } public HttpContent Content { get; set; } public Func ContentFactory { get; set; } public CancellationToken CancellationToken { get; set; } public string SendContent { get; set; } public TestHttpMessageHandler(string content = "test") { Content = new StringContent(content); ContentFactory = () => Content; } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { RequestMessage = request; if (request.Content != null) { SendContent = await request .Content.ReadAsStringAsync(cancellationToken) .ConfigureAwait(false); } CancellationToken = cancellationToken; MessagesSent++; return new HttpResponseMessage(HttpStatusCode.OK) { Content = ContentFactory() }; } } public class TestUrlParameterFormatter : IUrlParameterFormatter { readonly string constantParameterOutput; public TestUrlParameterFormatter(string constantOutput) { constantParameterOutput = constantOutput; } public string Format(object value, ICustomAttributeProvider attributeProvider, Type type) { return constantParameterOutput; } } // Converts enums to ints and adds a suffix to strings to test that both dictionary keys and values are formatted. public class TestEnumUrlParameterFormatter : DefaultUrlParameterFormatter { public override string Format( object parameterValue, ICustomAttributeProvider attributeProvider, Type type ) { if (parameterValue is TestEnum enumValue) { var enumBackingValue = (int)enumValue; return enumBackingValue.ToString(); } if (parameterValue is string stringValue) { return $"{stringValue}{StringParameterSuffix}"; } return base.Format(parameterValue, attributeProvider, type); } public static string StringParameterSuffix => "suffix"; } public class TestEnumerableUrlParameterFormatter : DefaultUrlParameterFormatter { public override string Format( object parameterValue, ICustomAttributeProvider attributeProvider, Type type ) { if (parameterValue is IEnumerable enu) { return string.Join(",", enu.Select(o => base.Format(o, attributeProvider, type))); } if (parameterValue is IEnumerable en) { return string.Join( ",", en.Cast().Select(o => base.Format(o, attributeProvider, type)) ); } return base.Format(parameterValue, attributeProvider, type); } } public class RequestBuilderTests { [Fact] public void MethodsShouldBeCancellableDefault() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("GetWithCancellation"); var output = factory(Array.Empty()); var uri = new Uri(new Uri("http://api"), output.RequestMessage.RequestUri); Assert.Equal("/foo", uri.PathAndQuery); Assert.False(output.CancellationToken.IsCancellationRequested); } [Fact] public void MethodsShouldBeCancellableWithToken() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("GetWithCancellation"); var cts = new CancellationTokenSource(); var output = factory(new object[] { cts.Token }); var uri = new Uri(new Uri("http://api"), output.RequestMessage.RequestUri); Assert.Equal("/foo", uri.PathAndQuery); Assert.False(output.CancellationToken.IsCancellationRequested); } [Fact] public void MethodsShouldBeCancellableWithTokenDoesCancel() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("GetWithCancellation"); var cts = new CancellationTokenSource(); cts.Cancel(); var output = factory(new object[] { cts.Token }); Assert.True(output.CancellationToken.IsCancellationRequested); } [Fact] public void AuthorizationHeaderValueGetterReceivesMethodCancellationToken() { var observedCancellationToken = CancellationToken.None; var settings = new RefitSettings { AuthorizationHeaderValueGetter = (_, cancellationToken) => { observedCancellationToken = cancellationToken; return Task.FromResult("tokenValue"); } }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.RunRequest("GetWithAuthorizationAndCancellation"); var cts = new CancellationTokenSource(); var output = factory(new object[] { cts.Token }); Assert.Equal(cts.Token, observedCancellationToken); Assert.Equal("Bearer tokenValue", output.RequestMessage.Headers.Authorization?.ToString()); } [Fact] public void HttpContentAsApiResponseTest() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("PostFileUploadWithMetadata"); var testHttpMessageHandler = new TestHttpMessageHandler(); var retContent = new StreamContent(new MemoryStream()); testHttpMessageHandler.Content = retContent; var mpc = new MultipartContent("foosubtype"); var task = (Task>) factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, new object[] { mpc } ); task.Wait(); Assert.NotNull(task.Result.Headers); Assert.True(task.Result.IsSuccessStatusCode); Assert.NotNull(task.Result.ReasonPhrase); Assert.False(task.Result.StatusCode == default); Assert.NotNull(task.Result.Version); Assert.Equal(testHttpMessageHandler.RequestMessage.Content, mpc); Assert.Equal(retContent, task.Result.Content); } [Fact] public void HttpContentTest() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("PostFileUpload"); var testHttpMessageHandler = new TestHttpMessageHandler(); var retContent = new StreamContent(new MemoryStream()); testHttpMessageHandler.Content = retContent; var mpc = new MultipartContent("foosubtype"); var task = (Task) factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, new object[] { mpc } ); task.Wait(); Assert.Equal(testHttpMessageHandler.RequestMessage.Content, mpc); Assert.Equal(retContent, task.Result); } [Fact] public void StreamResponseAsApiResponseTest() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("GetRemoteFileWithMetadata"); var testHttpMessageHandler = new TestHttpMessageHandler(); var streamResponse = new MemoryStream(); var reponseContent = "A remote file"; testHttpMessageHandler.Content = new StreamContent(streamResponse); var writer = new StreamWriter(streamResponse); writer.Write(reponseContent); writer.Flush(); streamResponse.Seek(0L, SeekOrigin.Begin); var task = (Task>) factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, new object[] { "test-file" } ); task.Wait(); Assert.NotNull(task.Result.Headers); Assert.True(task.Result.IsSuccessStatusCode); Assert.NotNull(task.Result.ReasonPhrase); Assert.False(task.Result.StatusCode == default); Assert.NotNull(task.Result.Version); using var reader = new StreamReader(task.Result.Content); Assert.Equal(reponseContent, reader.ReadToEnd()); } [Fact] public void StreamResponseTest() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("GetRemoteFile"); var testHttpMessageHandler = new TestHttpMessageHandler(); var streamResponse = new MemoryStream(); var reponseContent = "A remote file"; testHttpMessageHandler.Content = new StreamContent(streamResponse); var writer = new StreamWriter(streamResponse); writer.Write(reponseContent); writer.Flush(); streamResponse.Seek(0L, SeekOrigin.Begin); var task = (Task) factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, new object[] { "test-file" } ); task.Wait(); using var reader = new StreamReader(task.Result); Assert.Equal(reponseContent, reader.ReadToEnd()); } [Fact] public void MethodsThatDontHaveAnHttpMethodShouldFail() { var failureMethods = new[] { "SomeOtherMethod", "weofjwoeijfwe", null, }; var successMethods = new[] { "FetchSomeStuff", }; foreach (var v in failureMethods) { var shouldDie = true; try { var fixture = new RequestBuilderImplementation(); fixture.BuildRequestFactoryForMethod(v); } catch (Exception) { shouldDie = false; } Assert.False(shouldDie); } foreach (var v in successMethods) { var shouldDie = false; try { var fixture = new RequestBuilderImplementation(); fixture.BuildRequestFactoryForMethod(v); } catch (Exception) { shouldDie = true; } Assert.False(shouldDie); } } [Fact] public void HardcodedQueryParamShouldBeInUrl() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithHardcodedQueryParameter" ); var output = factory(new object[] { 6 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar/6?baz=bamf", uri.PathAndQuery); } [Fact] public void ParameterizedQueryParamsShouldBeInUrl() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithHardcodedAndOtherQueryParameters" ); var output = factory(new object[] { 6, "foo" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar/6?baz=bamf&search_for=foo", uri.PathAndQuery); } [Fact] public void ParameterizedValuesShouldBeInUrlMoreThanOnce() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.SomeApiThatUsesParameterMoreThanOnceInTheUrl) ); var output = factory(new object[] { 6 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/api/foo/6/file_6?query=6", uri.PathAndQuery); } [Theory] [InlineData("aaa/bbb", "/foo/bar/aaa/bbb/1")] [InlineData("aaa/bbb/ccc", "/foo/bar/aaa/bbb/ccc/1")] [InlineData("aaa", "/foo/bar/aaa/1")] [InlineData("aa a/bb-b", "/foo/bar/aa%20a/bb-b/1")] public void RoundTrippingParameterizedQueryParamsShouldBeInUrl( string path, string expectedQuery ) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithRoundTrippingParam" ); var output = factory(new object[] { path, 1 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Fact] public void ParameterizedNullQueryParamsShouldBeBlankInUrl() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("PostWithQueryStringParameters"); var output = factory( new object[] { new FileInfo(typeof(RequestBuilderTests).Assembly.Location), null } ); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?name=", uri.PathAndQuery); } [Fact] public void ParametersShouldBePutAsExplicitQueryString() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.QueryWithExplicitParameters) ); var output = factory(new object[] { "value1", "value2" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?q1=value1&q2=value2", uri.PathAndQuery); } [Fact] public void QueryParamShouldFormat() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuffWithQueryFormat"); var output = factory(new object[] { 6 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar/6.0", uri.PathAndQuery); } [Fact] public void ParameterizedQueryParamsShouldBeInUrlAndValuesEncoded() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithHardcodedAndOtherQueryParameters" ); var output = factory(new object[] { 6, "push!=pull&push" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar/6?baz=bamf&search_for=push%21%3Dpull%26push", uri.PathAndQuery); } [Fact] public void ParameterizedQueryParamsShouldBeInUrlAndValuesEncodedWhenMixedReplacementAndQuery() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithVoidAndQueryAlias" ); var output = factory(new object[] { "6 & 7/8", "test@example.com", "push!=pull" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "/void/6%20%26%207%2F8/path?a=test%40example.com&b=push%21%3Dpull", uri.PathAndQuery ); } [Fact] public void QueryParamWithPathDelimiterShouldBeEncoded() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithVoidAndQueryAlias" ); var output = factory(new object[] { "6/6", "test@example.com", "push!=pull" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "/void/6%2F6/path?a=test%40example.com&b=push%21%3Dpull", uri.PathAndQuery ); } [Fact] public void QueryParamWhichEndsInDoubleQuotesShouldNotBeTruncated() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithDoubleQuotesInUrl" ); var output = factory([42]); var uri = new Uri(new Uri("http://api"), output.RequestUri!); Assert.Equal("/foo?q=app_metadata.id%3A%2242%22", uri.PathAndQuery); } [Theory] [InlineData("GetWithTrainingParenthesis", ")", "/foo/bar/(1)")] [InlineData("GetWithTrailingSlash", "/", "/foo/bar/1/")] public void ShouldCaptureLastCharacterWhenRouteEndsWithConstant(string methodToTest, string constantChar, string contains) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( methodToTest ); var output = factory(["1"]); var uri = new Uri(new Uri("http://api/"), output.RequestUri!); Assert.EndsWith(constantChar, uri.PathAndQuery, StringComparison.Ordinal); Assert.Contains(contains, uri.PathAndQuery, StringComparison.Ordinal); } [Fact] public void ParameterizedQueryParamsShouldBeInUrlAndValuesEncodedWhenMixedReplacementAndQueryBadId() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithVoidAndQueryAlias" ); var output = factory(new object[] { "6", "test@example.com", "push!=pull" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/void/6/path?a=test%40example.com&b=push%21%3Dpull", uri.PathAndQuery); } [Fact] public void NonFormattableQueryParamsShouldBeIncluded() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithNonFormattableQueryParams" ); var output = factory(new object[] { true, 'x' }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?b=True&c=x", uri.PathAndQuery); } [Fact] public void MultipleParametersInTheSameSegmentAreGeneratedProperly() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomethingWithMultipleParametersPerSegment" ); var output = factory(new object[] { 6, 1024, 768 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/6/1024x768/foo", uri.PathAndQuery); } [Fact] public void HardcodedHeadersShouldBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithHardcodedHeaders) ); var output = factory(new object[] { 6 }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); Assert.Equal("RefitTestClient", output.Headers.UserAgent.ToString()); Assert.True( output.Headers.Contains("Api-Version"), "Headers include Api-Version header" ); Assert.Equal("2", output.Headers.GetValues("Api-Version").Single()); Assert.True(output.Headers.Contains("Accept"), "Headers include Accept header"); Assert.Equal("application/json", output.Headers.Accept.ToString()); } [Fact] public void EmptyHardcodedHeadersShouldBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithEmptyHardcodedHeader" ); var output = factory(new object[] { 6 }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); Assert.Equal("RefitTestClient", output.Headers.UserAgent.ToString()); Assert.True( output.Headers.Contains("Api-Version"), "Headers include Api-Version header" ); Assert.Equal("", output.Headers.GetValues("Api-Version").Single()); } [Fact] public void NullHardcodedHeadersShouldNotBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithNullHardcodedHeader" ); var output = factory(new object[] { 6 }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); Assert.Equal("RefitTestClient", output.Headers.UserAgent.ToString()); Assert.False( output.Headers.Contains("Api-Version"), "Headers include Api-Version header" ); } [Fact] public void ReadStringContentWithMetadata() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("FetchSomeStringWithMetadata"); var testHttpMessageHandler = new TestHttpMessageHandler(); var task = (Task>) factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, new object[] { 42 } ); task.Wait(); Assert.NotNull(task.Result.Headers); Assert.True(task.Result.IsSuccessStatusCode); Assert.NotNull(task.Result.ReasonPhrase); Assert.False(task.Result.StatusCode == default); Assert.NotNull(task.Result.Version); Assert.Equal("test", task.Result.Content); } [Fact] public void ContentHeadersCanBeHardcoded() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "PostSomeStuffWithHardCodedContentTypeHeader" ); var output = factory(new object[] { 6, "stuff" }); Assert.True( output.Content.Headers.Contains("Content-Type"), "Content headers include Content-Type header" ); Assert.Equal("literally/anything", output.Content.Headers.ContentType.ToString()); } [Fact] public void DynamicHeaderShouldBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuffWithDynamicHeader"); var output = factory(new object[] { 6, "Basic RnVjayB5ZWFoOmhlYWRlcnMh" }); Assert.NotNull(output.Headers.Authorization); //, "Headers include Authorization header"); Assert.Equal("RnVjayB5ZWFoOmhlYWRlcnMh", output.Headers.Authorization.Parameter); } [Fact] public void CustomDynamicHeaderShouldBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuffWithCustomHeader"); var output = factory(new object[] { 6, ":joy_cat:" }); Assert.True(output.Headers.Contains("X-Emoji"), "Headers include X-Emoji header"); Assert.Equal(":joy_cat:", output.Headers.GetValues("X-Emoji").First()); } [Fact] public void EmptyDynamicHeaderShouldBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuffWithCustomHeader"); var output = factory(new object[] { 6, "" }); Assert.True(output.Headers.Contains("X-Emoji"), "Headers include X-Emoji header"); Assert.Equal("", output.Headers.GetValues("X-Emoji").First()); } [Fact] public void NullDynamicHeaderShouldNotBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuffWithDynamicHeader"); var output = factory(new object[] { 6, null }); Assert.Null(output.Headers.Authorization); //, "Headers include Authorization header"); } [Fact] public void PathMemberAsCustomDynamicHeaderShouldBeInHeaders() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "FetchSomeStuffWithPathMemberInCustomHeader" ); var output = factory(new object[] { 6, ":joy_cat:" }); Assert.True( output.Headers.Contains("X-PathMember"), "Headers include X-PathMember header" ); Assert.Equal("6", output.Headers.GetValues("X-PathMember").First()); } [Fact] public void AddCustomHeadersToRequestHeadersOnly() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("PostSomeStuffWithCustomHeader"); var output = factory(new object[] { 6, new { Foo = "bar" }, ":smile_cat:" }); Assert.True( output.Headers.Contains("Api-Version"), "Headers include Api-Version header" ); Assert.True(output.Headers.Contains("X-Emoji"), "Headers include X-Emoji header"); Assert.False( output.Content.Headers.Contains("Api-Version"), "Content headers include Api-Version header" ); Assert.False( output.Content.Headers.Contains("X-Emoji"), "Content headers include X-Emoji header" ); } [Theory] [InlineData(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.DeleteSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.PutSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.PostSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.PatchSomeStuffWithDynamicHeaderCollection))] public void HeaderCollectionShouldBeInHeaders(string interfaceMethodName) { var headerCollection = new Dictionary { { "key1", "val1" }, { "key2", "val2" } }; var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(interfaceMethodName); var output = factory(new object[] { 6, headerCollection }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); Assert.Equal("RefitTestClient", output.Headers.GetValues("User-Agent").First()); Assert.True( output.Headers.Contains("Api-Version"), "Headers include Api-Version header" ); Assert.Equal("1", output.Headers.GetValues("Api-Version").First()); Assert.True( output.Headers.Contains("Authorization"), "Headers include Authorization header" ); Assert.Equal( "SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", output.Headers.GetValues("Authorization").First() ); Assert.True(output.Headers.Contains("Accept"), "Headers include Accept header"); Assert.Equal("application/json", output.Headers.GetValues("Accept").First()); Assert.True(output.Headers.Contains("key1"), "Headers include key1 header"); Assert.Equal("val1", output.Headers.GetValues("key1").First()); Assert.True(output.Headers.Contains("key2"), "Headers include key2 header"); Assert.Equal("val2", output.Headers.GetValues("key2").First()); } [Fact] public void LastWriteWinsWhenHeaderCollectionAndDynamicHeader() { var authHeader = "LetMeIn"; var headerCollection = new Dictionary { { "Authorization", "OpenSesame" } }; var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader) ); var output = factory(new object[] { 6, authHeader, headerCollection }); Assert.True( output.Headers.Contains("Authorization"), "Headers include Authorization header" ); Assert.Equal("OpenSesame", output.Headers.GetValues("Authorization").First()); fixture = new RequestBuilderImplementation(); factory = fixture.BuildRequestFactoryForMethod( nameof( IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeaderOrderFlipped ) ); output = factory(new object[] { 6, headerCollection, authHeader }); Assert.True( output.Headers.Contains("Authorization"), "Headers include Authorization header" ); Assert.Equal(authHeader, output.Headers.GetValues("Authorization").First()); } [Theory] [InlineData(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.DeleteSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.PutSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.PostSomeStuffWithDynamicHeaderCollection))] [InlineData(nameof(IDummyHttpApi.PatchSomeStuffWithDynamicHeaderCollection))] public void NullHeaderCollectionDoesntBlowUp(string interfaceMethodName) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(interfaceMethodName); var output = factory(new object[] { 6, null }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); Assert.Equal("RefitTestClient", output.Headers.GetValues("User-Agent").First()); Assert.True( output.Headers.Contains("Api-Version"), "Headers include Api-Version header" ); Assert.Equal("1", output.Headers.GetValues("Api-Version").First()); Assert.True( output.Headers.Contains("Authorization"), "Headers include Authorization header" ); Assert.Equal( "SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", output.Headers.GetValues("Authorization").First() ); Assert.True(output.Headers.Contains("Accept"), "Headers include Accept header"); Assert.Equal("application/json", output.Headers.GetValues("Accept").First()); } [Fact] public void HeaderCollectionCanUnsetHeaders() { var headerCollection = new Dictionary { { "Authorization", "" }, { "Api-Version", null } }; var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection) ); var output = factory(new object[] { 6, headerCollection }); Assert.True( !output.Headers.Contains("Api-Version"), "Headers does not include Api-Version header" ); Assert.True( output.Headers.Contains("Authorization"), "Headers include Authorization header" ); Assert.Equal("", output.Headers.GetValues("Authorization").First()); } [Theory] [InlineData(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicRequestProperty))] [InlineData(nameof(IDummyHttpApi.DeleteSomeStuffWithDynamicRequestProperty))] [InlineData(nameof(IDummyHttpApi.PutSomeStuffWithDynamicRequestProperty))] [InlineData(nameof(IDummyHttpApi.PostSomeStuffWithDynamicRequestProperty))] [InlineData(nameof(IDummyHttpApi.PatchSomeStuffWithDynamicRequestProperty))] public void DynamicRequestPropertiesShouldBeInProperties(string interfaceMethodName) { var someProperty = new object(); var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(interfaceMethodName); var output = factory(new object[] { 6, someProperty }); #if NET6_0_OR_GREATER Assert.NotEmpty(output.Options); Assert.Equal( someProperty, ((IDictionary)output.Options)["SomeProperty"] ); #endif #pragma warning disable CS0618 // Type or member is obsolete Assert.NotEmpty(output.Properties); Assert.Equal(someProperty, output.Properties["SomeProperty"]); #pragma warning restore CS0618 // Type or member is obsolete } [Fact] public void OptionsFromSettingsShouldBeInProperties() { const string nameProp1 = "UnitTest.Property1"; string valueProp1 = "TestValue"; const string nameProp2 = "UnitTest.Property2"; object valueProp2 = new List() { "123", "345" }; var fixture = new RequestBuilderImplementation( new RefitSettings() { HttpRequestMessageOptions = new Dictionary() { [nameProp1] = valueProp1, [nameProp2] = valueProp2, }, } ); var factory = fixture.BuildRequestFactoryForMethod(nameof(IContainAandB.Ping)); var output = factory(Array.Empty()); #if NET6_0_OR_GREATER Assert.NotEmpty(output.Options); Assert.True( output.Options.TryGetValue( new HttpRequestOptionsKey(nameProp1), out var resultValueProp1 ) ); Assert.Equal(valueProp1, resultValueProp1); Assert.True( output.Options.TryGetValue( new HttpRequestOptionsKey>(nameProp2), out var resultValueProp2 ) ); Assert.Equal(valueProp2, resultValueProp2); #else Assert.NotEmpty(output.Properties); Assert.True(output.Properties.TryGetValue(nameProp1, out var resultValueProp1)); Assert.IsType(resultValueProp1); Assert.Equal(valueProp1, (string)resultValueProp1); Assert.True(output.Properties.TryGetValue(nameProp2, out var resultValueProp2)); Assert.IsType>(resultValueProp2); Assert.Equal(valueProp2, (List)resultValueProp2); #endif } [Fact] public void InterfaceTypeShouldBeInProperties() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(nameof(IContainAandB.Ping)); var output = factory(Array.Empty()); #pragma warning disable CS0618 // Type or member is obsolete Assert.NotEmpty(output.Properties); Assert.Equal( typeof(IContainAandB), output.Properties[HttpRequestMessageOptions.InterfaceType] ); #pragma warning restore CS0618 // Type or member is obsolete } [Fact] public void RestMethodInfoShouldBeInProperties() { var someProperty = new object(); var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(nameof(IContainAandB.Ping)); var output = factory(new object[] { }); #if NET6_0_OR_GREATER Assert.NotEmpty(output.Options); Assert.True( output.Options.TryGetValue( new HttpRequestOptionsKey( HttpRequestMessageOptions.RestMethodInfo ), out var restMethodInfo ) ); #else Assert.NotEmpty(output.Properties); Assert.True( output.Properties.TryGetValue( HttpRequestMessageOptions.RestMethodInfo, out var restMethodInfoObj ) ); Assert.IsType(restMethodInfoObj); var restMethodInfo = restMethodInfoObj as RestMethodInfo; #endif Assert.Equal(nameof(IContainAandB.Ping), restMethodInfo.Name); } [Fact] public void DynamicRequestPropertiesWithDefaultKeysShouldBeInProperties() { var someProperty = new object(); var someOtherProperty = new object(); var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithDynamicRequestPropertyWithoutKey) ); var output = factory(new object[] { 6, someProperty, someOtherProperty }); #if NET6_0_OR_GREATER Assert.NotEmpty(output.Options); Assert.Equal(someProperty, ((IDictionary)output.Options)["someValue"]); Assert.Equal( someOtherProperty, ((IDictionary)output.Options)["someOtherValue"] ); #endif #pragma warning disable CS0618 // Type or member is obsolete Assert.NotEmpty(output.Properties); Assert.Equal(someProperty, output.Properties["someValue"]); Assert.Equal(someOtherProperty, output.Properties["someOtherValue"]); #pragma warning restore CS0618 // Type or member is obsolete } [Fact] public void DynamicRequestPropertiesWithDuplicateKeyShouldOverwritePreviousProperty() { var someProperty = new object(); var someOtherProperty = new object(); var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithDynamicRequestPropertyWithDuplicateKey) ); var output = factory(new object[] { 6, someProperty, someOtherProperty }); #if NET6_0_OR_GREATER Assert.Equal(3, output.Options.Count()); Assert.Equal( someOtherProperty, ((IDictionary)output.Options)["SomeProperty"] ); #endif #pragma warning disable CS0618 // Type or member is obsolete Assert.Equal(3, output.Properties.Count); Assert.Equal(someOtherProperty, output.Properties["SomeProperty"]); #pragma warning restore CS0618 // Type or member is obsolete } [Fact] public void HttpClientShouldPrefixedAbsolutePathToTheRequestUri() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("FetchSomeStuffWithoutFullPath"); var testHttpMessageHandler = new TestHttpMessageHandler(); var task = (Task)factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/foo/bar") }, Array.Empty() ); task.Wait(); Assert.Equal( "http://api/foo/bar/string", testHttpMessageHandler.RequestMessage.RequestUri.ToString() ); } [Fact] public void HttpClientForVoidMethodShouldPrefixedAbsolutePathToTheRequestUri() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("FetchSomeStuffWithVoid"); var testHttpMessageHandler = new TestHttpMessageHandler(); var task = (Task)factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/foo/bar") }, Array.Empty() ); task.Wait(); Assert.Equal( "http://api/foo/bar/void", testHttpMessageHandler.RequestMessage.RequestUri.ToString() ); } [Fact] public void HttpClientShouldNotPrefixEmptyAbsolutePathToTheRequestUri() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRestResultFuncForMethod("FetchSomeStuff"); var testHttpMessageHandler = new TestHttpMessageHandler(); var task = (Task)factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, new object[] { 42 } ); task.Wait(); Assert.Equal( "http://api/foo/bar/42", testHttpMessageHandler.RequestMessage.RequestUri.ToString() ); } [Fact] public void DontBlowUpWithDynamicAuthorizationHeaderAndContent() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("PutSomeContentWithAuthorization"); var output = factory( new object[] { 7, new { Octocat = "Dunetocat" }, "Basic RnVjayB5ZWFoOmhlYWRlcnMh" } ); Assert.NotNull(output.Headers.Authorization); //, "Headers include Authorization header"); Assert.Equal("RnVjayB5ZWFoOmhlYWRlcnMh", output.Headers.Authorization.Parameter); } [Fact] public void SuchFlexibleContentTypeWow() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "PutSomeStuffWithDynamicContentType" ); var output = factory( new object[] { 7, "such \"refit\" is \"amaze\" wow", "text/dson" } ); Assert.NotNull(output.Content); //, "Request has content"); Assert.NotNull(output.Content.Headers.ContentType); //, "Headers include Content-Type header"); Assert.Equal("text/dson", output.Content.Headers.ContentType.MediaType); //, "Content-Type header has the expected value"); } [Fact] public void BodyContentGetsUrlEncoded() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("PostSomeUrlEncodedStuff"); var output = factory( new object[] { 6, new { Foo = "Something", Bar = 100, Baz = "" // explicitly use blank to preserve value that would be stripped if null } } ); Assert.Equal("Foo=Something&Bar=100&Baz=", output.SendContent); } [Fact] public void BodyContentGetsUrlEncodedWithCollectionFormat() { var settings = new RefitSettings() { CollectionFormat = CollectionFormat.Csv }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.RunRequest("PostSomeUrlEncodedStuff"); var output = factory( new object[] { 6, new { Foo = "Something", Bar = 100, FooBar = new [] {5,7}, Baz = "" // explicitly use blank to preserve value that would be stripped if null } } ); Assert.Equal("Foo=Something&Bar=100&FooBar=5%2C7&Baz=", output.SendContent); } [Fact] public void FormFieldGetsAliased() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("PostSomeAliasedUrlEncodedStuff"); var output = factory( new object[] { 6, new SomeRequestData { ReadablePropertyName = 99 } } ); Assert.Equal("rpn=99", output.SendContent); } [Fact] public void CustomParmeterFormatter() { var settings = new RefitSettings { UrlParameterFormatter = new TestUrlParameterFormatter("custom-parameter") }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuff"); var output = factory(new object[] { 5 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar/custom-parameter", uri.PathAndQuery); } [Fact] public void QueryStringWithEnumerablesCanBeFormatted() { var settings = new RefitSettings { UrlParameterFormatter = new TestEnumerableUrlParameterFormatter() }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.BuildRequestFactoryForMethod("QueryWithEnumerable"); var output = factory(new object[] { new int[] { 1, 2, 3 } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?numbers=1%2C2%2C3", uri.PathAndQuery); } [Fact] public void QueryStringWithArrayCanBeFormatted() { var settings = new RefitSettings { UrlParameterFormatter = new TestEnumerableUrlParameterFormatter() }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.BuildRequestFactoryForMethod("QueryWithArray"); var output = factory(new object[] { new int[] { 1, 2, 3 } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?numbers=1%2C2%2C3", uri.PathAndQuery); } [Fact] public void QueryStringWithArrayCanBeFormattedByAttribute() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("UnescapedQueryParams"); var output = factory(new object[] { "Select+Id,Name+From+Account" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?q=Select+Id,Name+From+Account", uri.PathAndQuery); } [Fact] public void QueryStringWithArrayCanBeFormattedByAttributeWithMultiple() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("UnescapedQueryParamsWithFilter"); var output = factory(new object[] { "Select+Id+From+Account", "*" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?q=Select+Id+From+Account&filter=*", uri.PathAndQuery); } [Fact] public void QueryStringWithArrayCanBeFormattedByDefaultSetting() { var fixture = new RequestBuilderImplementation( new RefitSettings { CollectionFormat = CollectionFormat.Multi } ); var factory = fixture.BuildRequestFactoryForMethod("QueryWithArray"); var output = factory(new object[] { new[] { 1, 2, 3 } }); Assert.Equal("/query?numbers=1&numbers=2&numbers=3", output.RequestUri.PathAndQuery); } [Fact] public void DefaultCollectionFormatCanBeOverridenByQueryAttribute() { var fixture = new RequestBuilderImplementation( new RefitSettings { CollectionFormat = CollectionFormat.Multi } ); var factory = fixture.BuildRequestFactoryForMethod("QueryWithArrayFormattedAsCsv"); var output = factory(new object[] { new[] { 1, 2, 3 } }); Assert.Equal("/query?numbers=1%2C2%2C3", output.RequestUri.PathAndQuery); } [Fact] public void RequestWithParameterInMultiplePlaces() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithTheSameId) ); var output = factory(new object[] { "theId" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); var builder = new UriBuilder(uri); var qs = QueryHelpers.ParseQuery(uri.Query); Assert.Equal("/foo/bar/theId", builder.Path); Assert.Equal("theId", qs["param1"]); Assert.Equal("theId", qs["param2"]); } [Fact] public void RequestWithParameterInAQueryParameterMultipleTimes() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.FetchSomeStuffWithTheIdInAParameterMultipleTimes) ); var output = factory(new object[] { "theId" }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar?param=first%20theId%20and%20second%20theId", uri.PathAndQuery); } [Theory] [InlineData("QueryWithArrayFormattedAsMulti", "/query?numbers=1&numbers=2&numbers=3")] [InlineData("QueryWithArrayFormattedAsCsv", "/query?numbers=1%2C2%2C3")] [InlineData("QueryWithArrayFormattedAsSsv", "/query?numbers=1%202%203")] [InlineData("QueryWithArrayFormattedAsTsv", "/query?numbers=1%092%093")] [InlineData("QueryWithArrayFormattedAsPipes", "/query?numbers=1%7C2%7C3")] public void QueryStringWithArrayFormatted(string apiMethodName, string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(apiMethodName); var output = factory(new object[] { new[] { 1, 2, 3 } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Fact] public void QueryStringWithArrayFormattedAsSsvAndItemsFormattedIndividually() { var settings = new RefitSettings { UrlParameterFormatter = new TestUrlParameterFormatter("custom-parameter") }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.BuildRequestFactoryForMethod("QueryWithArrayFormattedAsSsv"); var output = factory(new object[] { new int[] { 1, 2, 3 } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "/query?numbers=custom-parameter%20custom-parameter%20custom-parameter", uri.PathAndQuery ); } [Fact] public void QueryStringWithEnumerablesCanBeFormattedEnumerable() { var settings = new RefitSettings { UrlParameterFormatter = new TestEnumerableUrlParameterFormatter() }; var fixture = new RequestBuilderImplementation(settings); var factory = fixture.BuildRequestFactoryForMethod("QueryWithEnumerable"); var list = new List { 1, 2, 3 }; var output = factory(new object[] { list }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?numbers=1%2C2%2C3", uri.PathAndQuery); } [Theory] [InlineData( "QueryWithEnumerableFormattedAsMulti", "/query?lines=first&lines=second&lines=third" )] [InlineData("QueryWithEnumerableFormattedAsCsv", "/query?lines=first%2Csecond%2Cthird")] [InlineData("QueryWithEnumerableFormattedAsSsv", "/query?lines=first%20second%20third")] [InlineData("QueryWithEnumerableFormattedAsTsv", "/query?lines=first%09second%09third")] [InlineData("QueryWithEnumerableFormattedAsPipes", "/query?lines=first%7Csecond%7Cthird")] public void QueryStringWithEnumerableFormatted(string apiMethodName, string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod(apiMethodName); var lines = new List { "first", "second", "third" }; var output = factory(new object[] { lines }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Fact] public void QueryStringExcludesPropertiesWithPrivateGetters() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithObjectWithPrivateGetters"); var person = new Person { FirstName = "Mickey", LastName = "Mouse" }; var output = factory(new object[] { person }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?FullName=Mickey%20Mouse", uri.PathAndQuery); } [Theory] [InlineData(FooWithEnumMember.A, "/query?foo=A")] [InlineData(FooWithEnumMember.B, "/query?foo=b")] public void QueryStringUsesEnumMemberAttribute( FooWithEnumMember queryParameter, string expectedQuery ) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithEnum"); var output = factory(new object[] { queryParameter }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Theory] [InlineData(FooWithEnumMember.A, "/query?foo=A")] [InlineData(FooWithEnumMember.B, "/query?foo=b")] public void QueryStringUsesEnumMemberAttributeInTypeWithEnum( FooWithEnumMember queryParameter, string expectedQuery ) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithTypeWithEnum"); var output = factory( new object[] { new TypeFooWithEnumMember { Foo = queryParameter } } ); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Theory] [InlineData("/api/123?text=title&optionalId=999&foo=foo&filters=A&filters=B")] public void TestNullableQueryStringParams(string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithOptionalParameters"); var output = factory(new object[] { 123, "title", 999, new Foo(), new string[] { "A", "B" } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Theory] [InlineData("/api/123?text=title&filters=A&filters=B")] public void TestNullableQueryStringParamsWithANull(string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithOptionalParameters"); var output = factory(new object[] { 123, "title", null, null, new string[] { "A", "B" } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Theory] [InlineData("/api/123?SomeProperty2=test&text=title&filters=A&filters=B")] public void TestNullableQueryStringParamsWithANullAndPathBoundObject(string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( "QueryWithOptionalParametersPathBoundObject" ); var output = factory( new object[] { new PathBoundObject() { SomeProperty = 123, SomeProperty2 = "test" }, "title", null, new string[] { "A", "B" } } ); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); } [Fact] [UseCulture("es-ES")] // Spain uses a , instead of a . public void DefaultParameterFormatterIsInvariant() { var settings = new RefitSettings(); var fixture = new RequestBuilderImplementation(settings); var factory = fixture.BuildRequestFactoryForMethod("FetchSomeStuff"); var output = factory(new object[] { 5.4 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo/bar/5.4", uri.PathAndQuery); } [Fact] public void ICanPostAValueTypeIfIWantYoureNotTheBossOfMe() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("PostAValueType", "true"); var guid = Guid.NewGuid(); var expected = string.Format("\"{0}\"", guid); var output = factory(new object[] { 7, guid }); Assert.Equal(expected, output.SendContent); } [Fact] public void DeleteWithQuery() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("Clear"); var output = factory(new object[] { 1 }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/api/v1/video?playerIndex=1", uri.PathAndQuery); } [Fact] public void ClearWithQuery() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("ClearWithEnumMember"); var output = factory(new object[] { FooWithEnumMember.B }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/api/bar?foo=b", uri.PathAndQuery); } [Fact] public void MultipartPostWithAliasAndHeader() { var fixture = new RequestBuilderImplementation(); var factory = fixture.RunRequest("UploadFile", "true"); using var file = MultipartTests.GetTestFileStream("Test Files/Test.pdf"); var sp = new StreamPart(file, "aFile"); var output = factory(new object[] { 42, "aPath", sp, "theAuth", false, "theMeta" }); var uri = new Uri(new Uri("http://api"), output.RequestMessage.RequestUri); Assert.Equal("/companies/42/aPath", uri.PathAndQuery); Assert.Equal("theAuth", output.RequestMessage.Headers.Authorization.ToString()); } [Fact] public void PostBlobByteWithAlias() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.Blob_Post_Byte) ); var bytes = new byte[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var bap = new ByteArrayPart(bytes, "theBytes"); var output = factory(new object[] { "the/path", bap }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/blobstorage/the/path", uri.PathAndQuery); } [Fact] public void QueryWithAliasAndHeadersWorks() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.QueryWithHeadersBeforeData) ); var authHeader = "theAuth"; var langHeader = "LnG"; var searchParam = "theSearchParam"; var controlIdParam = "theControlId"; var secretValue = "theSecret"; var output = factory( new object[] { authHeader, langHeader, searchParam, controlIdParam, secretValue } ); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( $"/api/someModule/deviceList?controlId={controlIdParam}&search={searchParam}&secret={secretValue}", uri.PathAndQuery ); Assert.Equal(langHeader, output.Headers.GetValues("X-LnG").FirstOrDefault()); Assert.Equal(authHeader, output.Headers.Authorization?.Scheme); } class RequestBuilderMock : IRequestBuilder { public int CallCount { get; private set; } public Func BuildRestResultFuncForMethod( string methodName, Type[] parameterTypes = null, Type[] genericArgumentTypes = null ) { CallCount++; return null; } } [Fact] public void CachedRequestBuilderCallInternalBuilderForParametersWithSameNamesButDifferentNamespaces() { var internalBuilder = new RequestBuilderMock(); var cachedBuilder = new CachedRequestBuilderImplementation(internalBuilder); cachedBuilder.BuildRestResultFuncForMethod( "TestMethodName", new[] { typeof(CollisionA.SomeType) } ); cachedBuilder.BuildRestResultFuncForMethod( "TestMethodName", new[] { typeof(CollisionB.SomeType) } ); cachedBuilder.BuildRestResultFuncForMethod( "TestMethodName", null, new[] { typeof(CollisionA.SomeType) } ); cachedBuilder.BuildRestResultFuncForMethod( "TestMethodName", null, new[] { typeof(CollisionB.SomeType) } ); Assert.Equal(4, internalBuilder.CallCount); } [Fact] public void DictionaryQueryWithEnumKeyProducesCorrectQueryString() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.QueryWithDictionaryWithEnumKey) ); var dict = new Dictionary { { TestEnum.A, "value1" }, { TestEnum.B, "value2" }, }; var output = factory(new object[] { dict }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?A=value1&B=value2", uri.PathAndQuery); } [Fact] public void DictionaryQueryWithPrefix() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.QueryWithDictionaryWithPrefix) ); var dict = new Dictionary { { TestEnum.A, "value1" }, { TestEnum.B, "value2" }, }; var output = factory(new object[] { dict }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?dictionary.A=value1&dictionary.B=value2", uri.PathAndQuery); } [Fact] public void DictionaryQueryWithNumericKeyProducesCorrectQueryString() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.QueryWithDictionaryWithNumericKey) ); var dict = new Dictionary { { 1, "value1" }, { 2, "value2" }, }; var output = factory(new object[] { dict }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?1=value1&2=value2", uri.PathAndQuery); } [Fact] public void DictionaryQueryWithCustomFormatterProducesCorrectQueryString() { var urlParameterFormatter = new TestEnumUrlParameterFormatter(); var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.QueryWithDictionaryWithEnumKey) ); var dict = new Dictionary { { TestEnum.A, "value1" }, { TestEnum.B, "value2" }, }; var output = factory(new object[] { dict }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( $"/foo?{(int)TestEnum.A}=value1{TestEnumUrlParameterFormatter.StringParameterSuffix}&{(int)TestEnum.B}=value2{TestEnumUrlParameterFormatter.StringParameterSuffix}", uri.PathAndQuery ); } [Fact] public void ComplexQueryObjectWithDefaultKeyFormatterProducesCorrectQueryString() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexQueryObjectWithDictionary) ); var complexQuery = new ComplexQueryObject { TestAlias2 = "value1" }; var output = factory(new object[] { complexQuery }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?TestAlias2=value1", uri.PathAndQuery); } [Fact] public void ComplexQueryObjectWithCustomKeyFormatterProducesCorrectQueryString() { var urlParameterKeyFormatter = new CamelCaseUrlParameterKeyFormatter(); var refitSettings = new RefitSettings { UrlParameterKeyFormatter = urlParameterKeyFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexQueryObjectWithDictionary) ); var complexQuery = new ComplexQueryObject { TestAlias2 = "value1" }; var output = factory(new object[] { complexQuery }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?testAlias2=value1", uri.PathAndQuery); } [Fact] public void ComplexQueryObjectWithAliasedDictionaryProducesCorrectQueryString() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexQueryObjectWithDictionary) ); var complexQuery = new ComplexQueryObject { TestAliasedDictionary = new Dictionary { { TestEnum.A, "value1" }, { TestEnum.B, "value2" }, }, }; var output = factory(new object[] { complexQuery }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( "/foo?test-dictionary-alias.A=value1&test-dictionary-alias.B=value2", uri.PathAndQuery ); } [Fact] public void ComplexQueryObjectWithDictionaryProducesCorrectQueryString() { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexQueryObjectWithDictionary) ); var complexQuery = new ComplexQueryObject { TestDictionary = new Dictionary { { TestEnum.A, "value1" }, { TestEnum.B, "value2" }, }, }; var output = factory(new object[] { complexQuery }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/foo?TestDictionary.A=value1&TestDictionary.B=value2", uri.PathAndQuery); } [Fact] public void ComplexQueryObjectWithDictionaryAndCustomFormatterProducesCorrectQueryString() { var urlParameterFormatter = new TestEnumUrlParameterFormatter(); var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter }; var fixture = new RequestBuilderImplementation(refitSettings); var factory = fixture.BuildRequestFactoryForMethod( nameof(IDummyHttpApi.ComplexQueryObjectWithDictionary) ); var complexQuery = new ComplexQueryObject { TestDictionary = new Dictionary { { TestEnum.A, "value1" }, { TestEnum.B, "value2" }, }, }; var output = factory(new object[] { complexQuery }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal( $"/foo?TestDictionary.{(int)TestEnum.A}=value1{TestEnumUrlParameterFormatter.StringParameterSuffix}&TestDictionary.{(int)TestEnum.B}=value2{TestEnumUrlParameterFormatter.StringParameterSuffix}", uri.PathAndQuery ); } } public record Foo { public override string ToString() { return "foo"; } } static class RequestBuilderTestExtensions { public static Func BuildRequestFactoryForMethod( this IRequestBuilder builder, string methodName, string baseAddress = "http://api/" ) { var factory = builder.BuildRestResultFuncForMethod(methodName); var testHttpMessageHandler = new TestHttpMessageHandler(); return paramList => { var task = (Task)factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri(baseAddress) }, paramList ); task.Wait(); return testHttpMessageHandler.RequestMessage; }; } public static Func RunRequest( this IRequestBuilder builder, string methodName, string returnContent = null, string baseAddress = "http://api/" ) { var factory = builder.BuildRestResultFuncForMethod(methodName); var testHttpMessageHandler = new TestHttpMessageHandler(); if (returnContent != null) { testHttpMessageHandler.Content = new StringContent(returnContent); } return paramList => { var task = (Task)factory( new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri(baseAddress) }, paramList ); try { task.Wait(); } catch (AggregateException e) when (e.InnerException is TaskCanceledException) { } return testHttpMessageHandler; }; } } } ================================================ FILE: Refit.Tests/ResponseTests.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Newtonsoft.Json; using Refit; using Refit.Buffers; using RichardSzalay.MockHttp; // for the code gen using Xunit; using JsonSerializer = Newtonsoft.Json.JsonSerializer; namespace Refit.Tests; public class TestAliasObject { [AliasAs("FIELD_WE_SHOULD_SHORTEN_WITH_ALIAS_AS")] public string ShortNameForAlias { get; set; } [JsonProperty(PropertyName = "FIELD_WE_SHOULD_SHORTEN_WITH_JSON_PROPERTY")] [JsonPropertyName("FIELD_WE_SHOULD_SHORTEN_WITH_JSON_PROPERTY")] public string ShortNameForJsonProperty { get; set; } } public class ResponseTests { readonly MockHttpMessageHandler mockHandler; readonly IMyAliasService fixture; public ResponseTests() { mockHandler = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler }; fixture = RestService.For("http://api", settings); } public interface IMyAliasService { [Get("/aliasTest")] Task GetTestObject(); [Get("/GetApiResponseTestObject")] Task> GetApiResponseTestObject(); [Get("/GetIApiResponse")] Task GetIApiResponse(); } [Fact] public async Task JsonPropertyCanBeUsedToAliasFieldNamesInResponses() { mockHandler .Expect(HttpMethod.Get, "http://api/aliasTest") .Respond( "application/json", "{\"FIELD_WE_SHOULD_SHORTEN_WITH_ALIAS_AS\": \"Hello\", \"FIELD_WE_SHOULD_SHORTEN_WITH_JSON_PROPERTY\": \"World\"}" ); var result = await fixture.GetTestObject(); Assert.Equal("World", result.ShortNameForJsonProperty); } /// /// Even though it may seem like AliasAs and JsonProperty are used interchangeably in some places, /// when serializing responses, AliasAs will not work -- only JsonProperty will. /// [Fact] public async Task AliasAsCannotBeUsedToAliasFieldNamesInResponses() { mockHandler .Expect(HttpMethod.Get, "http://api/aliasTest") .Respond( "application/json", "{\"FIELD_WE_SHOULD_SHORTEN_WITH_ALIAS_AS\": \"Hello\", \"FIELD_WE_SHOULD_SHORTEN_WITH_JSON_PROPERTY\": \"World\"}" ); var result = await fixture.GetTestObject(); Assert.Null(result.ShortNameForAlias); } /// /// Test to verify if a ValidationException is thrown for a Bad Request in terms of RFC 7807 /// [Fact] public async Task ThrowsValidationException() { var expectedContent = new ProblemDetails { Detail = "detail", Errors = { { "Field1", new string[] { "Problem1" } }, { "Field2", new string[] { "Problem2" } } }, Instance = "instance", Status = 1, Title = "title", Type = "type" }; var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(JsonConvert.SerializeObject(expectedContent)) }; expectedResponse.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/problem+json"); mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest").Respond(req => expectedResponse); var actualException = await Assert.ThrowsAsync( () => fixture.GetTestObject() ); Assert.NotNull(actualException.Content); Assert.Equal("detail", actualException.Content.Detail); Assert.Equal("Problem1", actualException.Content.Errors["Field1"][0]); Assert.Equal("Problem2", actualException.Content.Errors["Field2"][0]); Assert.Equal("instance", actualException.Content.Instance); Assert.Equal(1, actualException.Content.Status); Assert.Equal("title", actualException.Content.Title); Assert.Equal("type", actualException.Content.Type); } /// /// Test to verify if EnsureSuccessStatusCodeAsync throws a ValidationApiException for a Bad Request in terms of RFC 7807 /// [Fact] public async Task When_BadRequest_EnsureSuccessStatusCodeAsync_ThrowsValidationException() { var expectedContent = new ProblemDetails { Detail = "detail", Errors = { { "Field1", new string[] { "Problem1" } }, { "Field2", new string[] { "Problem2" } } }, Instance = "instance", Status = 1, Title = "title", Type = "type" }; var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(JsonConvert.SerializeObject(expectedContent)) }; expectedResponse.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/problem+json"); mockHandler .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") .Respond(req => expectedResponse); using var response = await fixture.GetApiResponseTestObject(); var actualException = await Assert.ThrowsAsync( () => response.EnsureSuccessStatusCodeAsync() ); Assert.NotNull(actualException.Content); Assert.Equal("detail", actualException.Content.Detail); Assert.Equal("Problem1", actualException.Content.Errors["Field1"][0]); Assert.Equal("Problem2", actualException.Content.Errors["Field2"][0]); Assert.Equal("instance", actualException.Content.Instance); Assert.Equal(1, actualException.Content.Status); Assert.Equal("title", actualException.Content.Title); Assert.Equal("type", actualException.Content.Type); } /// /// Test to verify if IsSuccessful returns false if we have a success status code, but there is a deserialization error /// [Fact] public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccessful_ShouldReturnFalse() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Invalid JSON") }; mockHandler .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") .Respond(req => expectedResponse); using var response = await fixture.GetApiResponseTestObject(); Assert.True(response.IsSuccessStatusCode); Assert.False(response.IsSuccessful); Assert.NotNull(response.Error); } /// /// Test to verify if EnsureSuccessStatusCodeAsync do not throw an ApiException if we have a success status code, but there is a deserialization error /// [Fact] public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccesStatusCodeAsync_DoNotThrowApiException() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Invalid JSON") }; mockHandler .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") .Respond(req => expectedResponse); using var response = await fixture.GetApiResponseTestObject(); await response.EnsureSuccessStatusCodeAsync(); Assert.True(response.IsSuccessStatusCode); Assert.False(response.IsSuccessful); Assert.NotNull(response.Error); } /// /// Test to verify if EnsureSuccessfulAsync throws an ApiException if we have a success status code, but there is a deserialization error /// [Fact] public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessfulAsync_ThrowsApiException() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Invalid JSON") }; mockHandler .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") .Respond(req => expectedResponse); using var response = await fixture.GetApiResponseTestObject(); var actualException = await Assert.ThrowsAsync( () => response.EnsureSuccessfulAsync() ); Assert.True(response.IsSuccessStatusCode); Assert.False(response.IsSuccessful); Assert.NotNull(actualException); Assert.IsType(actualException.InnerException); } [Fact] public async Task WhenProblemDetailsResponseContainsExtensions_ShouldHydrateExtensions() { var expectedContent = new { Detail = "detail", Instance = "instance", Status = 1, Title = "title", Type = "type", Foo = "bar", Baz = 123d, }; var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(JsonConvert.SerializeObject(expectedContent)) }; expectedResponse.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/problem+json"); mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest").Respond(req => expectedResponse); mockHandler.Expect(HttpMethod.Get, "http://api/soloyolo").Respond(req => expectedResponse); var actualException = await Assert.ThrowsAsync( () => fixture.GetTestObject() ); Assert.NotNull(actualException.Content); Assert.Equal("detail", actualException.Content.Detail); Assert.Equal("instance", actualException.Content.Instance); Assert.Equal(1, actualException.Content.Status); Assert.Equal("title", actualException.Content.Title); Assert.Equal("type", actualException.Content.Type); Assert.Collection( actualException.Content.Extensions, kvp => Assert.Equal( new KeyValuePair( nameof(expectedContent.Foo), expectedContent.Foo ), kvp ), kvp => Assert.Equal( new KeyValuePair( nameof(expectedContent.Baz), expectedContent.Baz ), kvp ) ); } [Fact] public async Task WithNonSeekableStream_UsingSystemTextJsonContentSerializer() { var model = new TestAliasObject { ShortNameForAlias = nameof(WithNonSeekableStream_UsingSystemTextJsonContentSerializer), ShortNameForJsonProperty = nameof(TestAliasObject) }; var localHandler = new MockHttpMessageHandler(); var settings = new RefitSettings(new SystemTextJsonContentSerializer()) { HttpMessageHandlerFactory = () => localHandler }; using var utf8BufferWriter = new PooledBufferWriter(); var utf8JsonWriter = new Utf8JsonWriter(utf8BufferWriter); System.Text.Json.JsonSerializer.Serialize(utf8JsonWriter, model); using var sourceStream = utf8BufferWriter.DetachStream(); using var contentStream = new ThrowOnGetLengthMemoryStream { CanGetLength = true }; sourceStream.CopyTo(contentStream); contentStream.Position = 0; contentStream.CanGetLength = false; var httpContent = new StreamContent(contentStream) { Headers = { ContentType = new MediaTypeHeaderValue("application/json") { CharSet = Encoding.UTF8.WebName } } }; var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = httpContent }; expectedResponse.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); expectedResponse.StatusCode = HttpStatusCode.OK; localHandler .Expect(HttpMethod.Get, "http://api/aliasTest") .Respond(req => expectedResponse); var localFixture = RestService.For("http://api", settings); var result = await localFixture.GetTestObject(); Assert.NotNull(result); Assert.Equal( nameof(WithNonSeekableStream_UsingSystemTextJsonContentSerializer), result.ShortNameForAlias ); Assert.Equal(nameof(TestAliasObject), result.ShortNameForJsonProperty); } [Fact] public async Task BadRequestWithEmptyContent_ShouldReturnApiException() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("Hello world") }; expectedResponse.Content.Headers.Clear(); mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest").Respond(req => expectedResponse); var actualException = await Assert.ThrowsAsync(() => fixture.GetTestObject()); Assert.NotNull(actualException.Content); Assert.Equal("Hello world", actualException.Content); } [Fact] public async Task BadRequestWithEmptyContent_ShouldReturnApiResponse() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("Hello world") }; expectedResponse.Content.Headers.Clear(); mockHandler .Expect(HttpMethod.Get, $"http://api/{nameof(fixture.GetApiResponseTestObject)}") .Respond(req => expectedResponse); var apiResponse = await fixture.GetApiResponseTestObject(); Assert.NotNull(apiResponse); Assert.NotNull(apiResponse.Error); Assert.NotNull(apiResponse.Error.Content); Assert.Equal("Hello world", apiResponse.Error.Content); } [Fact] public async Task BadRequestWithStringContent_ShouldReturnIApiResponse() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("Hello world") }; expectedResponse.Content.Headers.Clear(); mockHandler .Expect(HttpMethod.Get, $"http://api/{nameof(fixture.GetIApiResponse)}") .Respond(req => expectedResponse); var apiResponse = await fixture.GetIApiResponse(); Assert.NotNull(apiResponse); Assert.NotNull(apiResponse.Error); Assert.NotNull(apiResponse.Error.Content); Assert.Equal("Hello world", apiResponse.Error.Content); } [Fact] public async Task ValidationApiException_HydratesBaseContent() { var expectedProblemDetails = new ProblemDetails { Detail = "detail", Instance = "instance", Status = 1, Title = "title", Type = "type" }; var expectedContent = JsonConvert.SerializeObject(expectedProblemDetails); var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(expectedContent) }; expectedResponse.Content.Headers.ContentType = new MediaTypeHeaderValue( "application/problem+json" ); mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest").Respond(req => expectedResponse); var actualException = await Assert.ThrowsAsync( () => fixture.GetTestObject() ); var actualBaseException = actualException as ApiException; Assert.Equal(expectedContent, actualBaseException.Content); } [Fact] public async Task WithHtmlResponse_ShouldReturnApiException() { const string htmlResponse = "Hello world"; var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(htmlResponse) }; expectedResponse.Content.Headers.Clear(); mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest").Respond(req => expectedResponse); var actualException = await Assert.ThrowsAsync(() => fixture.GetTestObject()); Assert.IsType(actualException.InnerException); Assert.NotNull(actualException.Content); Assert.Equal(htmlResponse, actualException.Content); } [Fact] public async Task WithHtmlResponse_ShouldReturnApiResponse() { const string htmlResponse = "Hello world"; var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(htmlResponse) }; expectedResponse.Content.Headers.Clear(); mockHandler .Expect(HttpMethod.Get, $"http://api/{nameof(fixture.GetApiResponseTestObject)}") .Respond(req => expectedResponse); var apiResponse = await fixture.GetApiResponseTestObject(); Assert.NotNull(apiResponse.Error); Assert.IsType(apiResponse.Error.InnerException); Assert.NotNull(apiResponse.Error.Content); Assert.Equal(htmlResponse, apiResponse.Error.Content); } [Fact] public async Task WithNonJsonResponseUsingNewtonsoftJsonContentSerializer_ShouldReturnApiException() { var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, ContentSerializer = new NewtonsoftJsonContentSerializer() }; var newtonSoftFixture = RestService.For("http://api", settings); const string nonJsonResponse = "bad response"; var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(nonJsonResponse) }; expectedResponse.Content.Headers.Clear(); mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest").Respond(req => expectedResponse); var actualException = await Assert.ThrowsAsync( () => newtonSoftFixture.GetTestObject() ); Assert.IsType(actualException.InnerException); Assert.NotNull(actualException.Content); Assert.Equal(nonJsonResponse, actualException.Content); } [Fact] public async Task WithNonJsonResponseUsingNewtonsoftJsonContentSerializer_ShouldReturnApiResponse() { var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHandler, ContentSerializer = new NewtonsoftJsonContentSerializer() }; var newtonSoftFixture = RestService.For("http://api", settings); const string nonJsonResponse = "bad response"; var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(nonJsonResponse) }; expectedResponse.Content.Headers.Clear(); mockHandler .Expect(HttpMethod.Get, $"http://api/{nameof(fixture.GetApiResponseTestObject)}") .Respond(req => expectedResponse); var apiResponse = await newtonSoftFixture.GetApiResponseTestObject(); Assert.NotNull(apiResponse.Error); Assert.IsType(apiResponse.Error.InnerException); Assert.NotNull(apiResponse.Error.Content); Assert.Equal(nonJsonResponse, apiResponse.Error.Content); } } public sealed class ThrowOnGetLengthMemoryStream : MemoryStream { public bool CanGetLength { get; set; } public override bool CanSeek { get => CanGetLength; } public override long Length => CanGetLength ? base.Length : throw new NotSupportedException(); } ================================================ FILE: Refit.Tests/RestService.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Reactive.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Refit; // InterfaceStubGenerator looks for this using RichardSzalay.MockHttp; using Xunit; namespace Refit.Tests; #pragma warning disable IDE1006 // Naming Styles public class RootObject { public string _id { get; set; } public string _rev { get; set; } public string name { get; set; } } #pragma warning restore IDE1006 // Naming Styles public class BigObject { public byte[] BigData { get; set; } } [Headers("User-Agent: Refit Integration Tests")] public interface INpmJs { [Get("/congruence")] Task GetCongruence(); } public interface IRequestBin { [Post("/1h3a5jm1")] Task Post(); [Post("/foo")] Task PostRawStringDefault([Body] string str); [Post("/foo")] Task PostRawStringJson([Body(BodySerializationMethod.Serialized)] string str); [Post("/foo")] Task PostRawStringUrlEncoded([Body(BodySerializationMethod.UrlEncoded)] string str); [Post("/1h3a5jm1")] Task PostGeneric(T param); [Post("/foo")] Task PostVoidReturnBodyBuffered([Body(buffered: true)] T param); [Post("/foo")] Task PostNonVoidReturnBodyBuffered([Body(buffered: true)] T param); [Post("/big")] Task PostBig(BigObject big); [Get("/foo/{arguments}")] Task SomeApiThatUsesVariableNameFromCodeGen(string arguments); } public interface IApiBindPathToObject { [Get("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task GetFooBars(PathBoundObject request); [Get("/foos/{Requestparams.SomeProperty}/bar/{requestParams.SoMeProPerty2}")] Task GetFooBarsWithDifferentCasing(PathBoundObject requestParams); [Get("/foos/{id}/{request.someProperty}/bar/{request.someProperty2}")] Task GetBarsByFoo(string id, PathBoundObject request); [Get("/foos/{someProperty}/bar/{request.someProperty2}")] Task GetFooBars(PathBoundObject request, string someProperty); [Get("/foos/{request.someProperty}/bar")] Task GetBarsByFoo(PathBoundObject request); [Get("/foo")] Task GetBarsWithCustomQueryFormat(PathBoundObjectWithQueryFormat request); [Get("/foos/{request.someProperty}/bar/{request.someProperty3}")] Task GetFooBarsDerived(PathBoundDerivedObject request); [Get("/foos/{request.values}")] Task GetFoos(PathBoundList request); [Get("/foos2/{values}")] Task GetFoos2(List values); [Post("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task PostFooBar(PathBoundObject request, [Body] object someObject); [Get("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task GetFooBars(PathBoundObjectWithQuery request); [Post("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task PostFooBar( PathBoundObject request, [Query] ModelObject someQueryParams ); [Multipart] [Post("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task PostFooBarStreamPart( PathBoundObject request, [Query] ModelObject someQueryParams, StreamPart stream ); [Multipart] [Post("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task PostFooBarStreamPart(PathBoundObject request, StreamPart stream); [Multipart] [Post("/foos/{request.someProperty}/bar/{request.someProperty2}")] Task PostFooBarStreamPart( PathBoundObjectWithQuery request, StreamPart stream ); } public class PathBoundList { public List Values { get; set; } } public class PathBoundDerivedObject : PathBoundObject { public string SomeProperty3 { get; set; } } public class PathBoundObject { public int SomeProperty { get; set; } public string SomeProperty2 { get; set; } } public class PathBoundObjectWithQuery { public int SomeProperty { get; set; } public string SomeProperty2 { get; set; } [Query] public string SomeQuery { get; set; } } public class PathBoundObjectWithQueryFormat { [Query(Format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'")] public DateTime SomeQueryWithFormat { get; set; } } public interface INoRefitHereBuddy { Task Post(); } public interface IAmHalfRefit { [Post("/anything")] Task Post(); Task Get(); } public interface IRefitInterfaceWithStaticMethod { [Get("")] Task Get(); #if NETCOREAPP3_1_OR_GREATER public static IRefitInterfaceWithStaticMethod Create() { // This is a C# 8 factory method return RestService.For("http://foo/"); } #endif } public class ErrorResponse { public string[] Errors { get; set; } } public interface IHttpBinApi where TResponse : class where THeader : struct { [Get("")] Task Get(TParam param, [Header("X-Refit")] THeader header); [Get("/get?hardcoded=true")] Task GetQuery([Query("_")] TParam param); [Post("/post?hardcoded=true")] Task PostQuery([Query("_")] TParam param); [Get("")] Task GetQueryWithIncludeParameterName([Query(".", "search")] TParam param); [Get("/get?hardcoded=true")] Task GetQuery1([Query("_")] TParam param); } public interface IBrokenWebApi { [Post("/what-spec")] Task PostAValue([Body] string derp); } public interface IHttpContentApi { [Post("/blah")] Task PostFileUpload([Body] HttpContent content); [Post("/blah")] Task> PostFileUploadWithMetadata([Body] HttpContent content); } public interface IStreamApi { [Post("/{filename}")] Task GetRemoteFile(string filename); [Post("/{filename}")] Task> GetRemoteFileWithMetadata(string filename); } public interface IApiWithDecimal { [Get("/withDecimal")] Task GetWithDecimal(decimal value); } public interface IBodylessApi { [Post("/nobody")] [Headers("Content-Type: application/x-www-form-urlencoded; charset=UTF-8")] Task Post(); [Get("/nobody")] [Headers("Content-Type: application/x-www-form-urlencoded; charset=UTF-8")] Task Get(); [Head("/nobody")] [Headers("Content-Type: application/x-www-form-urlencoded; charset=UTF-8")] Task Head(); } public interface ITrimTrailingForwardSlashApi { HttpClient Client { get; } [Get("/someendpoint")] Task Get(); } public interface IValidApi { [Get("/someendpoint")] Task Get(); } public interface IQueryApi { [Get("/foo?")] Task EmptyQuery(); [Get("/foo? ")] Task WhiteSpaceQuery(); [Get("/foo?=value")] Task EmptyQueryKey(); [Get("/foo?key=")] Task EmptyQueryValue(); [Get("/foo?=")] Task EmptyQueryKeyAndValue(); [Get("/foo?key,=value,&key1(=value1(")] Task UnescapedQuery(); [Get("/foo?key%2C=value%2C&key1%28=value1%28")] Task EscapedQuery(); [Get("/foo?{key}={value}")] Task ParameterMappedQuery(string key, string value); [Get("/foo")] Task NullableIntCollectionQuery([Query] int?[] values); } public interface IFragmentApi { [Get("/foo#name")] Task Fragment(); [Get("/foo#")] Task EmptyFragment(); [Get("/foo#first#second")] Task ManyFragments(); [Get("/foo#{frag}")] Task ParameterFragment(string frag); [Get("/foo?key=value#name")] Task FragmentAfterQuery(); [Get("/foo#?key=value")] Task QueryAfterFragment(); } public interface ICancellableApi { [Get("/foo")] Task GetWithCancellation(CancellationToken token = default); [Get("/foo")] Task GetWithCancellationAndReturn(CancellationToken token = default); [Get("/foo")] Task GetWithNullableCancellation(CancellationToken? token); } public class HttpBinGet { public Dictionary Args { get; set; } public Dictionary Headers { get; set; } public string Origin { get; set; } public string Url { get; set; } } public class RestServiceIntegrationTests { #if NETCOREAPP3_1_OR_GREATER [Fact] public void CanCreateInstanceUsingStaticMethod() { var instance = IRefitInterfaceWithStaticMethod.Create(); Assert.NotNull(instance); } #endif [Fact] public async Task CanAddContentHeadersToPostWithoutBody() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Post, "http://foo/nobody") // The content length header is set automatically by the HttpContent instance, // so checking the header as a string doesn't work .With(r => r.Content?.Headers.ContentLength == 0) // But we added content type ourselves, so this should work .WithHeaders("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") .WithContent("") .Respond("application/json", "Ok"); var fixture = RestService.For("http://foo", settings); await fixture.Post(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithNoParametersTest() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/someendpoint") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.Get(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task BaseAddressFromHttpClientMatchesTest() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/someendpoint") .WithExactQueryString("") .Respond("application/json", "Ok"); var client = new HttpClient(mockHttp) { BaseAddress = new Uri("http://foo") }; var fixture = RestService.For(client); await fixture.Get(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task BaseAddressWithTrailingSlashFromHttpClientMatchesTest() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/someendpoint") .WithExactQueryString("") .Respond("application/json", "Ok"); var client = new HttpClient(mockHttp) { BaseAddress = new Uri("http://foo/") }; var fixture = RestService.For(client); await fixture.Get(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task BaseAddressWithTrailingSlashCalledBeforeFromHttpClientMatchesTest() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/someendpoint") .WithExactQueryString("") .Respond("application/json", "Ok"); var client = new HttpClient(mockHttp) { BaseAddress = new Uri("http://foo/") }; _ = await client.GetAsync(new Uri("/firstRequest", UriKind.RelativeOrAbsolute)); var fixture = RestService.For(client); await fixture.Get(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithNoParametersTestTrailingSlashInBase() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/someendpoint") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo/", settings); await fixture.Get(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObject() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/1/bar/barNone") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetFooBars( new PathBoundObject() { SomeProperty = 1, SomeProperty2 = "barNone" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithLongPathBoundObject() { var mockHttp = new MockHttpMessageHandler(); var longPathString = string.Concat(Enumerable.Repeat("barNone", 1000)); mockHttp .Expect(HttpMethod.Get, $"http://foo/foos/12345/bar/{longPathString}") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetFooBars( new PathBoundObject() { SomeProperty = 12345, SomeProperty2 = longPathString } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectDifferentCasing() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/1/bar/barNone") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetFooBarsWithDifferentCasing( new PathBoundObject() { SomeProperty = 1, SomeProperty2 = "barNone" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectAndParameter() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/myId/22/bar/bart") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetBarsByFoo( "myId", new PathBoundObject() { SomeProperty = 22, SomeProperty2 = "bart" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectAndParameterParameterPrecedence() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/chooseMe/bar/barNone") .WithExactQueryString(new[] { new KeyValuePair("SomeProperty", "1") }) .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetFooBars( new PathBoundObject() { SomeProperty = 1, SomeProperty2 = "barNone" }, "chooseMe" ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundDerivedObject() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/1/bar/test") .WithExactQueryString( new[] { new KeyValuePair("SomeProperty2", "barNone") } ) .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetFooBarsDerived( new PathBoundDerivedObject() { SomeProperty = 1, SomeProperty2 = "barNone", SomeProperty3 = "test" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithDerivedObjectAsBaseType() { // possibly a bug see https://github.com/reactiveui/refit/issues/1882 var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/1/bar") .WithExactQueryString( new[] { new KeyValuePair("SomeProperty3", "test"), new KeyValuePair("SomeProperty2", "barNone"), new KeyValuePair("SomeProperty", "1") } ) .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetBarsByFoo( new PathBoundDerivedObject() { SomeProperty = 1, SomeProperty2 = "barNone", SomeProperty3 = "test" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectAndQueryParameter() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/22/bar") .WithExactQueryString( new[] { new KeyValuePair("SomeProperty2", "bart") } ) .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetBarsByFoo( new PathBoundObject() { SomeProperty = 22, SomeProperty2 = "bart" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostFooBarPathBoundObject() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Post, "http://foo/foos/22/bar/bart") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.PostFooBar( new PathBoundObject() { SomeProperty = 22, SomeProperty2 = "bart" }, new { } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PathBoundObjectsRespectFormatter() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/22%2C23") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, UrlParameterFormatter = new TestEnumerableUrlParameterFormatter() }; var fixture = RestService.For("http://foo", settings); await fixture.GetFoos( new PathBoundList() { Values = new List() { 22, 23 } } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectAndQuery() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foos/1/bar/barNone") .WithExactQueryString("SomeQuery=test") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetFooBars( new PathBoundObjectWithQuery() { SomeProperty = 1, SomeProperty2 = "barNone", SomeQuery = "test" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectAndQueryWithFormat() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://foo/foo") .WithExactQueryString("SomeQueryWithFormat=2020-03-05T13:55:00Z") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.GetBarsWithCustomQueryFormat( new PathBoundObjectWithQueryFormat { SomeQueryWithFormat = new DateTime(2020, 03, 05, 13, 55, 00) } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithPathBoundObjectAndQueryObject() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Post, "http://foo/foos/1/bar/barNone") .WithExactQueryString("Property1=test&Property2=test2") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); await fixture.PostFooBar( new PathBoundObject() { SomeProperty = 1, SomeProperty2 = "barNone" }, new ModelObject() { Property1 = "test", Property2 = "test2" } ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostFooBarPathMultipart() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Post, "http://foo/foos/22/bar/bar") .WithExactQueryString("") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); using var stream = GetTestFileStream("Test Files/Test.pdf"); await fixture.PostFooBarStreamPart( new PathBoundObject() { SomeProperty = 22, SomeProperty2 = "bar" }, new StreamPart(stream, "Test.pdf", "application/pdf") ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostFooBarPathQueryMultipart() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Post, "http://foo/foos/22/bar/bar") .WithExactQueryString("SomeQuery=test") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); using var stream = GetTestFileStream("Test Files/Test.pdf"); await fixture.PostFooBarStreamPart( new PathBoundObjectWithQuery() { SomeProperty = 22, SomeProperty2 = "bar", SomeQuery = "test" }, new StreamPart(stream, "Test.pdf", "application/pdf") ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostFooBarPathQueryObjectMultipart() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Post, "http://foo/foos/22/bar/bar") .WithExactQueryString("Property1=test&Property2=test2") .Respond("application/json", "Ok"); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://foo", settings); using var stream = GetTestFileStream("Test Files/Test.pdf"); await fixture.PostFooBarStreamPart( new PathBoundObject { SomeProperty = 22, SomeProperty2 = "bar" }, new ModelObject() { Property1 = "test", Property2 = "test2" }, new StreamPart(stream, "Test.pdf", "application/pdf") ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task DoesntAddAutoAddContentToGetRequest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "http://foo/nobody") // We can't add HttpContent to a GET request, // because HttpClient doesn't allow it and it will // blow up at runtime .With(r => r.Content == null) .Respond("application/json", "Ok"); var fixture = RestService.For("http://foo", settings); await fixture.Get(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task DoesntAddAutoAddContentToHeadRequest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Head, "http://foo/nobody") // We can't add HttpContent to a HEAD request, // because HttpClient doesn't allow it and it will // blow up at runtime .With(r => r.Content == null) .Respond("application/json", "Ok"); var fixture = RestService.For("http://foo", settings); await fixture.Head(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task GetWithDecimal() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "http://foo/withDecimal") .WithExactQueryString(new[] { new KeyValuePair("value", "3.456") }) .Respond("application/json", "Ok"); var fixture = RestService.For("http://foo", settings); const decimal val = 3.456M; var result = await fixture.GetWithDecimal(val); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubUserApiAsApiResponse() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; var responseMessage = new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent( "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }", System.Text.Encoding.UTF8, "application/json" ), }; responseMessage.Headers.Add("Cookie", "Value"); mockHttp .Expect(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond(req => responseMessage); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.GetUserWithMetadata("octocat"); Assert.True(result.Headers.Any()); Assert.True(result.IsSuccessStatusCode); Assert.NotNull(result.ReasonPhrase); Assert.NotNull(result.RequestMessage); Assert.False(result.StatusCode == default); Assert.NotNull(result.Version); Assert.Equal("octocat", result.Content.Login); Assert.False(string.IsNullOrEmpty(result.Content.AvatarUrl)); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheNonExistentApiAsApiResponse() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/give-me-some-404-action") .Respond(HttpStatusCode.NotFound); var fixture = RestService.For("https://api.github.com", settings); using var result = await fixture.NothingToSeeHereWithMetadata(); Assert.False(result.IsSuccessStatusCode); Assert.NotNull(result.ReasonPhrase); Assert.NotNull(result.RequestMessage); Assert.True(result.StatusCode == HttpStatusCode.NotFound); Assert.NotNull(result.Version); Assert.Null(result.Content); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheNonExistentApi() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/give-me-some-404-action") .Respond(HttpStatusCode.NotFound); var fixture = RestService.For("https://api.github.com", settings); try { var result = await fixture.NothingToSeeHere(); } catch (Exception ex) { Assert.IsType(ex); } mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubUserApiAsObservableApiResponse() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; var responseMessage = new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent( "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }", System.Text.Encoding.UTF8, "application/json" ), }; responseMessage.Headers.Add("Cookie", "Value"); mockHttp .Expect(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond(req => responseMessage); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture .GetUserObservableWithMetadata("octocat") .Timeout(TimeSpan.FromSeconds(10)); Assert.True(result.Headers.Any()); Assert.True(result.IsSuccessStatusCode); Assert.NotNull(result.ReasonPhrase); Assert.NotNull(result.RequestMessage); Assert.False(result.StatusCode == default); Assert.NotNull(result.Version); Assert.Equal("octocat", result.Content.Login); Assert.False(string.IsNullOrEmpty(result.Content.AvatarUrl)); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubUserApiAsObservableIApiResponse() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; var responseMessage = new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent( "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }", System.Text.Encoding.UTF8, "application/json" ), }; responseMessage.Headers.Add("Cookie", "Value"); mockHttp .Expect(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond(req => responseMessage); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture .GetUserIApiResponseObservableWithMetadata("octocat") .Timeout(TimeSpan.FromSeconds(10)); Assert.True(result.Headers.Any()); Assert.True(result.IsSuccessStatusCode); Assert.NotNull(result.ReasonPhrase); Assert.NotNull(result.RequestMessage); Assert.False(result.StatusCode == default); Assert.NotNull(result.Version); Assert.Equal("octocat", result.Content.Login); Assert.False(string.IsNullOrEmpty(result.Content.AvatarUrl)); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubUserApi() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond("application/json", "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }"); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.GetUser("octocat"); Assert.Equal("octocat", result.Login); Assert.False(string.IsNullOrEmpty(result.AvatarUrl)); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitWithCamelCaseParameter() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond("application/json", "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }"); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.GetUserCamelCase("octocat"); Assert.Equal("octocat", result.Login); Assert.False(string.IsNullOrEmpty(result.AvatarUrl)); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubOrgMembersApi() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/orgs/github/members") .Respond( "application/json", "[{ 'login':'octocat', 'avatar_url':'http://foo/bar', 'type':'User'}]" ); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.GetOrgMembers("github"); Assert.True(result.Count > 0); Assert.Contains(result, member => member.Type == "User"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubOrgMembersApiInParallel() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/orgs/github/members") .Respond( "application/json", "[{ 'login':'octocat', 'avatar_url':'http://foo/bar', 'type':'User'}]" ); mockHttp .Expect(HttpMethod.Get, "https://api.github.com/orgs/github/members") .Respond( "application/json", "[{ 'login':'octocat', 'avatar_url':'http://foo/bar', 'type':'User'}]" ); var fixture = RestService.For("https://api.github.com", settings); var task1 = fixture.GetOrgMembers("github"); var task2 = fixture.GetOrgMembers("github"); await Task.WhenAll(task1, task2); Assert.True(task1.Result.Count > 0); Assert.Contains(task1.Result, member => member.Type == "User"); Assert.True(task2.Result.Count > 0); Assert.Contains(task2.Result, member => member.Type == "User"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task RequestCanceledBeforeResponseRead() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; var cts = new CancellationTokenSource(); mockHttp .When(HttpMethod.Get, "https://api.github.com/orgs/github/members") .Respond(req => { // Cancel the request cts.Cancel(); return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent( "[{ 'login':'octocat', 'avatar_url':'http://foo/bar', 'type':'User'}]", Encoding.UTF8, "application/json" ) }; }); var fixture = RestService.For("https://api.github.com", settings); var result = await Assert.ThrowsAsync( async () => await fixture.GetOrgMembers("github", cts.Token) ); AssertStackTraceContains(nameof(IGitHubApi.GetOrgMembers), result.StackTrace); } [Fact] public async Task HitTheGitHubUserSearchApi() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/search/users") .WithQueryString("q", "tom repos:>42 followers:>1000") .Respond( "application/json", "{ 'total_count': 1, 'items': [{ 'login':'octocat', 'avatar_url':'http://foo/bar', 'type':'User'}]}" ); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.FindUsers("tom repos:>42 followers:>1000"); Assert.True(result.TotalCount > 0); Assert.Contains(result.Items, member => member.Type == "User"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubUserApiAsObservable() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond("application/json", "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }"); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.GetUserObservable("octocat").Timeout(TimeSpan.FromSeconds(10)); Assert.Equal("octocat", result.Login); Assert.False(string.IsNullOrEmpty(result.AvatarUrl)); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task HitTheGitHubUserApiAsObservableAndSubscribeAfterTheFact() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .When(HttpMethod.Get, "https://api.github.com/users/octocat") .Respond("application/json", "{ 'login':'octocat', 'avatar_url':'http://foo/bar' }"); var fixture = RestService.For("https://api.github.com", settings); var obs = fixture.GetUserObservable("octocat").Timeout(TimeSpan.FromSeconds(10)); // NB: We're gonna await twice, so that the 2nd await is definitely // after the result has completed. await obs; var result2 = await obs; Assert.Equal("octocat", result2.Login); Assert.False(string.IsNullOrEmpty(result2.AvatarUrl)); } [Fact] public async Task TwoSubscriptionsResultInTwoRequests() { var input = new TestHttpMessageHandler { // we need to use a factory here to ensure each request gets its own httpcontent instance ContentFactory = () => new StringContent("test") }; var client = new HttpClient(input) { BaseAddress = new Uri("http://foo") }; var fixture = RestService.For(client); Assert.Equal(0, input.MessagesSent); var obs = fixture.GetIndexObservable().Timeout(TimeSpan.FromSeconds(10)); var result1 = await obs; Assert.Equal(1, input.MessagesSent); var result2 = await obs; Assert.Equal(2, input.MessagesSent); // NB: TestHttpMessageHandler returns what we tell it to ('test' by default) Assert.Contains("test", result1); Assert.Contains("test", result2); } [Fact] public async Task ShouldRetHttpResponseMessage() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp.When(HttpMethod.Get, "https://api.github.com/").Respond(HttpStatusCode.OK); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.GetIndex(); Assert.NotNull(result); Assert.True(result.IsSuccessStatusCode); } [Fact] public async Task ShouldRetHttpResponseMessageWithNestedInterface() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp.When(HttpMethod.Get, "https://api.github.com/").Respond(HttpStatusCode.OK); var fixture = RestService.For( "https://api.github.com", settings ); var result = await fixture.GetIndex(); Assert.NotNull(result); Assert.True(result.IsSuccessStatusCode); } [Fact] public async Task HitTheNpmJs() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://registry.npmjs.org/congruence") .Respond( "application/json", "{ \"_id\":\"congruence\", \"_rev\":\"rev\" , \"name\":\"name\"}" ); var fixture = RestService.For("https://registry.npmjs.org", settings); var result = await fixture.GetCongruence(); Assert.Equal("congruence", result._id); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostToRequestBin() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp.Expect(HttpMethod.Post, "http://httpbin.org/1h3a5jm1").Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.Post(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostStringDefaultToRequestBin() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Post, "http://httpbin.org/foo") .WithContent("raw string") .Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.PostRawStringDefault("raw string"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostStringJsonToRequestBin() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Post, "http://httpbin.org/foo") .WithContent("\"json string\"") .WithHeaders("Content-Type", "application/json; charset=utf-8") .Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.PostRawStringJson("json string"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostStringUrlToRequestBin() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Post, "http://httpbin.org/foo") .WithContent("url%26string") .WithHeaders("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") .Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.PostRawStringUrlEncoded("url&string"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostToRequestBinWithGenerics() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp.Expect(HttpMethod.Post, "http://httpbin.org/1h3a5jm1").Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.PostGeneric(5); mockHttp.VerifyNoOutstandingExpectation(); mockHttp.ResetExpectations(); mockHttp.Expect(HttpMethod.Post, "http://httpbin.org/1h3a5jm1").Respond(HttpStatusCode.OK); await fixture.PostGeneric("4"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostWithVoidReturnBufferedBodyExpectContentLengthHeader() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var postBody = new Dictionary { { "some", "body" }, { "once", "told me" } }; mockHttp .Expect(HttpMethod.Post, "http://httpbin.org/foo") .With(request => request.Content?.Headers.ContentLength > 0) .Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.PostVoidReturnBodyBuffered(postBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task PostWithNonVoidReturnBufferedBodyExpectContentLengthHeader() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var postBody = new Dictionary { { "some", "body" }, { "once", "told me" } }; const string expectedResponse = "some response"; mockHttp .Expect(HttpMethod.Post, "http://httpbin.org/foo") .With(request => request.Content?.Headers.ContentLength > 0) .Respond("text/plain", expectedResponse); var fixture = RestService.For("http://httpbin.org/", settings); var result = await fixture.PostNonVoidReturnBodyBuffered(postBody); mockHttp.VerifyNoOutstandingExpectation(); Assert.Equal(expectedResponse, result); } [Fact] public async Task UseMethodWithArgumentsParameter() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("http://httpbin.org/", settings); mockHttp .Expect(HttpMethod.Get, "http://httpbin.org/foo/something") .Respond(HttpStatusCode.OK); await fixture.SomeApiThatUsesVariableNameFromCodeGen("something"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task CanGetDataOutOfErrorResponses() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .When(HttpMethod.Get, "https://api.github.com/give-me-some-404-action") .Respond( HttpStatusCode.NotFound, "application/json", "{'message': 'Not Found', 'documentation_url': 'http://foo/bar'}" ); var fixture = RestService.For("https://api.github.com", settings); try { await fixture.NothingToSeeHere(); Assert.True(false); } catch (ApiException exception) { Assert.Equal(HttpStatusCode.NotFound, exception.StatusCode); var content = await exception.GetContentAsAsync>(); Assert.Equal("Not Found", content["message"]); Assert.NotNull(content["documentation_url"]); } } [Fact] public async Task CanSerializeBigData() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new SystemTextJsonContentSerializer() }; var bigObject = new BigObject { BigData = Enumerable.Range(0, 800000).Select(x => (byte)(x % 256)).ToArray() }; mockHttp .Expect(HttpMethod.Post, "http://httpbin.org/big") .With(m => { async Task T() { using var s = await m.Content.ReadAsStreamAsync(); var it = await System.Text.Json.JsonSerializer.DeserializeAsync( s, new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase } ); return it.BigData.SequenceEqual(bigObject.BigData); } return T().Result; }) .Respond(HttpStatusCode.OK); var fixture = RestService.For("http://httpbin.org/", settings); await fixture.PostBig(bigObject); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ErrorsFromApiReturnErrorContent() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Post, "https://api.github.com/users") .Respond( HttpStatusCode.BadRequest, "application/json", "{ 'errors': [ 'error1', 'message' ]}" ); var fixture = RestService.For("https://api.github.com", settings); var result = await Assert.ThrowsAsync( async () => await fixture.CreateUser(new User { Name = "foo" }) ); AssertStackTraceContains(nameof(IGitHubApi.CreateUser), result.StackTrace); var errors = await result.GetContentAsAsync(); Assert.Contains("error1", errors.Errors); Assert.Contains("message", errors.Errors); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ErrorsFromApiReturnErrorContentWhenApiResponse() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = new NewtonsoftJsonContentSerializer( new JsonSerializerSettings() { ContractResolver = new SnakeCasePropertyNamesContractResolver() } ) }; mockHttp .Expect(HttpMethod.Post, "https://api.github.com/users") .Respond( HttpStatusCode.BadRequest, "application/json", "{ 'errors': [ 'error1', 'message' ]}" ); var fixture = RestService.For("https://api.github.com", settings); using var response = await fixture.CreateUserWithMetadata(new User { Name = "foo" }); Assert.False(response.IsSuccessStatusCode); Assert.NotNull(response.Error); var errors = await response.Error.GetContentAsAsync(); Assert.Contains("error1", errors.Errors); Assert.Contains("message", errors.Errors); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public void NonRefitInterfacesThrowMeaningfulExceptions() { try { RestService.For("http://example.com"); } catch (InvalidOperationException exception) { Assert.StartsWith("INoRefitHereBuddy", exception.Message, StringComparison.Ordinal); Assert.Contains("Refit source generator", exception.Message, StringComparison.Ordinal); Assert.Contains("Native AOT", exception.Message, StringComparison.Ordinal); } } [Fact] public async Task NonRefitMethodsThrowMeaningfulExceptions() { try { var fixture = RestService.For("http://example.com"); await fixture.Get(); } catch (NotImplementedException exception) { Assert.Contains("no Refit HTTP method attribute", exception.Message); } } [Fact] public async Task GenericsWork() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "http://httpbin.org/get") .WithHeaders("X-Refit", "99") .WithQueryString("param", "foo") .Respond( "application/json", "{\"url\": \"http://httpbin.org/get?param=foo\", \"args\": {\"param\": \"foo\"}, \"headers\":{\"X-Refit\":\"99\"}}" ); var fixture = RestService.For>( "http://httpbin.org/get", settings ); var result = await fixture.Get("foo", 99); Assert.Equal("http://httpbin.org/get?param=foo", result.Url); Assert.Equal("foo", result.Args["param"]); Assert.Equal("99", result.Headers["X-Refit"]); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ValueTypesArentValidButTheyWorkAnyway() { var handler = new TestHttpMessageHandler("true"); var fixture = RestService.For( new HttpClient(handler) { BaseAddress = new Uri("http://nowhere.com") } ); var result = await fixture.PostAValue("Does this work?"); Assert.True(result); } [Fact] public async Task MissingBaseUrlThrowsArgumentException() { var client = new HttpClient(); // No BaseUrl specified var fixture = RestService.For(client); // We should get an InvalidOperationException if we call a method without a base address set var result = await Assert.ThrowsAsync( async () => await fixture.GetUser(null) ); AssertStackTraceContains(nameof(IGitHubApi.GetUser), result.StackTrace); } [Fact] public async Task SimpleDynamicQueryparametersTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithHeaders("X-Refit", "99") .Respond( "application/json", "{\"url\": \"https://httpbin.org/get?FirstName=John&LastName=Rambo\", \"args\": {\"FirstName\": \"John\", \"lName\": \"Rambo\"}}" ); var myParams = new MySimpleQueryParams { FirstName = "John", LastName = "Rambo" }; var fixture = RestService.For>( "https://httpbin.org/get", settings ); var resp = await fixture.Get(myParams, 99); Assert.Equal("John", resp.Args["FirstName"]); Assert.Equal("Rambo", resp.Args["lName"]); } [Fact] public async Task ComplexDynamicQueryparametersTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond( "application/json", "{\"url\": \"https://httpbin.org/get?hardcoded=true&FirstName=John&LastName=Rambo&Addr_Zip=9999&Addr_Street=HomeStreet 99&MetaData_Age=99&MetaData_Initials=JR&MetaData_Birthday=10%2F31%2F1918 4%3A21%3A16 PM&Other=12345&Other=10%2F31%2F2017 4%3A21%3A17 PM&Other=696e8653-6671-4484-a65f-9485af95fd3a\", \"args\": { \"Addr_Street\": \"HomeStreet 99\", \"Addr_Zip\": \"9999\", \"FirstName\": \"John\", \"LastName\": \"Rambo\", \"MetaData_Age\": \"99\", \"MetaData_Birthday\": \"10/31/1981 4:32:59 PM\", \"MetaData_Initials\": \"JR\", \"Other\": [\"12345\",\"10/31/2017 4:32:59 PM\",\"60282dd2-f79a-4400-be01-bcb0e86e7bc6\"], \"hardcoded\": \"true\"}}" ); var myParams = new MyComplexQueryParams { FirstName = "John", LastName = "Rambo" }; myParams.Address.Postcode = 9999; myParams.Address.Street = "HomeStreet 99"; myParams.MetaData.Add("Age", 99); myParams.MetaData.Add("Initials", "JR"); myParams.MetaData.Add("Birthday", new DateTime(1981, 10, 31, 16, 24, 59)); myParams.Other.Add(12345); myParams.Other.Add(new DateTime(2017, 10, 31, 16, 24, 59)); myParams.Other.Add(new Guid("60282dd2-f79a-4400-be01-bcb0e86e7bc6")); var fixture = RestService.For>( "https://httpbin.org", settings ); var resp = await fixture.GetQuery(myParams); Assert.Equal("John", resp.Args["FirstName"]); Assert.Equal("Rambo", resp.Args["LastName"]); Assert.Equal("9999", resp.Args["Addr_Zip"]); } [Fact] public async Task ComplexPostDynamicQueryparametersTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Post, "https://httpbin.org/post") .Respond( "application/json", "{\"url\": \"https://httpbin.org/post?hardcoded=true&FirstName=John&LastName=Rambo&Addr_Zip=9999&Addr_Street=HomeStreet 99&MetaData_Age=99&MetaData_Initials=JR&MetaData_Birthday=10%2F31%2F1918 4%3A21%3A16 PM&Other=12345&Other=10%2F31%2F2017 4%3A21%3A17 PM&Other=696e8653-6671-4484-a65f-9485af95fd3a\", \"args\": { \"Addr_Street\": \"HomeStreet 99\", \"Addr_Zip\": \"9999\", \"FirstName\": \"John\", \"LastName\": \"Rambo\", \"MetaData_Age\": \"99\", \"MetaData_Birthday\": \"10/31/1981 4:32:59 PM\", \"MetaData_Initials\": \"JR\", \"Other\": [\"12345\",\"10/31/2017 4:32:59 PM\",\"60282dd2-f79a-4400-be01-bcb0e86e7bc6\"], \"hardcoded\": \"true\"}}" ); var myParams = new MyComplexQueryParams { FirstName = "John", LastName = "Rambo" }; myParams.Address.Postcode = 9999; myParams.Address.Street = "HomeStreet 99"; myParams.MetaData.Add("Age", 99); myParams.MetaData.Add("Initials", "JR"); myParams.MetaData.Add("Birthday", new DateTime(1981, 10, 31, 16, 24, 59)); myParams.Other.Add(12345); myParams.Other.Add(new DateTime(2017, 10, 31, 16, 24, 59)); myParams.Other.Add(new Guid("60282dd2-f79a-4400-be01-bcb0e86e7bc6")); var fixture = RestService.For>( "https://httpbin.org", settings ); var resp = await fixture.PostQuery(myParams); Assert.Equal("John", resp.Args["FirstName"]); Assert.Equal("Rambo", resp.Args["LastName"]); Assert.Equal("9999", resp.Args["Addr_Zip"]); } [Fact] public async Task GenericMethodTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; const string response = "4"; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", response); var myParams = new Dictionary { ["FirstName"] = "John", ["LastName"] = "Rambo", ["Address"] = new { Zip = 9999, Street = "HomeStreet 99" } }; var fixture = RestService.For, int>>( "https://httpbin.org", settings ); // Use the generic to get it as an ApiResponse of string var resp = await fixture.GetQuery1>(myParams); Assert.Equal(response, resp.Content); mockHttp.VerifyNoOutstandingExpectation(); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", response); // Get as string var resp1 = await fixture.GetQuery1(myParams); Assert.Equal(response, resp1); mockHttp.VerifyNoOutstandingExpectation(); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", response); var resp2 = await fixture.GetQuery1(myParams); Assert.Equal(4, resp2); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task InheritedMethodTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("https://httpbin.org", settings); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", nameof(IAmInterfaceA.Ping)); var resp = await fixture.Ping(); Assert.Equal(nameof(IAmInterfaceA.Ping), resp); mockHttp.VerifyNoOutstandingExpectation(); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", nameof(IAmInterfaceB.Pong)); resp = await fixture.Pong(); Assert.Equal(nameof(IAmInterfaceB.Pong), resp); mockHttp.VerifyNoOutstandingExpectation(); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", nameof(IAmInterfaceC.Pang)); resp = await fixture.Pang(); Assert.Equal(nameof(IAmInterfaceC.Pang), resp); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task InheritedInterfaceWithOnlyBaseMethodsTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For("https://httpbin.org", settings); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", nameof(IAmInterfaceA.Ping)); var resp = await fixture.Ping(); Assert.Equal(nameof(IAmInterfaceA.Ping), resp); mockHttp.VerifyNoOutstandingExpectation(); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond("application/json", nameof(IAmInterfaceB.Pong)); resp = await fixture.Pong(); Assert.Equal(nameof(IAmInterfaceB.Pong), resp); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task InheritedInterfaceWithoutRefitInBaseMethodsTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For( "https://httpbin.org", settings ); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/doSomething") .WithQueryString("parameter", "4") .Respond("application/json", nameof(IImplementTheInterfaceAndUseRefit.DoSomething)); await fixture.DoSomething(4); mockHttp.VerifyNoOutstandingExpectation(); mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/DoSomethingElse") .Respond("application/json", nameof(IImplementTheInterfaceAndUseRefit.DoSomethingElse)); await fixture.DoSomethingElse(); mockHttp.VerifyNoOutstandingExpectation(); // base non refit method should throw NotImplementedException await Assert.ThrowsAsync( () => ((IAmInterfaceEWithNoRefit)fixture).DoSomethingElse() ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task InheritedInterfaceWithoutRefitMethodsOverrideBaseTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; var fixture = RestService.For( "https://httpbin.org", settings ); // inherited non refit method should throw NotImplementedException await Assert.ThrowsAsync( () => fixture.Test() ); mockHttp.VerifyNoOutstandingExpectation(); // base Refit method should respond mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .WithQueryString("result", "Test") .Respond("application/json", nameof(IAmInterfaceD.Test)); await ((IAmInterfaceD)fixture).Test(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task DictionaryDynamicQueryParametersTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond( "application/json", "{\"url\": \"https://httpbin.org/get?hardcoded=true&FirstName=John&LastName=Rambo&Address_Zip=9999&Address_Street=HomeStreet 99\", \"args\": {\"Address_Street\": \"HomeStreet 99\",\"Address_Zip\": \"9999\",\"FirstName\": \"John\",\"LastName\": \"Rambo\",\"hardcoded\": \"true\"}}" ); var myParams = new Dictionary { ["FirstName"] = "John", ["LastName"] = "Rambo", ["Address"] = new { Zip = 9999, Street = "HomeStreet 99" } }; var fixture = RestService.For, int>>( "https://httpbin.org", settings ); var resp = await fixture.GetQuery(myParams); Assert.Equal("John", resp.Args["FirstName"]); Assert.Equal("Rambo", resp.Args["LastName"]); Assert.Equal("9999", resp.Args["Address_Zip"]); } [Fact] public async Task ComplexDynamicQueryparametersTestWithIncludeParameterName() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "https://httpbin.org/get") .Respond( "application/json", "{\"url\": \"https://httpbin.org/get?search.FirstName=John&search.LastName=Rambo&search.Addr.Zip=9999&search.Addr.Street=HomeStreet 99\", \"args\": {\"search.Addr.Street\": \"HomeStreet 99\",\"search.Addr.Zip\": \"9999\",\"search.FirstName\": \"John\",\"search.LastName\": \"Rambo\"}}" ); var myParams = new MyComplexQueryParams { FirstName = "John", LastName = "Rambo" }; myParams.Address.Postcode = 9999; myParams.Address.Street = "HomeStreet 99"; var fixture = RestService.For>( "https://httpbin.org/get", settings ); var resp = await fixture.GetQueryWithIncludeParameterName(myParams); Assert.Equal("John", resp.Args["search.FirstName"]); Assert.Equal("Rambo", resp.Args["search.LastName"]); Assert.Equal("9999", resp.Args["search.Addr.Zip"]); } [Fact] public async Task ServiceOutsideNamespaceGetRequest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp .Expect(HttpMethod.Get, "http://foo/") // We can't add HttpContent to a GET request, // because HttpClient doesn't allow it and it will // blow up at runtime .With(r => r.Content == null) .Respond("application/json", "Ok"); var fixture = RestService.For("http://foo", settings); await fixture.GetRoot(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ServiceOutsideNamespacePostRequest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp }; mockHttp.Expect(HttpMethod.Post, "http://foo/").Respond("application/json", "Ok"); var fixture = RestService.For("http://foo", settings); await fixture.PostRoot(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task CanSerializeContentAsXml() { var mockHttp = new MockHttpMessageHandler(); var contentSerializer = new XmlContentSerializer(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, ContentSerializer = contentSerializer }; mockHttp .Expect(HttpMethod.Post, "/users") .WithHeaders("Content-Type:application/xml; charset=utf-8") .Respond( req => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent( "Created", Encoding.UTF8, "application/xml" ) } ); var fixture = RestService.For("https://api.github.com", settings); var result = await fixture.CreateUser(new User()).ConfigureAwait(false); Assert.Equal("Created", result.Name); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public void ShouldTrimTrailingForwardSlashFromBaseUrl() { var expectedBaseAddress = "http://example.com/api"; var inputBaseAddress = "http://example.com/api/"; var fixture = RestService.For(inputBaseAddress); Assert.Equal(fixture.Client.BaseAddress.AbsoluteUri, expectedBaseAddress); } [Fact] public void ShouldThrowArgumentExceptionIfHostUrlIsNull() { try { RestService.For(hostUrl: null); } catch (ArgumentException ex) { Assert.Equal("hostUrl", ex.ParamName); return; } Assert.Fail("Exception not thrown."); } [Fact] public void ShouldThrowArgumentExceptionIfHostUrlIsEmpty() { try { RestService.For(hostUrl: ""); } catch (ArgumentException ex) { Assert.Equal("hostUrl", ex.ParamName); return; } Assert.Fail("Exception not thrown."); } [Fact] public void ShouldThrowArgumentExceptionIfHostUrlIsWhitespace() { try { RestService.For(hostUrl: " "); } catch (ArgumentException ex) { Assert.Equal("hostUrl", ex.ParamName); return; } Assert.Fail("Exception not thrown."); } [Fact] public void NonGenericCreate() { var expectedBaseAddress = "http://example.com/api"; var inputBaseAddress = "http://example.com/api/"; var fixture = RestService.For(typeof(ITrimTrailingForwardSlashApi), inputBaseAddress) as ITrimTrailingForwardSlashApi; Assert.Equal(fixture.Client.BaseAddress.AbsoluteUri, expectedBaseAddress); } [Fact] public async Task EmptyQueryShouldBeEmpty() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo?") .WithExactQueryString("") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.EmptyQuery(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task WhiteSpaceQueryShouldBeEmpty() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo?") .WithExactQueryString("") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.WhiteSpaceQuery(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task EmptyQueryKeyShouldBeEmpty() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo?") .WithExactQueryString("") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.EmptyQueryKey(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task EmptyQueryValueShouldNotBeEmpty() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo?key=") .WithExactQueryString("key=") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.EmptyQueryValue(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task EmptyQueryKeyAndValueShouldBeEmpty() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo?") .WithExactQueryString("") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.EmptyQueryKeyAndValue(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task UnescapedQueryShouldBeEscaped() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .WithExactQueryString("key%2C=value%2C&key1%28=value1%28") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.UnescapedQuery(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task EscapedQueryShouldStillBeEscaped() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .WithExactQueryString("key%2C=value%2C&key1%28=value1%28") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.EscapedQuery(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ParameterMappedQueryShouldWork() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .WithExactQueryString("key1=value1") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.ParameterMappedQuery("key1", "value1"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ParameterMappedQueryShouldEscape() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .WithExactQueryString("key1%2C=value1%2C") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.ParameterMappedQuery("key1,", "value1,"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task NullableIntCollectionQuery() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .WithExactQueryString("values=3%2C4%2C") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.NullableIntCollectionQuery([3, 4, null]); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ShouldStripFragment() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.Fragment(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ShouldStripEmptyFragment() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.EmptyFragment(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ShouldStripManyFragments() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.ManyFragments(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ShouldStripParameterFragment() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.ParameterFragment("ignore"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ShouldStripFragmentAfterQuery() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .WithExactQueryString("key=value") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.FragmentAfterQuery(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task ShouldStripQueryAfterFragment() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.QueryAfterFragment(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task TaskShouldCancelWhenRequested() { var ctSource = new CancellationTokenSource(); var token = ctSource.Token; var fixture = RestService.For("https://github.com"); ctSource.Cancel(); var task = fixture.GetWithCancellation(token); await Assert.ThrowsAsync(async () => await task); } [Fact] public async Task TaskResultShouldCancelWhenRequested() { var ctSource = new CancellationTokenSource(); var token = ctSource.Token; var fixture = RestService.For("https://github.com"); ctSource.Cancel(); var task = fixture.GetWithCancellationAndReturn(token); await Assert.ThrowsAsync(async () => await task); } [Fact] public async Task NullableCancellationTokenShouldBeIgnored() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; mockHttp .Expect(HttpMethod.Get, "https://github.com/foo") .Respond(HttpStatusCode.OK); var fixture = RestService.For("https://github.com", settings); await fixture.GetWithNullableCancellation(null); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] public async Task TypeCollisionTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; const string Url = "https://httpbin.org/get"; mockHttp.Expect(HttpMethod.Get, Url).Respond("application/json", "{ }"); var fixtureA = RestService.For(Url, settings); var respA = await fixtureA.SomeARequest(); mockHttp.Expect(HttpMethod.Get, Url).Respond("application/json", "{ }"); var fixtureB = RestService.For(Url, settings); var respB = await fixtureB.SomeBRequest(); Assert.IsType(respA); Assert.IsType(respB); } internal static Stream GetTestFileStream(string relativeFilePath) { const char namespaceSeparator = '.'; // get calling assembly var assembly = Assembly.GetCallingAssembly(); // compute resource name suffix var relativeName = "." + relativeFilePath .Replace('\\', namespaceSeparator) .Replace('/', namespaceSeparator) .Replace(' ', '_'); // get resource stream var fullName = assembly .GetManifestResourceNames() .FirstOrDefault(name => name.EndsWith(relativeName, StringComparison.InvariantCulture)); if (fullName == null) { throw new Exception( $"Unable to find resource for path \"{relativeFilePath}\". Resource with name ending on \"{relativeName}\" was not found in assembly." ); } var stream = assembly.GetManifestResourceStream(fullName); if (stream == null) { throw new Exception( $"Unable to find resource for path \"{relativeFilePath}\". Resource named \"{fullName}\" was not found in assembly." ); } return stream; } [Fact] public async Task SameTypeNameInMultipleNamespacesTest() { var mockHttp = new MockHttpMessageHandler(); var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, }; const string Url = "https://httpbin.org/get"; mockHttp.Expect(HttpMethod.Get, Url + "/").Respond("application/json", "{ }"); var fixtureA = RestService.For(Url, settings); var respA = await fixtureA.SomeRequest(); mockHttp.Expect(HttpMethod.Get, Url + "/").Respond("application/json", "{ }"); var fixtureB = RestService.For(Url, settings); var respB = await fixtureB.SomeRequest(); mockHttp.Expect(HttpMethod.Get, Url + "/").Respond("application/json", "{ }"); var fixtureC = RestService.For(Url, settings); var respC = await fixtureC.SomeRequest(); Assert.IsType(respA); Assert.IsType(respB); Assert.IsType(respC); } private static void AssertStackTraceContains(string expectedSubstring, string actualString) { Assert.Contains(expectedSubstring, actualString); } } ================================================ FILE: Refit.Tests/RestServiceExceptions.cs ================================================ using Xunit; // ReSharper disable InconsistentNaming namespace Refit.Tests; public interface IManyCancellationTokens { [Get("/")] Task GetValue(CancellationToken token0, CancellationToken token1); } public interface IManyHeaderCollections { [Get("/")] Task GetValue([HeaderCollection] IDictionary collection0, [HeaderCollection] IDictionary collection1); } public interface IHeaderCollectionWrongType { [Get("/")] Task GetValue([HeaderCollection] IDictionary collection); } public interface IDoesNotStartSlash { [Get("users")] Task GetValue(); } public interface IUrlContainsCRLF { [Get("/\r")] Task GetValue(); } public interface IRoundTripNotString { [Get("/{**value}")] Task GetValue(int value); } public interface IRoundTrippingLeadingWhitespace { [Get("/{ **path}")] Task GetValue(string path); } public interface IRoundTrippingTrailingWhitespace { [Get("/{** path}")] Task GetValue(string path); } public interface IInvalidParamSubstitution { [Get("/{/path}")] Task GetValue(string path); } public interface IInvalidFragmentParamSubstitution { [Get("/{#path}")] Task GetValue(string path); } public interface IUrlNoMatchingParameters { [Get("/{value}")] Task GetValue(); } public interface IMultipartAndBody { [Get("/}")] [Multipart] Task GetValue([Body] string body); } public interface IManyBody { [Get("/")] Task GetValue([Body] string body0, [Body] string body1); } public class UserBody { public string Value { get; set; } } public interface IManyComplexTypes { [Post("/")] Task PostValue(UserBody body0, UserBody body1); } public interface IManyAuthorize { [Get("/")] Task GetValue([Authorize("Bearer")] string token0, [Authorize("Bearer")] string token1); } public interface IInvalidReturnType { [Get("/")] string GetValue(); } public class RestServiceExceptionTests { [Fact] public void ManyCancellationTokensShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("only contain a single CancellationToken", exception); } [Fact] public void ManyHeaderCollectionShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("Only one parameter can be a HeaderCollection parameter", exception); } [Fact] public void InvalidHeaderCollectionTypeShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("HeaderCollection parameter of type", exception); } [Fact] public void UrlDoesntStartWithSlashShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("must start with '/' and be of the form", exception); } [Fact] public void UrlContainsCRLFShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("must not contain CR or LF characters", exception); } [Fact] public void RoundTripParameterNotStringShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("has round-tripping parameter", exception); } [Fact] public void RoundTripWithLeadingWhitespaceShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("has parameter **path, but no method parameter matches", exception); } [Fact] public void RoundTripWithTrailingWhitespaceShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("has parameter ** path, but no method parameter matches", exception); } [Fact] public async Task InvalidParamSubstitutionShouldThrow() { var service = RestService.For("https://api.github.com"); Assert.NotNull(service); await Assert.ThrowsAsync(() => service.GetValue("throws")); } [Fact] public void InvalidFragmentParamSubstitutionShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("but no method parameter matches", exception); } [Fact] public void UrlNoMatchingParameterShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("but no method parameter matches", exception); } [Fact] public void MultipartAndBodyShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("Multipart requests may not contain a Body parameter", exception); } [Fact] public void ManyBodyShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("Only one parameter can be a Body parameter", exception); } [Fact] public void ManyComplexTypesShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("Multiple complex types found. Specify one parameter as the body using BodyAttribute", exception); } [Fact] public void ManyAuthorizeAttributesShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("Only one parameter can be an Authorize parameter", exception); } [Fact] public void InvalidReturnTypeShouldThrow() { var exception = Assert.Throws(() => RestService.For("https://api.github.com")); AssertExceptionContains("is invalid. All REST Methods must return either Task or ValueTask or IObservable", exception); } private static void AssertExceptionContains(string expectedSubstring, Exception exception) { Assert.Contains(expectedSubstring, exception.Message!, StringComparison.Ordinal); } } ================================================ FILE: Refit.Tests/SerializedContentTests.cs ================================================ using System; using System.IO; using System.Net; using System.Net.Http; using System.Reflection; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using SystemTextJsonSerializer = System.Text.Json.JsonSerializer; using Xunit; namespace Refit.Tests; public partial class SerializedContentTests { const string BaseAddress = "https://api/"; [Theory] [InlineData(typeof(NewtonsoftJsonContentSerializer))] [InlineData(typeof(SystemTextJsonContentSerializer))] [InlineData(typeof(XmlContentSerializer))] public async Task WhenARequestRequiresABodyThenItDoesNotDeadlock(Type contentSerializerType) { if ( Activator.CreateInstance(contentSerializerType) is not IHttpContentSerializer serializer ) { throw new ArgumentException( $"{contentSerializerType.FullName} does not implement {nameof(IHttpContentSerializer)}" ); } var handler = new MockPushStreamContentHttpMessageHandler { Asserts = async content => new StringContent(await content.ReadAsStringAsync().ConfigureAwait(false)) }; var settings = new RefitSettings(serializer) { HttpMessageHandlerFactory = () => handler }; var fixture = RestService.For(BaseAddress, settings); var fixtureTask = await RunTaskWithATimeLimit(fixture.CreateUser(new User())) .ConfigureAwait(false); Assert.True(fixtureTask.IsCompleted); Assert.Equal(TaskStatus.RanToCompletion, fixtureTask.Status); } [Theory] [InlineData(typeof(NewtonsoftJsonContentSerializer))] [InlineData(typeof(SystemTextJsonContentSerializer))] [InlineData(typeof(XmlContentSerializer))] public async Task WhenARequestRequiresABodyThenItIsSerialized(Type contentSerializerType) { if ( Activator.CreateInstance(contentSerializerType) is not IHttpContentSerializer serializer ) { throw new ArgumentException( $"{contentSerializerType.FullName} does not implement {nameof(IHttpContentSerializer)}" ); } var model = new User { Name = "Wile E. Coyote", CreatedAt = new DateTime(1949, 9, 16).ToString(), Company = "ACME", }; var handler = new MockPushStreamContentHttpMessageHandler { Asserts = async content => { var stringContent = new StringContent( await content.ReadAsStringAsync().ConfigureAwait(false) ); var user = await serializer .FromHttpContentAsync(content) .ConfigureAwait(false); Assert.NotSame(model, user); Assert.Equal(model.Name, user.Name); Assert.Equal(model.CreatedAt, user.CreatedAt); Assert.Equal(model.Company, user.Company); // return some content so that the serializer doesn't complain return stringContent; } }; var settings = new RefitSettings(serializer) { HttpMessageHandlerFactory = () => handler }; var fixture = RestService.For(BaseAddress, settings); var fixtureTask = await RunTaskWithATimeLimit(fixture.CreateUser(model)) .ConfigureAwait(false); Assert.True(fixtureTask.IsCompleted); } [Fact] public void VerityDefaultSerializer() { var settings = new RefitSettings(); Assert.NotNull(settings.ContentSerializer); Assert.IsType(settings.ContentSerializer); settings = new RefitSettings(new NewtonsoftJsonContentSerializer()); Assert.NotNull(settings.ContentSerializer); Assert.IsType(settings.ContentSerializer); } /// /// Runs the task to completion or until the timeout occurs /// static async Task> RunTaskWithATimeLimit(Task fixtureTask) { var circuitBreakerTask = Task.Delay(TimeSpan.FromSeconds(30)); await Task.WhenAny(fixtureTask, circuitBreakerTask); return fixtureTask; } class MockPushStreamContentHttpMessageHandler : HttpMessageHandler { public Func> Asserts { get; set; } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { var content = request.Content as PushStreamContent; Assert.IsType(content); Assert.NotNull(Asserts); var responseContent = await Asserts(content).ConfigureAwait(false); return new HttpResponseMessage(HttpStatusCode.OK) { Content = responseContent }; } } [Fact] public async Task StreamDeserialization_UsingSystemTextJsonContentSerializer() { var model = new TestAliasObject { ShortNameForAlias = nameof(StreamDeserialization_UsingSystemTextJsonContentSerializer), ShortNameForJsonProperty = nameof(TestAliasObject) }; var serializer = new SystemTextJsonContentSerializer(); var json = serializer.ToHttpContent(model); var result = await serializer.FromHttpContentAsync(json); Assert.NotNull(result); Assert.Equal(model.ShortNameForAlias, result.ShortNameForAlias); Assert.Equal(model.ShortNameForJsonProperty, result.ShortNameForJsonProperty); } [Fact] public void StreamDeserialization_UsingSystemTextJsonContentSerializer_SetsCorrectHeaders() { var model = new TestAliasObject { ShortNameForAlias = nameof(StreamDeserialization_UsingSystemTextJsonContentSerializer), ShortNameForJsonProperty = nameof(TestAliasObject) }; var serializer = new SystemTextJsonContentSerializer(); var json = serializer.ToHttpContent(model); Assert.NotNull(json.Headers.ContentType); Assert.Equal("utf-8", json.Headers.ContentType.CharSet); Assert.Equal("application/json", json.Headers.ContentType.MediaType); } [Fact] public async Task StreamDeserialization_UsingNewtonsoftJsonContentSerializer_DoesNotUseSynchronousReads() { var serializer = new NewtonsoftJsonContentSerializer(); var content = new AsyncOnlyJsonContent("{\"name\":\"Road Runner\"}"); var result = await serializer.FromHttpContentAsync(content); Assert.NotNull(result); Assert.Equal("Road Runner", result.Name); } [Fact] public async Task StreamDeserialization_UsingNewtonsoftJsonContentSerializer_ReturnsDefaultForNullContent() { var serializer = new NewtonsoftJsonContentSerializer(); var result = await serializer.FromHttpContentAsync(null!); Assert.Null(result); } [Fact] public void NewtonsoftJsonContentSerializer_GetFieldNameForProperty_ReturnsJsonPropertyName() { var serializer = new NewtonsoftJsonContentSerializer(); var property = typeof(NewtonsoftFieldNameModel).GetProperty( nameof(NewtonsoftFieldNameModel.Name) ); var fieldName = serializer.GetFieldNameForProperty(property!); Assert.Equal("json_name", fieldName); } [Fact] public void NewtonsoftJsonContentSerializer_GetFieldNameForProperty_ReturnsNullWithoutJsonPropertyAttribute() { var serializer = new NewtonsoftJsonContentSerializer(); var property = typeof(NewtonsoftFieldNameModel).GetProperty( nameof(NewtonsoftFieldNameModel.Unaliased) ); var fieldName = serializer.GetFieldNameForProperty(property!); Assert.Null(fieldName); } [Fact] public void NewtonsoftJsonContentSerializer_GetFieldNameForProperty_ThrowsForNullProperty() { var serializer = new NewtonsoftJsonContentSerializer(); var exception = Assert.Throws(() => serializer.GetFieldNameForProperty(null!)); Assert.Equal("propertyInfo", exception.ParamName); } [Fact] public void SystemTextJsonContentSerializer_GetFieldNameForProperty_ReturnsJsonPropertyName() { var serializer = new SystemTextJsonContentSerializer(); var property = typeof(SystemTextFieldNameModel).GetProperty( nameof(SystemTextFieldNameModel.Name) ); var fieldName = serializer.GetFieldNameForProperty(property!); Assert.Equal("json_name", fieldName); } [Fact] public void SystemTextJsonContentSerializer_GetFieldNameForProperty_ReturnsNullWithoutJsonPropertyNameAttribute() { var serializer = new SystemTextJsonContentSerializer(); var property = typeof(SystemTextFieldNameModel).GetProperty( nameof(SystemTextFieldNameModel.Unaliased) ); var fieldName = serializer.GetFieldNameForProperty(property!); Assert.Null(fieldName); } [Fact] public void SystemTextJsonContentSerializer_GetFieldNameForProperty_ThrowsForNullProperty() { var serializer = new SystemTextJsonContentSerializer(); var exception = Assert.Throws(() => serializer.GetFieldNameForProperty(null!)); Assert.Equal("propertyInfo", exception.ParamName); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_InferBooleanObjectValues() { var result = SystemTextJsonSerializer.Deserialize( """{"value":true}""", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.True(Assert.IsType(result!.Value)); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_InferIntegralObjectValues() { var result = SystemTextJsonSerializer.Deserialize( """{"value":42}""", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(42L, Assert.IsType(result!.Value)); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_InferFloatingPointObjectValues() { var result = SystemTextJsonSerializer.Deserialize( """{"value":42.5}""", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(42.5, Assert.IsType(result!.Value)); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_InferDateObjectValues() { var result = SystemTextJsonSerializer.Deserialize( """{"value":"2024-01-02T03:04:05Z"}""", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal( new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc), Assert.IsType(result!.Value) ); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_InferStringObjectValues() { var result = SystemTextJsonSerializer.Deserialize( """{"value":"Road Runner"}""", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal("Road Runner", Assert.IsType(result!.Value)); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeObjectValuesAsJsonElements() { var result = SystemTextJsonSerializer.Deserialize( """{"value":{"company":"ACME"}}""", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(JsonValueKind.Object, Assert.IsType(result!.Value).ValueKind); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_SerializeObjectEnumValuesAsCamelCase() { var json = SystemTextJsonSerializer.Serialize( new ObjectValueContainer { Value = CamelCaseEnum.ValueOne }, SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal("""{"value":"valueOne"}""", json); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_SerializeNullObjectValuesAsJsonNull() { var json = SystemTextJsonSerializer.Serialize( new ObjectValueContainer { Value = null }, SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal("""{"value":null}""", json); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_UseResolverWhenSerializingObjectValues() { var resolver = new TrackingTypeInfoResolver(ObjectValueContainerJsonSerializerContext.Default); var options = new JsonSerializerOptions( SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ) { TypeInfoResolver = resolver }; var json = SystemTextJsonSerializer.Serialize( new ObjectValueContainer { Value = "Road Runner" }, options ); Assert.Equal("""{"value":"Road Runner"}""", json); Assert.Contains(typeof(string), resolver.RequestedTypes); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeCamelCaseEnumValues() { var result = SystemTextJsonSerializer.Deserialize( "\"valueOne\"", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(CamelCaseEnum.ValueOne, result); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeCaseInsensitiveEnumValues() { var result = SystemTextJsonSerializer.Deserialize( "\"VALUEONE\"", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(CamelCaseEnum.ValueOne, result); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeLowercaseEnumValues() { var result = SystemTextJsonSerializer.Deserialize( "\"alreadyLowercase\"", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(CamelCaseEnum.alreadyLowercase, result); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeNumericEnumValues() { var result = SystemTextJsonSerializer.Deserialize( "2", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal(CamelCaseEnum.alreadyLowercase, result); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeNullNullableEnumValues() { var result = SystemTextJsonSerializer.Deserialize( "null", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Null(result); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_DeserializeEmptyNullableEnumValues() { var result = SystemTextJsonSerializer.Deserialize( "\"\"", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Null(result); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_ThrowForNullNonNullableEnumValues() { Assert.Throws( () => SystemTextJsonSerializer.Deserialize( "null", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ) ); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_ThrowForEmptyNonNullableEnumValues() { Assert.Throws( () => SystemTextJsonSerializer.Deserialize( "\"\"", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ) ); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_ThrowForInvalidEnumValues() { Assert.Throws( () => SystemTextJsonSerializer.Deserialize( "\"notAValue\"", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ) ); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_ThrowForUnexpectedTokensWhenParsingEnums() { Assert.Throws( () => SystemTextJsonSerializer.Deserialize( "true", SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ) ); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_SerializeUndefinedEnumValuesAsNumbers() { var json = SystemTextJsonSerializer.Serialize( (CamelCaseEnum)999, SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal("999", json); } [Fact] public void SystemTextJsonContentSerializer_DefaultOptions_SerializeLowercaseEnumNamesUnchanged() { var json = SystemTextJsonSerializer.Serialize( CamelCaseEnum.alreadyLowercase, SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions() ); Assert.Equal("\"alreadyLowercase\"", json); } [Fact] public async Task SystemTextJsonContentSerializer_UsesSourceGeneratedMetadataWhenProvided() { var resolver = new TrackingTypeInfoResolver(SerializedContentJsonSerializerContext.Default); var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { TypeInfoResolver = resolver }; var serializer = new SystemTextJsonContentSerializer(options); var model = new User { Name = "Road Runner", Company = "ACME", CreatedAt = "1949-09-17" }; var content = serializer.ToHttpContent(model); var roundTrip = await serializer.FromHttpContentAsync(content); Assert.NotNull(roundTrip); Assert.Equal(model.Name, roundTrip.Name); Assert.Equal(model.Company, roundTrip.Company); Assert.Equal(model.CreatedAt, roundTrip.CreatedAt); Assert.Contains(typeof(User), resolver.RequestedTypes); } [Fact] public async Task RestService_CanUseSourceGeneratedSystemTextJsonMetadata() { var resolver = new TrackingTypeInfoResolver(SerializedContentJsonSerializerContext.Default); var settings = new RefitSettings( new SystemTextJsonContentSerializer( new JsonSerializerOptions(JsonSerializerDefaults.Web) { TypeInfoResolver = resolver } ) ) { HttpMessageHandlerFactory = () => new StubHttpMessageHandler( _ => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent( "{\"name\":\"Road Runner\",\"company\":\"ACME\",\"createdAt\":\"1949-09-17\"}", Encoding.UTF8, "application/json" ) } ) }; var api = RestService.For(BaseAddress, settings); var user = await api.GetUser("roadrunner"); Assert.NotNull(user); Assert.Equal("Road Runner", user.Name); Assert.Equal("ACME", user.Company); Assert.Equal("1949-09-17", user.CreatedAt); Assert.Contains(typeof(User), resolver.RequestedTypes); } [JsonSerializable(typeof(User))] internal sealed partial class SerializedContentJsonSerializerContext : JsonSerializerContext { } [JsonSerializable(typeof(ObjectValueContainer))] [JsonSerializable(typeof(string))] internal sealed partial class ObjectValueContainerJsonSerializerContext : JsonSerializerContext { } sealed class TrackingTypeInfoResolver(IJsonTypeInfoResolver innerResolver) : IJsonTypeInfoResolver { public HashSet RequestedTypes { get; } = []; public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) { RequestedTypes.Add(type); return innerResolver.GetTypeInfo(type, options); } } sealed class StubHttpMessageHandler(Func responder) : HttpMessageHandler { protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) => Task.FromResult(responder(request)); } sealed class NewtonsoftFieldNameModel { [JsonProperty(PropertyName = "json_name")] public string Name { get; set; } public string Unaliased { get; set; } } sealed class SystemTextFieldNameModel { [JsonPropertyName("json_name")] public string Name { get; set; } public string Unaliased { get; set; } } public sealed class ObjectValueContainer { public object Value { get; set; } } enum CamelCaseEnum { ValueOne = 1, alreadyLowercase = 2 } sealed class AsyncOnlyJsonContent(string json) : HttpContent { readonly byte[] _bytes = Encoding.UTF8.GetBytes(json); protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => stream.WriteAsync(_bytes, 0, _bytes.Length); protected override bool TryComputeLength(out long length) { length = _bytes.Length; return true; } protected override Task CreateContentReadStreamAsync() => Task.FromResult(new AsyncOnlyReadStream(_bytes)); } sealed class AsyncOnlyReadStream(byte[] bytes) : Stream { readonly MemoryStream _inner = new(bytes, writable: false); public override bool CanRead => true; public override bool CanSeek => _inner.CanSeek; public override bool CanWrite => false; public override long Length => _inner.Length; public override long Position { get => _inner.Position; set => _inner.Position = value; } public override void Flush() => _inner.Flush(); public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("Synchronous reads are intentionally not supported."); public override ValueTask ReadAsync( Memory buffer, CancellationToken cancellationToken = default ) => _inner.ReadAsync(buffer, cancellationToken); public override Task ReadAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) => _inner.ReadAsync(buffer, offset, count, cancellationToken); public override long Seek(long offset, SeekOrigin origin) => _inner.Seek(offset, origin); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } } ================================================ FILE: Refit.Tests/TypeCollisionApiA.cs ================================================ using System.Threading.Tasks; using CollisionA; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests; public interface ITypeCollisionApiA { [Get("")] Task SomeARequest(); } public static class TypeCollisionApiA { public static ITypeCollisionApiA Create() { return RestService.For("http://somewhere.com"); } } ================================================ FILE: Refit.Tests/TypeCollisionApiB.cs ================================================ using System.Threading.Tasks; using CollisionB; using Refit; // InterfaceStubGenerator looks for this namespace Refit.Tests; public interface ITypeCollisionApiB { [Get("")] Task SomeBRequest(); } public static class TypeCollisionApiB { public static ITypeCollisionApiB Create() { return RestService.For("http://somewhere.com"); } } ================================================ FILE: Refit.Tests/UniqueNameTests.cs ================================================ using System.Collections.Generic; using Xunit; namespace Refit.Tests { namespace Http { class Client { public class Request { } public class Response { } } } namespace Tcp { class Client { } } public class UniqueNameTests { [Fact] public void SystemTypeAndLanguageTypeHaveSameNames() { var name1 = UniqueName.ForType(); var name2 = UniqueName.ForType(); Assert.Equal(name1, name2); } [Fact] public void GenericClassWithDifferentTypesHaveUniqueNames() { var name1 = UniqueName.ForType>(); var name2 = UniqueName.ForType>(); Assert.NotEqual(name1, name2); } [Fact] public void SameClassNameInDifferentNamespacesHaveUniqueNames() { var name1 = UniqueName.ForType(); var name2 = UniqueName.ForType(); Assert.NotEqual(name1, name2); } [Fact] public void ClassesWithNestedClassesHaveUniqueNames() { var name1 = UniqueName.ForType(); var name2 = UniqueName.ForType(); Assert.NotEqual(name1, name2); } [Fact] public void NestedClassesHaveUniqueNames() { var name1 = UniqueName.ForType(); var name2 = UniqueName.ForType(); Assert.NotEqual(name1, name2); } } } ================================================ FILE: Refit.Tests/UseCultureAttribute.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Text; using System.Threading; using Xunit.Sdk; namespace Refit.Tests; /// /// Apply this attribute to your test method to replace the /// and /// with another culture. /// [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true )] public class UseCultureAttribute : BeforeAfterTestAttribute { readonly Lazy culture; readonly Lazy uiCulture; CultureInfo originalCulture; CultureInfo originalUICulture; /// /// Replaces the culture and UI culture of the current thread with /// /// /// The name of the culture. /// /// /// This constructor overload uses for both /// and . /// /// public UseCultureAttribute(string culture) : this(culture, culture) { } /// /// Replaces the culture and UI culture of the current thread with /// and /// /// The name of the culture. /// The name of the UI culture. public UseCultureAttribute(string culture, string uiCulture) { this.culture = new Lazy(() => new CultureInfo(culture)); this.uiCulture = new Lazy(() => new CultureInfo(uiCulture)); } /// /// Gets the culture. /// public CultureInfo Culture { get { return culture.Value; } } /// /// Gets the UI culture. /// public CultureInfo UICulture { get { return uiCulture.Value; } } /// /// Stores the current /// and /// and replaces them with the new cultures defined in the constructor. /// /// The method under test public override void Before(MethodInfo methodUnderTest) { originalCulture = CultureInfo.CurrentCulture; originalUICulture = CultureInfo.CurrentUICulture; CultureInfo.CurrentCulture = Culture; CultureInfo.CurrentUICulture = UICulture; } /// /// Restores the original and /// to /// /// The method under test public override void After(MethodInfo methodUnderTest) { CultureInfo.CurrentCulture = originalCulture; CultureInfo.CurrentUICulture = originalUICulture; } } ================================================ FILE: Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1+Test.cs ================================================ using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing; namespace Refit.Tests; public static partial class CSharpIncrementalSourceGeneratorVerifier where TIncrementalGenerator : IIncrementalGenerator, new() { public class Test : CSharpSourceGeneratorTest { public Test() { SolutionTransforms.Add( (solution, projectId) => { var compilationOptions = solution.GetProject(projectId).CompilationOptions; compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( compilationOptions.SpecificDiagnosticOptions.SetItems( CSharpVerifierHelper.NullableWarnings ) ); solution = solution.WithProjectCompilationOptions( projectId, compilationOptions ); return solution; } ); } /// /// Gets the source generators. /// /// protected override IEnumerable GetSourceGenerators() { yield return new TIncrementalGenerator().AsSourceGenerator().GetGeneratorType(); } /// /// Creates the parse options. /// /// protected override ParseOptions CreateParseOptions() { var parseOptions = (CSharpParseOptions)base.CreateParseOptions(); return parseOptions.WithLanguageVersion(LanguageVersion.Preview); } } } ================================================ FILE: Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1.cs ================================================ using Microsoft.CodeAnalysis; namespace Refit.Tests; public static partial class CSharpIncrementalSourceGeneratorVerifier where TIncrementalGenerator : IIncrementalGenerator, new(); ================================================ FILE: Refit.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs ================================================ using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing; namespace Refit.Tests; public static partial class CSharpSourceGeneratorVerifier where TSourceGenerator : ISourceGenerator, new() { public class Test : CSharpSourceGeneratorTest { public Test() { SolutionTransforms.Add( (solution, projectId) => { var compilationOptions = solution.GetProject(projectId).CompilationOptions; compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( compilationOptions.SpecificDiagnosticOptions.SetItems( CSharpVerifierHelper.NullableWarnings ) ); solution = solution.WithProjectCompilationOptions( projectId, compilationOptions ); return solution; } ); } } } ================================================ FILE: Refit.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs ================================================ using Microsoft.CodeAnalysis; namespace Refit.Tests; public static partial class CSharpSourceGeneratorVerifier where TSourceGenerator : ISourceGenerator, new(); ================================================ FILE: Refit.Tests/Verifiers/CSharpVerifierHelper.cs ================================================ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace Refit.Tests; static class CSharpVerifierHelper { /// /// By default, the compiler reports diagnostics for nullable reference types at /// , and the analyzer test framework defaults to only validating /// diagnostics at . This map contains all compiler diagnostic IDs /// related to nullability mapped to , which is then used to enable all /// of these warnings for default validation during analyzer and code fix tests. /// internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler(); static ImmutableDictionary GetNullableWarningsFromCompiler() { string[] args = { "/warnaserror:nullable" }; var commandLineArguments = CSharpCommandLineParser.Default.Parse( args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory ); return commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; } } ================================================ FILE: Refit.Tests/XmlContentSerializerTests.cs ================================================ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; using Xunit; namespace Refit.Tests; public class XmlContentSerializerTests { public class Dto { public DateTime CreatedOn { get; set; } public string Identifier { get; set; } [XmlElement(Namespace = "https://google.com")] public string Name { get; set; } } [Fact] public void MediaTypeShouldBeApplicationXmlAsync() { var dto = BuildDto(); var sut = new XmlContentSerializer(); var content = sut.ToHttpContent(dto); Assert.Equal("application/xml", content.Headers.ContentType.MediaType); } [Fact] public async Task ShouldSerializeToXml() { var dto = BuildDto(); var sut = new XmlContentSerializer(); var content = sut.ToHttpContent(dto); var document = new XmlDocument(); document.LoadXml(await content.ReadAsStringAsync()); var root = document[nameof(Dto)] ?? throw new NullReferenceException("Root element was not found"); Assert.Equal( dto.CreatedOn, XmlConvert.ToDateTime( root[nameof(Dto.CreatedOn)].InnerText, XmlDateTimeSerializationMode.Utc ) ); Assert.Equal(dto.Identifier, root[nameof(Dto.Identifier)].InnerText); Assert.Equal(dto.Name, root[nameof(Dto.Name)].InnerText); } [Fact] public async Task ShouldSerializeToXmlUsingAttributeOverrides() { const string overridenRootElementName = "dto-ex"; var dto = BuildDto(); var serializerSettings = new XmlContentSerializerSettings(); var attributes = new XmlAttributes { XmlRoot = new XmlRootAttribute(overridenRootElementName) }; serializerSettings.XmlAttributeOverrides.Add(dto.GetType(), attributes); var sut = new XmlContentSerializer(serializerSettings); var content = sut.ToHttpContent(dto); var document = new XmlDocument(); document.LoadXml(await content.ReadAsStringAsync()); Assert.Equal(overridenRootElementName, document.DocumentElement?.Name); } [Fact] public async Task ShouldSerializeToXmlUsingNamespaceOverrides() { const string prefix = "google"; var dto = BuildDto(); var serializerSettings = new XmlContentSerializerSettings { XmlNamespaces = new XmlSerializerNamespaces() }; serializerSettings.XmlNamespaces.Add(prefix, "https://google.com"); var sut = new XmlContentSerializer(serializerSettings); var content = sut.ToHttpContent(dto); var document = new XmlDocument(); document.LoadXml(await content.ReadAsStringAsync()); Assert.Equal(prefix, document["Dto"]?["Name", "https://google.com"]?.Prefix); } [Fact] public async Task ShouldDeserializeFromXmlAsync() { var serializerSettings = new XmlContentSerializerSettings { XmlNamespaces = new XmlSerializerNamespaces() }; var sut = new XmlContentSerializer(serializerSettings); var dto = await sut.FromHttpContentAsync( new StringContent("123") ); Assert.Equal("123", dto.Identifier); } [Fact] public async Task XmlEncodingShouldMatchWriterSettingAsync() { var encoding = Encoding.UTF32; var serializerSettings = new XmlContentSerializerSettings { XmlReaderWriterSettings = new XmlReaderWriterSettings() { WriterSettings = new XmlWriterSettings() { Encoding = encoding } } }; var sut = new XmlContentSerializer(serializerSettings); var dto = BuildDto(); var content = sut.ToHttpContent(dto); var xml = XDocument.Parse(await content.ReadAsStringAsync()); var documentEncoding = xml.Declaration.Encoding; Assert.Equal(encoding.WebName, documentEncoding); } static Dto BuildDto() { var dto = new Dto { CreatedOn = DateTime.UtcNow, Identifier = Guid.NewGuid().ToString(), Name = "Test Dto Object" }; return dto; } } ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#Generated.g.verified.cs ================================================ //HintName: Generated.g.cs #pragma warning disable namespace Refit.Implementation { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] internal static partial class Generated { #if NET5_0_OR_GREATER [System.Runtime.CompilerServices.ModuleInitializer] [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(global::Refit.Implementation.Generated))] public static void Initialize() { } #endif } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs ================================================ //HintName: IGitHubApi.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitTestsIGitHubApi : global::Refit.Tests.IGitHubApi { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitTestsIGitHubApi(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task GetUser(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUser", ______typeParameters ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters0 = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable GetUserObservable(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserObservable", ______typeParameters0 ); return (global::System.IObservable)______func(this.Client, ______arguments); } private static readonly global::System.Type[] ______typeParameters1 = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable GetUserCamelCase(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserCamelCase", ______typeParameters1 ); return (global::System.IObservable)______func(this.Client, ______arguments); } private static readonly global::System.Type[] ______typeParameters2 = new global::System.Type[] {typeof(string), typeof(global::System.Threading.CancellationToken) }; /// public async global::System.Threading.Tasks.Task> GetOrgMembers(string @orgName, global::System.Threading.CancellationToken @cancellationToken) { var ______arguments = new object[] { @orgName, @cancellationToken }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetOrgMembers", ______typeParameters2 ); return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters3 = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task FindUsers(string @q) { var ______arguments = new object[] { @q }; var ______func = requestBuilder.BuildRestResultFuncForMethod("FindUsers", ______typeParameters3 ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public async global::System.Threading.Tasks.Task GetIndex() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndex", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public global::System.IObservable GetIndexObservable() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndexObservable", global::System.Array.Empty() ); return (global::System.IObservable)______func(this.Client, ______arguments); } /// public async global::System.Threading.Tasks.Task NothingToSeeHere() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("NothingToSeeHere", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public async global::System.Threading.Tasks.Task> NothingToSeeHereWithMetadata() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("NothingToSeeHereWithMetadata", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters4 = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task> GetUserWithMetadata(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserWithMetadata", ______typeParameters4 ); return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters5 = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable> GetUserObservableWithMetadata(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserObservableWithMetadata", ______typeParameters5 ); return (global::System.IObservable>)______func(this.Client, ______arguments); } private static readonly global::System.Type[] ______typeParameters6 = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable> GetUserIApiResponseObservableWithMetadata(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserIApiResponseObservableWithMetadata", ______typeParameters6 ); return (global::System.IObservable>)______func(this.Client, ______arguments); } private static readonly global::System.Type[] ______typeParameters7 = new global::System.Type[] {typeof(global::Refit.Tests.User) }; /// public async global::System.Threading.Tasks.Task CreateUser(global::Refit.Tests.User @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("CreateUser", ______typeParameters7 ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters8 = new global::System.Type[] {typeof(global::Refit.Tests.User) }; /// public async global::System.Threading.Tasks.Task> CreateUserWithMetadata(global::Refit.Tests.User @user) { var ______arguments = new object[] { @user }; var ______func = requestBuilder.BuildRestResultFuncForMethod("CreateUserWithMetadata", ______typeParameters8 ); return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApiDisposable.g.verified.cs ================================================ //HintName: IGitHubApiDisposable.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitTestsIGitHubApiDisposable : global::Refit.Tests.IGitHubApiDisposable { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitTestsIGitHubApiDisposable(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task RefitMethod() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("RefitMethod", global::System.Array.Empty() ); await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// void global::System.IDisposable.Dispose() { Client?.Dispose(); } } } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#INestedGitHubApi.g.verified.cs ================================================ //HintName: INestedGitHubApi.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class RefitTestsTestNestedINestedGitHubApi : global::Refit.Tests.TestNested.INestedGitHubApi { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public RefitTestsTestNestedINestedGitHubApi(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } private static readonly global::System.Type[] ______typeParameters = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task GetUser(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUser", ______typeParameters ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters0 = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable GetUserObservable(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserObservable", ______typeParameters0 ); return (global::System.IObservable)______func(this.Client, ______arguments); } private static readonly global::System.Type[] ______typeParameters1 = new global::System.Type[] {typeof(string) }; /// public global::System.IObservable GetUserCamelCase(string @userName) { var ______arguments = new object[] { @userName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserCamelCase", ______typeParameters1 ); return (global::System.IObservable)______func(this.Client, ______arguments); } private static readonly global::System.Type[] ______typeParameters2 = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task> GetOrgMembers(string @orgName) { var ______arguments = new object[] { @orgName }; var ______func = requestBuilder.BuildRestResultFuncForMethod("GetOrgMembers", ______typeParameters2 ); return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false); } private static readonly global::System.Type[] ______typeParameters3 = new global::System.Type[] {typeof(string) }; /// public async global::System.Threading.Tasks.Task FindUsers(string @q) { var ______arguments = new object[] { @q }; var ______func = requestBuilder.BuildRestResultFuncForMethod("FindUsers", ______typeParameters3 ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public async global::System.Threading.Tasks.Task GetIndex() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndex", global::System.Array.Empty() ); return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public global::System.IObservable GetIndexObservable() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndexObservable", global::System.Array.Empty() ); return (global::System.IObservable)______func(this.Client, ______arguments); } /// public async global::System.Threading.Tasks.Task NothingToSeeHere() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("NothingToSeeHere", global::System.Array.Empty() ); await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#PreserveAttribute.g.verified.cs ================================================ //HintName: PreserveAttribute.g.cs #pragma warning disable namespace RefitInternalGenerated { [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.AttributeUsage (global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Enum | global::System.AttributeTargets.Constructor | global::System.AttributeTargets.Method | global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Event | global::System.AttributeTargets.Interface | global::System.AttributeTargets.Delegate)] sealed class PreserveAttribute : global::System.Attribute { // // Fields // public bool AllMembers; public bool Conditional; } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#Generated.g.verified.cs ================================================ //HintName: Generated.g.cs #pragma warning disable namespace Refit.Implementation { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] internal static partial class Generated { #if NET5_0_OR_GREATER [System.Runtime.CompilerServices.ModuleInitializer] [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(global::Refit.Implementation.Generated))] public static void Initialize() { } #endif } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#IServiceWithoutNamespace.g.verified.cs ================================================ //HintName: IServiceWithoutNamespace.g.cs #nullable disable #pragma warning disable namespace Refit.Implementation { partial class Generated { /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.Diagnostics.DebuggerNonUserCode] [global::RefitInternalGenerated.PreserveAttribute] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] partial class IServiceWithoutNamespace : global::IServiceWithoutNamespace { /// public global::System.Net.Http.HttpClient Client { get; } readonly global::Refit.IRequestBuilder requestBuilder; /// public IServiceWithoutNamespace(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) { Client = client; this.requestBuilder = requestBuilder; } /// public async global::System.Threading.Tasks.Task GetRoot() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("GetRoot", global::System.Array.Empty() ); await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } /// public async global::System.Threading.Tasks.Task PostRoot() { var ______arguments = global::System.Array.Empty(); var ______func = requestBuilder.BuildRestResultFuncForMethod("PostRoot", global::System.Array.Empty() ); await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false); } } } } #pragma warning restore ================================================ FILE: Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#PreserveAttribute.g.verified.cs ================================================ //HintName: PreserveAttribute.g.cs #pragma warning disable namespace RefitInternalGenerated { [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.AttributeUsage (global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Enum | global::System.AttributeTargets.Constructor | global::System.AttributeTargets.Method | global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Event | global::System.AttributeTargets.Interface | global::System.AttributeTargets.Delegate)] sealed class PreserveAttribute : global::System.Attribute { // // Fields // public bool AllMembers; public bool Conditional; } } #pragma warning restore ================================================ FILE: Refit.Xml/Refit.Xml.csproj ================================================  Refit Xml Serializer ($(TargetFramework)) Refit Serializers for Xml $(RefitTargets) true Refit enable true ================================================ FILE: Refit.Xml/XmlContentSerializer.cs ================================================ using System; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; namespace Refit { /// /// A implementing which provides Xml content serialization. /// /// /// Initializes a new instance of the class. /// /// The settings. /// settings public class XmlContentSerializer(XmlContentSerializerSettings settings) : IHttpContentSerializer { readonly XmlContentSerializerSettings settings = settings ?? throw new ArgumentNullException(nameof(settings)); readonly ConcurrentDictionary serializerCache = new(); /// /// Initializes a new instance of the class. /// public XmlContentSerializer() : this(new XmlContentSerializerSettings()) { } /// /// Serialize object of type to a with Xml. /// /// Type of the object to serialize from. /// Object to serialize. /// that contains the serialized object in Xml. /// public HttpContent ToHttpContent(T item) { if (item is null) throw new ArgumentNullException(nameof(item)); var xmlSerializer = serializerCache.GetOrAdd( item.GetType(), t => new XmlSerializer(t, settings.XmlAttributeOverrides) ); using var stream = new MemoryStream(); using var writer = XmlWriter.Create( stream, settings.XmlReaderWriterSettings.WriterSettings ); var encoding = settings.XmlReaderWriterSettings.WriterSettings?.Encoding ?? Encoding.Unicode; xmlSerializer.Serialize(writer, item, settings.XmlNamespaces); var str = encoding.GetString(stream.ToArray()); var content = new StringContent(str, encoding, "application/xml"); return content; } /// /// Deserializes an object of type from a object that contains Xml content. /// /// Type of the object to deserialize to. /// HttpContent object with Xml content to deserialize. /// CancellationToken to abort the deserialization. /// The deserialized object of type . public async Task FromHttpContentAsync( HttpContent content, CancellationToken cancellationToken = default ) { var xmlSerializer = serializerCache.GetOrAdd( typeof(T), t => new XmlSerializer( t, settings.XmlAttributeOverrides, [], null, settings.XmlDefaultNamespace ) ); using var input = new StringReader( await content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false) ); using var reader = XmlReader.Create( input, settings.XmlReaderWriterSettings.ReaderSettings ); return (T?)xmlSerializer.Deserialize(reader); } /// public string? GetFieldNameForProperty(PropertyInfo propertyInfo) { if (propertyInfo is null) throw new ArgumentNullException(nameof(propertyInfo)); return propertyInfo .GetCustomAttributes(true) .Select(a => a.ElementName) .FirstOrDefault() ?? propertyInfo .GetCustomAttributes(true) .Select(a => a.AttributeName) .FirstOrDefault(); } } /// /// XmlReaderWriterSettings. /// public class XmlReaderWriterSettings { XmlReaderSettings readerSettings; XmlWriterSettings writerSettings; /// /// Initializes a new instance of the class. /// public XmlReaderWriterSettings() : this(new XmlReaderSettings(), new XmlWriterSettings()) { } /// /// Initializes a new instance of the class. /// /// The reader settings. public XmlReaderWriterSettings(XmlReaderSettings readerSettings) : this(readerSettings, new XmlWriterSettings()) { } /// /// Initializes a new instance of the class. /// /// The writer settings. public XmlReaderWriterSettings(XmlWriterSettings writerSettings) : this(new XmlReaderSettings(), writerSettings) { } #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. /// /// Initializes a new instance of the class. /// /// The reader settings. /// The writer settings. public XmlReaderWriterSettings( XmlReaderSettings readerSettings, XmlWriterSettings writerSettings ) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { ReaderSettings = readerSettings; WriterSettings = writerSettings; } /// /// Gets or sets the reader settings. /// /// /// The reader settings. /// /// value public XmlReaderSettings ReaderSettings { get { ApplyOverrideSettings(); return readerSettings; } set => readerSettings = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the writer settings. /// /// /// The writer settings. /// /// value public XmlWriterSettings WriterSettings { get { ApplyOverrideSettings(); return writerSettings; } set => writerSettings = value ?? throw new ArgumentNullException(nameof(value)); } /// /// The writer and reader settings are set by the caller, but certain properties /// should remain set to meet the demands of the XmlContentSerializer. Those properties /// are always set here. /// void ApplyOverrideSettings() { writerSettings.Async = true; readerSettings.Async = true; } } /// /// XmlContentSerializerSettings. /// public class XmlContentSerializerSettings { /// /// Initializes a new instance of the class. /// public XmlContentSerializerSettings() { XmlDefaultNamespace = null; XmlReaderWriterSettings = new XmlReaderWriterSettings(); XmlNamespaces = new XmlSerializerNamespaces( [new XmlQualifiedName(string.Empty, string.Empty),] ); XmlAttributeOverrides = new XmlAttributeOverrides(); } /// /// Gets or sets the XML default namespace. /// /// /// The XML default namespace. /// public string? XmlDefaultNamespace { get; set; } /// /// Gets or sets the XML reader writer settings. /// /// /// The XML reader writer settings. /// public XmlReaderWriterSettings XmlReaderWriterSettings { get; set; } /// /// Gets or sets the XML namespaces. /// /// /// The XML namespaces. /// public XmlSerializerNamespaces XmlNamespaces { get; set; } /// /// Gets or sets the XML attribute overrides. /// /// /// The XML attribute overrides. /// public XmlAttributeOverrides XmlAttributeOverrides { get; set; } } } ================================================ FILE: Refit.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.4.11612.150 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4F1C8991-7097-4471-A9A6-A72005AB594D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .github\workflows\ci-build.yml = .github\workflows\ci-build.yml Directory.build.props = Directory.build.props LICENSE = LICENSE README.md = README.md .github\workflows\release.yml = .github\workflows\release.yml version.json = version.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0E99249A-FB80-4C60-8FD3-13820E853FF7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit", "Refit\Refit.csproj", "{613670B3-BBC6-4479-A3D6-B6A6E80D94FD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.Tests", "Refit.Tests\Refit.Tests.csproj", "{EB833B36-D3CA-4308-A776-8D574F2ADF64}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.HttpClientFactory", "Refit.HttpClientFactory\Refit.HttpClientFactory.csproj", "{01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.Newtonsoft.Json", "Refit.Newtonsoft.Json\Refit.Newtonsoft.Json.csproj", "{2210E606-1C91-4EA0-8876-3B2F501F2669}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Roslyn38", "InterfaceStubGenerator.Roslyn38\InterfaceStubGenerator.Roslyn38.csproj", "{72869789-0310-4916-9A41-20D16A01C1B8}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InterfaceStubGenerator.Shared", "InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.shproj", "{B591423D-F92D-4E00-B0EB-615C9853506C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Roslyn41", "InterfaceStubGenerator.Roslyn41\InterfaceStubGenerator.Roslyn41.csproj", "{A4B61169-3314-41DB-8156-BE9677C90C9F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Roslyn50", "InterfaceStubGenerator.Roslyn50\InterfaceStubGenerator.Roslyn50.csproj", "{4F855D43-6D47-4D31-9C17-82E6D7EF50A1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.Xml", "Refit.Xml\Refit.Xml.csproj", "{C4E38B33-3D17-47A7-A47F-8F3900F07499}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refit.Benchmarks", "Refit.Benchmarks\Refit.Benchmarks.csproj", "{ABD72A27-9C30-481A-8303-D8F825A8FD47}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refit.GeneratorTests", "Refit.GeneratorTests\Refit.GeneratorTests.csproj", "{CE7894EA-D411-494A-BA8B-1C231D45025D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{FA3CFAFC-3218-487F-837B-E8755672EA27}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meow.Common", "examples\Meow.Common\Meow.Common.csproj", "{18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meow", "examples\Meow\Meow.csproj", "{73AB8215-BABD-D6DE-F2D5-20BF123DE073}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibraryWithSDKandRefitService", "examples\SampleUsingLocalApi\LibraryWithSDKandRefitService\LibraryWithSDKandRefitService.csproj", "{55ED7170-CB15-0A50-9C4F-C3A0188E150B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleSampleUsingLocalApi", "examples\SampleUsingLocalApi\ConsoleApplication\ConsoleSampleUsingLocalApi.csproj", "{BE9DED31-FAE3-F798-BD79-7BA96883D07A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiForTest", "examples\SampleUsingLocalApi\RestApiforTest\RestApiForTest.csproj", "{23305490-94C3-7131-087F-54EDF910A7ED}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|ARM = Release|ARM Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|ARM.ActiveCfg = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|ARM.Build.0 = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|x64.ActiveCfg = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|x64.Build.0 = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|x86.ActiveCfg = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Debug|x86.Build.0 = Debug|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|Any CPU.Build.0 = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|ARM.ActiveCfg = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|ARM.Build.0 = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|x64.ActiveCfg = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|x64.Build.0 = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|x86.ActiveCfg = Release|Any CPU {613670B3-BBC6-4479-A3D6-B6A6E80D94FD}.Release|x86.Build.0 = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|ARM.ActiveCfg = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|ARM.Build.0 = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|x64.ActiveCfg = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|x64.Build.0 = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|x86.ActiveCfg = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Debug|x86.Build.0 = Debug|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|Any CPU.ActiveCfg = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|Any CPU.Build.0 = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|ARM.ActiveCfg = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|ARM.Build.0 = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|x64.ActiveCfg = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|x64.Build.0 = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|x86.ActiveCfg = Release|Any CPU {EB833B36-D3CA-4308-A776-8D574F2ADF64}.Release|x86.Build.0 = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|ARM.ActiveCfg = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|ARM.Build.0 = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|x64.ActiveCfg = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|x64.Build.0 = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|x86.ActiveCfg = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Debug|x86.Build.0 = Debug|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|Any CPU.Build.0 = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|ARM.ActiveCfg = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|ARM.Build.0 = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|x64.ActiveCfg = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|x64.Build.0 = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|x86.ActiveCfg = Release|Any CPU {01AE14AC-88D1-49C4-A612-2F9B2DEB5DEA}.Release|x86.Build.0 = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|Any CPU.Build.0 = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|ARM.ActiveCfg = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|ARM.Build.0 = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|x64.ActiveCfg = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|x64.Build.0 = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|x86.ActiveCfg = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Debug|x86.Build.0 = Debug|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|Any CPU.ActiveCfg = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|Any CPU.Build.0 = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|ARM.ActiveCfg = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|ARM.Build.0 = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|x64.ActiveCfg = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|x64.Build.0 = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|x86.ActiveCfg = Release|Any CPU {2210E606-1C91-4EA0-8876-3B2F501F2669}.Release|x86.Build.0 = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|ARM.ActiveCfg = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|ARM.Build.0 = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|x64.ActiveCfg = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|x64.Build.0 = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|x86.ActiveCfg = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Debug|x86.Build.0 = Debug|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|Any CPU.Build.0 = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|ARM.ActiveCfg = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|ARM.Build.0 = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x64.ActiveCfg = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x64.Build.0 = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x86.ActiveCfg = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x86.Build.0 = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|ARM.ActiveCfg = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|ARM.Build.0 = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x64.ActiveCfg = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x64.Build.0 = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x86.ActiveCfg = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x86.Build.0 = Debug|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|Any CPU.Build.0 = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|ARM.ActiveCfg = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|ARM.Build.0 = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x64.ActiveCfg = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x64.Build.0 = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x86.ActiveCfg = Release|Any CPU {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x86.Build.0 = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|ARM.ActiveCfg = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|ARM.Build.0 = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|x64.ActiveCfg = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|x64.Build.0 = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|x86.ActiveCfg = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Debug|x86.Build.0 = Debug|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|Any CPU.Build.0 = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|ARM.ActiveCfg = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|ARM.Build.0 = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|x64.ActiveCfg = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|x64.Build.0 = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|x86.ActiveCfg = Release|Any CPU {4F855D43-6D47-4D31-9C17-82E6D7EF50A1}.Release|x86.Build.0 = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|Any CPU.Build.0 = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|ARM.ActiveCfg = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|ARM.Build.0 = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|x64.ActiveCfg = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|x64.Build.0 = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|x86.ActiveCfg = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Debug|x86.Build.0 = Debug|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|Any CPU.ActiveCfg = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|Any CPU.Build.0 = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|ARM.ActiveCfg = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|ARM.Build.0 = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|x64.ActiveCfg = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|x64.Build.0 = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|x86.ActiveCfg = Release|Any CPU {C4E38B33-3D17-47A7-A47F-8F3900F07499}.Release|x86.Build.0 = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|Any CPU.Build.0 = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|ARM.ActiveCfg = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|ARM.Build.0 = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|x64.ActiveCfg = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|x64.Build.0 = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|x86.ActiveCfg = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Debug|x86.Build.0 = Debug|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|Any CPU.Build.0 = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|ARM.ActiveCfg = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|ARM.Build.0 = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|x64.ActiveCfg = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|x64.Build.0 = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|x86.ActiveCfg = Release|Any CPU {ABD72A27-9C30-481A-8303-D8F825A8FD47}.Release|x86.Build.0 = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|Any CPU.Build.0 = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|ARM.ActiveCfg = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|ARM.Build.0 = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|x64.ActiveCfg = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|x64.Build.0 = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|x86.ActiveCfg = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Debug|x86.Build.0 = Debug|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|Any CPU.Build.0 = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|ARM.ActiveCfg = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|ARM.Build.0 = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|x64.ActiveCfg = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|x64.Build.0 = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|x86.ActiveCfg = Release|Any CPU {CE7894EA-D411-494A-BA8B-1C231D45025D}.Release|x86.Build.0 = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|Any CPU.Build.0 = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|ARM.ActiveCfg = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|ARM.Build.0 = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|x64.ActiveCfg = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|x64.Build.0 = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|x86.ActiveCfg = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Debug|x86.Build.0 = Debug|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|Any CPU.ActiveCfg = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|Any CPU.Build.0 = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|ARM.ActiveCfg = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|ARM.Build.0 = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|x64.ActiveCfg = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|x64.Build.0 = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|x86.ActiveCfg = Release|Any CPU {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B}.Release|x86.Build.0 = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|Any CPU.Build.0 = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|ARM.ActiveCfg = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|ARM.Build.0 = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|x64.ActiveCfg = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|x64.Build.0 = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|x86.ActiveCfg = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Debug|x86.Build.0 = Debug|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|Any CPU.ActiveCfg = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|Any CPU.Build.0 = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|ARM.ActiveCfg = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|ARM.Build.0 = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|x64.ActiveCfg = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|x64.Build.0 = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|x86.ActiveCfg = Release|Any CPU {73AB8215-BABD-D6DE-F2D5-20BF123DE073}.Release|x86.Build.0 = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|Any CPU.Build.0 = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|ARM.ActiveCfg = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|ARM.Build.0 = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|x64.ActiveCfg = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|x64.Build.0 = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|x86.ActiveCfg = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Debug|x86.Build.0 = Debug|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|Any CPU.ActiveCfg = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|Any CPU.Build.0 = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|ARM.ActiveCfg = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|ARM.Build.0 = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|x64.ActiveCfg = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|x64.Build.0 = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|x86.ActiveCfg = Release|Any CPU {55ED7170-CB15-0A50-9C4F-C3A0188E150B}.Release|x86.Build.0 = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|ARM.ActiveCfg = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|ARM.Build.0 = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|x64.ActiveCfg = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|x64.Build.0 = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|x86.ActiveCfg = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Debug|x86.Build.0 = Debug|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|Any CPU.Build.0 = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|ARM.ActiveCfg = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|ARM.Build.0 = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|x64.ActiveCfg = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|x64.Build.0 = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|x86.ActiveCfg = Release|Any CPU {BE9DED31-FAE3-F798-BD79-7BA96883D07A}.Release|x86.Build.0 = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|ARM.ActiveCfg = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|ARM.Build.0 = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|x64.ActiveCfg = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|x64.Build.0 = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|x86.ActiveCfg = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Debug|x86.Build.0 = Debug|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|Any CPU.Build.0 = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|ARM.ActiveCfg = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|ARM.Build.0 = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|x64.ActiveCfg = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|x64.Build.0 = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|x86.ActiveCfg = Release|Any CPU {23305490-94C3-7131-087F-54EDF910A7ED}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {EB833B36-D3CA-4308-A776-8D574F2ADF64} = {0E99249A-FB80-4C60-8FD3-13820E853FF7} {ABD72A27-9C30-481A-8303-D8F825A8FD47} = {0E99249A-FB80-4C60-8FD3-13820E853FF7} {CE7894EA-D411-494A-BA8B-1C231D45025D} = {0E99249A-FB80-4C60-8FD3-13820E853FF7} {18EEE85D-7CEE-6DE4-7A39-F6E0F417D87B} = {FA3CFAFC-3218-487F-837B-E8755672EA27} {73AB8215-BABD-D6DE-F2D5-20BF123DE073} = {FA3CFAFC-3218-487F-837B-E8755672EA27} {55ED7170-CB15-0A50-9C4F-C3A0188E150B} = {FA3CFAFC-3218-487F-837B-E8755672EA27} {BE9DED31-FAE3-F798-BD79-7BA96883D07A} = {FA3CFAFC-3218-487F-837B-E8755672EA27} {23305490-94C3-7131-087F-54EDF910A7ED} = {FA3CFAFC-3218-487F-837B-E8755672EA27} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6E9C2873-AFF9-4D32-A784-1BA094814054} EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{72869789-0310-4916-9a41-20d16a01c1b8}*SharedItemsImports = 5 InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{a4b61169-3314-41db-8156-be9677c90c9f}*SharedItemsImports = 5 InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{4f855d43-6d47-4d31-9c17-82e6d7ef50a1}*SharedItemsImports = 5 InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{b591423d-f92d-4e00-b0eb-615c9853506c}*SharedItemsImports = 13 EndGlobalSection EndGlobal ================================================ FILE: _config.yml ================================================ title: Refit description: The automatic type-safe REST library for Xamarin and .NET google_analytics: show_downloads: true theme: jekyll-theme-dinky gems: - jekyll-mentions ================================================ FILE: config/filelist.txt ================================================ **/Refit.* **/InterfaceStubGenerator.* ================================================ FILE: config/signclient.json ================================================ { "SignClient": { "AzureAd": { "AADInstance": "https://login.microsoftonline.com/", "ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8", "TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e" }, "Service": { "Url": "https://codesign.dotnetfoundation.org/", "ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001" } } } ================================================ FILE: examples/Meow/Meow.csproj ================================================ Exe net8.0 enable enable false ================================================ FILE: examples/Meow/Program.cs ================================================ using Serilog; namespace Meow; public static class Program { public static async Task Main(string[] args) { Log.Logger = new LoggerConfiguration() .WriteTo.Console() .MinimumLevel.Verbose() .CreateLogger(); await Issue2056And2058Demo.RunAsync(); Log.Information("Issue #2056 and #2058 demo checks passed."); var service = new CatsService(new Uri("https://api.thecatapi.com")); var results = await service.Search("bengal"); Log.Debug("{results}", results); } } ================================================ FILE: examples/Meow.Common/Meow.Common.csproj ================================================ net8.0 enable enable false ================================================ FILE: examples/Meow.Common/Middleware/HttpClientDiagnosticsHandler.cs ================================================ using System.Diagnostics; using Serilog; namespace HttpClientDiagnostics; [DebuggerStepThrough] public class HttpClientDiagnosticsHandler : DelegatingHandler { public HttpClientDiagnosticsHandler(HttpMessageHandler innerHandler) : base(innerHandler) { } public HttpClientDiagnosticsHandler() { } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { var totalElapsedTime = Stopwatch.StartNew(); Log.Debug(string.Format("Request: {0}", request)); if (request.Content != null) { var content = await request.Content.ReadAsStringAsync().ConfigureAwait(false); Log.Debug(string.Format("Request Content: {0}", content)); } var responseElapsedTime = Stopwatch.StartNew(); var response = await base.SendAsync(request, cancellationToken); Log.Debug(string.Format("Response: {0}", response)); if (response.Content != null) { var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); Log.Debug(string.Format("Response Content: {0}", content)); } responseElapsedTime.Stop(); Log.Debug( string.Format( "Response elapsed time: {0} ms", responseElapsedTime.ElapsedMilliseconds ) ); totalElapsedTime.Stop(); Log.Debug( string.Format("Total elapsed time: {0} ms", totalElapsedTime.ElapsedMilliseconds) ); return response; } } ================================================ FILE: examples/Meow.Common/Responses/SearchResponse.cs ================================================ namespace Meow.Responses; public class SearchResult { public Breed[] breeds { get; set; } public string id { get; set; } public string url { get; set; } public int width { get; set; } public int height { get; set; } } public class Breed { public Weight weight { get; set; } public string id { get; set; } public string name { get; set; } public string cfa_url { get; set; } public string vetstreet_url { get; set; } public string vcahospitals_url { get; set; } public string temperament { get; set; } public string origin { get; set; } public string country_codes { get; set; } public string country_code { get; set; } public string description { get; set; } public string life_span { get; set; } public int indoor { get; set; } public int lap { get; set; } public string alt_names { get; set; } public int adaptability { get; set; } public int affection_level { get; set; } public int child_friendly { get; set; } public int dog_friendly { get; set; } public int energy_level { get; set; } public int grooming { get; set; } public int health_issues { get; set; } public int intelligence { get; set; } public int shedding_level { get; set; } public int social_needs { get; set; } public int stranger_friendly { get; set; } public int vocalisation { get; set; } public int experimental { get; set; } public int hairless { get; set; } public int natural { get; set; } public int rare { get; set; } public int rex { get; set; } public int suppressed_tail { get; set; } public int short_legs { get; set; } public string wikipedia_url { get; set; } public int hypoallergenic { get; set; } } public class Weight { public string imperial { get; set; } public string metric { get; set; } } ================================================ FILE: examples/Meow.Common/Services/CatsService.cs ================================================ using HttpClientDiagnostics; using Meow.Responses; using Refit; namespace Meow; public class CatsService { private readonly HttpClient _httpClient; private readonly ITheCatsAPI _theCatsApi; public CatsService(Uri baseUrl) { _httpClient = new HttpClient(new HttpClientDiagnosticsHandler(new HttpClientHandler())) { BaseAddress = baseUrl }; _theCatsApi = RestService.For(_httpClient); } public async Task> Search(string breed) { return await _theCatsApi.Search(breed).ConfigureAwait(false); } } ================================================ FILE: examples/Meow.Common/Services/ITheCatsAPI.cs ================================================ using Meow.Responses; using Refit; namespace Meow; [Headers("x-api-key: b95bfb30-55bc-4327-bb8b-35d740f70051")] public interface ITheCatsAPI { [Get("/v1/images/search")] Task> Search([AliasAs("q")] string breedIdentifier); } ================================================ FILE: examples/Meow.Common/Services/Issue2056And2058Demo.cs ================================================ using System.Net; using System.Text; using Newtonsoft.Json; using Refit; namespace Meow; public static class Issue2056And2058Demo { public static async Task RunAsync() { using var httpClient = new HttpClient( new CustomerIdHeaderHandler(new DemoBackendHandler()) ) { BaseAddress = new Uri("https://demo.local") }; var api = RestService.For( httpClient, new RefitSettings { ContentSerializer = new NewtonsoftJsonContentSerializer() } ); await ValidateIssue2056Async(api); await ValidateIssue2058Async(api); } static async Task ValidateIssue2056Async(IIssueDemoApi api) { var customerIds = Enumerable.Range(1000, 50).ToArray(); var responses = await Task.WhenAll( customerIds.Select(async customerId => { var echo = await api.EchoCustomerAsync(customerId); return (Expected: customerId, Actual: echo.CustomerIdHeader); }) ); var mismatches = responses.Where(x => x.Expected.ToString() != x.Actual).ToArray(); if (mismatches.Length > 0) { throw new InvalidOperationException( $"Issue #2056 check failed. Found {mismatches.Length} mismatched CustomerId headers." ); } } static async Task ValidateIssue2058Async(IIssueDemoApi api) { var payload = await api.GetLargePayloadAsync(2000); if (payload.Items.Count != 2000) { throw new InvalidOperationException( $"Issue #2058 check failed. Expected 2000 items but got {payload.Items.Count}." ); } } } public interface IIssueDemoApi { [Get("/echo-customer")] Task EchoCustomerAsync([Property("CustomerId")] int customerId); [Get("/large-payload")] Task GetLargePayloadAsync([AliasAs("size")] int size); } public sealed class CustomerEchoResponse { [JsonProperty("customerIdHeader")] public string? CustomerIdHeader { get; set; } } public sealed class LargePayloadResponse { [JsonProperty("items")] public List Items { get; set; } = []; } public sealed class CustomerIdHeaderHandler(HttpMessageHandler innerHandler) : DelegatingHandler(innerHandler) { static readonly HttpRequestOptionsKey CustomerIdKey = new("CustomerId"); protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { if (request.Options.TryGetValue(CustomerIdKey, out var customerId) && customerId is not null) { request.Headers.Remove("CustomerId"); request.Headers.TryAddWithoutValidation("CustomerId", customerId.ToString()); } return base.SendAsync(request, cancellationToken); } } public sealed class DemoBackendHandler : HttpMessageHandler { protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { if (request.RequestUri?.AbsolutePath == "/echo-customer") { var customerIdHeader = request.Headers.TryGetValues("CustomerId", out var values) ? values.FirstOrDefault() : null; return Task.FromResult( new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent( JsonConvert.SerializeObject(new CustomerEchoResponse { CustomerIdHeader = customerIdHeader }), Encoding.UTF8, "application/json" ) } ); } if (request.RequestUri?.AbsolutePath == "/large-payload") { var query = request.RequestUri.Query.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries); var size = 100; foreach (var part in query) { var kv = part.Split('=', 2); if (kv.Length == 2 && kv[0] == "size" && int.TryParse(Uri.UnescapeDataString(kv[1]), out var parsed)) { size = parsed; break; } } var payload = JsonConvert.SerializeObject(new LargePayloadResponse { Items = Enumerable.Range(1, size).ToList() }); return Task.FromResult( new HttpResponseMessage(HttpStatusCode.OK) { Content = new AsyncOnlyJsonHttpContent(payload) } ); } return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)); } } public sealed class AsyncOnlyJsonHttpContent(string json) : HttpContent { readonly byte[] _buffer = Encoding.UTF8.GetBytes(json); protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => stream.WriteAsync(_buffer, 0, _buffer.Length); protected override bool TryComputeLength(out long length) { length = _buffer.Length; return true; } protected override Task CreateContentReadStreamAsync() => Task.FromResult(new AsyncOnlyReadStream(_buffer)); } public sealed class AsyncOnlyReadStream(byte[] data) : Stream { readonly MemoryStream _inner = new(data, writable: false); public override bool CanRead => true; public override bool CanSeek => _inner.CanSeek; public override bool CanWrite => false; public override long Length => _inner.Length; public override long Position { get => _inner.Position; set => _inner.Position = value; } public override void Flush() => _inner.Flush(); public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("Synchronous reads are not supported in this stream."); public override async ValueTask ReadAsync( Memory buffer, CancellationToken cancellationToken = default ) => await _inner.ReadAsync(buffer, cancellationToken); public override Task ReadAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) => _inner.ReadAsync(buffer, offset, count, cancellationToken); public override long Seek(long offset, SeekOrigin origin) => _inner.Seek(offset, origin); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } ================================================ FILE: examples/Meow.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28711.60 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meow", "Meow\Meow.csproj", "{F89AADAA-1C9E-4125-B6CF-9AD1E6CD94F1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meow.Common", "Meow.Common\Meow.Common.csproj", "{270B1965-B666-4360-AE18-D129DFE3CB9F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {F89AADAA-1C9E-4125-B6CF-9AD1E6CD94F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F89AADAA-1C9E-4125-B6CF-9AD1E6CD94F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {F89AADAA-1C9E-4125-B6CF-9AD1E6CD94F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {F89AADAA-1C9E-4125-B6CF-9AD1E6CD94F1}.Release|Any CPU.Build.0 = Release|Any CPU {270B1965-B666-4360-AE18-D129DFE3CB9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {270B1965-B666-4360-AE18-D129DFE3CB9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {270B1965-B666-4360-AE18-D129DFE3CB9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {270B1965-B666-4360-AE18-D129DFE3CB9F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DCB37AC1-9A6D-4947-AA9D-61B0A757E0D6} EndGlobalSection EndGlobal ================================================ FILE: examples/SampleUsingLocalApi/ConsoleApplication/ConsoleSampleUsingLocalApi.csproj ================================================ Exe net8.0 false ================================================ FILE: examples/SampleUsingLocalApi/ConsoleApplication/Program.cs ================================================ using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using LibraryWithSDKandRefitService; using Refit; namespace ConsoleSampleUsingLocalApi { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); HttpClient _client = new HttpClient { BaseAddress = new Uri("http://localhost:61868") }; IRestService _restApiService = RestService.For(_client); Console.WriteLine( "Enter from the following numbers to access the APIs,\n1 for get ,\n2 for get with argument, \n3 for post,\n4 for put, \n5 for Delete \n" ); while (true) { int choice = Int32.Parse(Console.ReadLine() ?? "6"); switch (choice) { case 1: var result1 = _restApiService.GetWithNoParameter().Result; Console.WriteLine(result1); break; case 2: var result2 = _restApiService.GetWithParameter(4).Result; Console.WriteLine(result2); break; case 3: var result3 = _restApiService.PostWithTestObject(new ModelForTest()).Result; Console.WriteLine(result3); break; case 4: var result4 = _restApiService .PutWithParameters(4, new ModelForTest()) .Result; Console.WriteLine(result4); break; case 5: var result5 = _restApiService.DeleteWithParameters(5).Result; Console.WriteLine(result5); break; default: Console.WriteLine("Bhai Please Enter valid if you are really serious"); break; } } } } } ================================================ FILE: examples/SampleUsingLocalApi/LibraryWithSDKandRefitService/IRestService.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Refit; namespace LibraryWithSDKandRefitService { public interface IRestService { [Get("/api/values")] Task GetWithNoParameter(); [Get("/api/values/{id}")] Task GetWithParameter([AliasAs("id")] int id); [Post("/api/values")] Task PostWithTestObject([Body] ModelForTest modelObject); [Put("/api/values/{id}")] Task PutWithParameters([AliasAs("id")] int id, [Body] ModelForTest modelObject); [Delete("/api/values/{id}")] Task DeleteWithParameters([AliasAs("id")] int id); } } ================================================ FILE: examples/SampleUsingLocalApi/LibraryWithSDKandRefitService/LibraryWithSDKandRefitService.csproj ================================================  net8.0 false ================================================ FILE: examples/SampleUsingLocalApi/LibraryWithSDKandRefitService/ModelForTest.cs ================================================ namespace LibraryWithSDKandRefitService { public class ModelForTest { public string TestVariable { get; set; } } } ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/Controllers/ValuesController.cs ================================================ using LibraryWithSDKandRefitService; using Microsoft.AspNetCore.Mvc; namespace RestApiforTest.Controllers; /// /// Represents an API controller that provides endpoints for managing and retrieving values. /// /// This controller defines standard HTTP endpoints for GET, POST, PUT, and DELETE operations /// using RESTful conventions. Each action corresponds to a specific HTTP verb and route, allowing clients to /// interact with value resources. The controller is intended for demonstration or template purposes and can be /// extended to implement actual data operations. [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { /// /// Handles HTTP GET requests and returns a confirmation message indicating that the API was called without /// arguments. /// /// An containing a message that the API was called with no arguments. [HttpGet] public ActionResult Get() { return "Get Api with no argument was Called"; } /// /// Retrieves a string response for the specified identifier. /// /// The identifier for which to retrieve the response. /// An containing the response string for the specified identifier. [HttpGet("{id}")] public ActionResult Get(int id) { return "Get Api was called"; } /// /// Handles HTTP POST requests to create a new resource using the provided test object. /// /// The object containing the data to be processed in the POST request. Cannot be null. /// An ActionResult containing a string that indicates the result of the POST operation. [HttpPost] public ActionResult Post([FromBody] ModelForTest testObject) { return "Post Api was Called"; } /// /// Updates the resource identified by the specified ID with the provided data. /// /// The unique identifier of the resource to update. /// The data used to update the resource. Cannot be null. /// An ActionResult containing a confirmation message indicating that the update operation was called. [HttpPut("{id}")] public ActionResult Put(int id, [FromBody] ModelForTest testObject) { return "Put Api was called"; } /// /// Deletes the resource identified by the specified ID. /// /// The unique identifier of the resource to delete. /// An ActionResult containing a message indicating that the delete operation was called. [HttpDelete("{id}")] public ActionResult Delete(int id) { return "Delete Api was Called"; } } ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/Program.cs ================================================ namespace RestApiforTest; /// /// Provides the entry point and web host configuration methods for the application. /// internal static class Program { /// /// Serves as the entry point for the application. /// /// This method configures and starts the web host. It is typically called automatically /// by the runtime and should not be invoked directly. /// An array of command-line arguments supplied to the application. public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } /// /// Initializes a new instance of the web host builder with pre-configured defaults and the specified startup /// class. /// /// This method sets up the web host with default configuration, logging, and Kestrel /// server settings, and specifies the application's startup class. It is typically called from the /// application's entry point to configure and launch the ASP.NET Core application. /// An array of command-line arguments to configure the web host. May be empty but cannot be null. /// A configured web host builder instance that can be used to build and run the web application. public static IHostBuilder CreateWebHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); } ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/Properties/launchSettings.json ================================================ { "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "RestApiforTest": { "commandName": "Project", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5000" } }, "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:51523/", "sslPort": 44302 } } } ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/RestApiForTest.csproj ================================================ net8.0 InProcess false ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/Startup.cs ================================================ namespace RestApiforTest; /// /// Provides configuration and service setup for the application's startup process. /// /// The Startup class is used by the ASP.NET Core runtime to configure services and the HTTP /// request pipeline for the application. It defines methods for registering services with the dependency injection /// container and for specifying how HTTP requests are handled. This class is typically specified as the entry point /// for application startup in the program's host configuration. internal class Startup { /// /// Initializes a new instance of the Startup class with the specified application configuration settings. /// /// The application configuration settings to be used for configuring services and the app's request pipeline. /// Cannot be null. public Startup(IConfiguration configuration) { Configuration = configuration; } /// /// Gets the application's configuration settings. /// /// Use this property to access key-value pairs and configuration sections defined for /// the application, such as settings from appsettings.json, environment variables, or other configuration /// providers. public IConfiguration Configuration { get; } /// /// Configures the application's services by adding required service registrations to the dependency injection /// container. /// /// Call this method to register services needed by the application, such as MVC /// controllers and related infrastructure. This method is typically called by the runtime during application /// startup. /// The collection of service descriptors to which application services are added. This parameter must not be /// null. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); } /// /// Configures the application's request pipeline and environment-specific middleware. /// /// In a development environment, this method adds middleware to display detailed /// exception information. It also configures MVC routing for handling HTTP requests. /// The application builder used to configure the HTTP request pipeline. /// The hosting environment information for the current application. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: examples/SampleUsingLocalApi/RestApiforTest/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" } ================================================ FILE: examples/SampleUsingLocalApi.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.852 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryWithSDKandRefitService", "SampleUsingLocalApi\LibraryWithSDKandRefitService\LibraryWithSDKandRefitService.csproj", "{97A0AF3F-830B-4067-BFC3-0BA7AC6851E7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestApiForTest", "SampleUsingLocalApi\RestApiforTest\RestApiForTest.csproj", "{1A2E3E56-878E-4D2D-89D3-626DB0825120}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleSampleUsingLocalApi", "SampleUsingLocalApi\ConsoleApplication\ConsoleSampleUsingLocalApi.csproj", "{D7F3EC64-7473-4FD3-AB05-D7BE55009BE3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {97A0AF3F-830B-4067-BFC3-0BA7AC6851E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {97A0AF3F-830B-4067-BFC3-0BA7AC6851E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {97A0AF3F-830B-4067-BFC3-0BA7AC6851E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {97A0AF3F-830B-4067-BFC3-0BA7AC6851E7}.Release|Any CPU.Build.0 = Release|Any CPU {1A2E3E56-878E-4D2D-89D3-626DB0825120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A2E3E56-878E-4D2D-89D3-626DB0825120}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A2E3E56-878E-4D2D-89D3-626DB0825120}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A2E3E56-878E-4D2D-89D3-626DB0825120}.Release|Any CPU.Build.0 = Release|Any CPU {D7F3EC64-7473-4FD3-AB05-D7BE55009BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7F3EC64-7473-4FD3-AB05-D7BE55009BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7F3EC64-7473-4FD3-AB05-D7BE55009BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7F3EC64-7473-4FD3-AB05-D7BE55009BE3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ACEEE495-B3D1-4788-9EE2-C33449109B85} EndGlobalSection EndGlobal ================================================ FILE: stylesheets/pygment_trac.css ================================================ .highlight { background: #ffffff; } .highlight .c { color: #999988; font-style: italic } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { font-weight: bold } /* Keyword */ .highlight .o { font-weight: bold } /* Operator */ .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #999999 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { font-weight: bold } /* Keyword.Constant */ .highlight .kd { font-weight: bold } /* Keyword.Declaration */ .highlight .kn { font-weight: bold } /* Keyword.Namespace */ .highlight .kp { font-weight: bold } /* Keyword.Pseudo */ .highlight .kr { font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #009999 } /* Literal.Number */ .highlight .s { color: #d14 } /* Literal.String */ .highlight .na { color: #008080 } /* Name.Attribute */ .highlight .nb { color: #0086B3 } /* Name.Builtin */ .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ .highlight .no { color: #008080 } /* Name.Constant */ .highlight .ni { color: #800080 } /* Name.Entity */ .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ .highlight .nn { color: #555555 } /* Name.Namespace */ .highlight .nt { color: #000080 } /* Name.Tag */ .highlight .nv { color: #008080 } /* Name.Variable */ .highlight .ow { font-weight: bold } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mf { color: #009999 } /* Literal.Number.Float */ .highlight .mh { color: #009999 } /* Literal.Number.Hex */ .highlight .mi { color: #009999 } /* Literal.Number.Integer */ .highlight .mo { color: #009999 } /* Literal.Number.Oct */ .highlight .sb { color: #d14 } /* Literal.String.Backtick */ .highlight .sc { color: #d14 } /* Literal.String.Char */ .highlight .sd { color: #d14 } /* Literal.String.Doc */ .highlight .s2 { color: #d14 } /* Literal.String.Double */ .highlight .se { color: #d14 } /* Literal.String.Escape */ .highlight .sh { color: #d14 } /* Literal.String.Heredoc */ .highlight .si { color: #d14 } /* Literal.String.Interpol */ .highlight .sx { color: #d14 } /* Literal.String.Other */ .highlight .sr { color: #009926 } /* Literal.String.Regex */ .highlight .s1 { color: #d14 } /* Literal.String.Single */ .highlight .ss { color: #990073 } /* Literal.String.Symbol */ .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #008080 } /* Name.Variable.Class */ .highlight .vg { color: #008080 } /* Name.Variable.Global */ .highlight .vi { color: #008080 } /* Name.Variable.Instance */ .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ .type-csharp .highlight .k { color: #0000FF } .type-csharp .highlight .kt { color: #0000FF } .type-csharp .highlight .nf { color: #000000; font-weight: normal } .type-csharp .highlight .nc { color: #2B91AF } .type-csharp .highlight .nn { color: #000000 } .type-csharp .highlight .s { color: #A31515 } .type-csharp .highlight .sc { color: #A31515 } ================================================ FILE: stylesheets/styles.css ================================================ @import url(https://fonts.googleapis.com/css?family=Arvo:400,700,400italic); /* MeyerWeb Reset */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font: inherit; vertical-align: baseline; } /* Base text styles */ body { padding:10px 50px 0 0; font-family:"Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; color: #232323; background-color: #FBFAF7; margin: 0; line-height: 1.8em; -webkit-font-smoothing: antialiased; } h1, h2, h3, h4, h5, h6 { color:#232323; margin:36px 0 10px; } p, ul, ol, table, dl { margin:0 0 22px; } h1, h2, h3 { font-family: Arvo, Monaco, serif; line-height:1.3; font-weight: normal; } h1,h2, h3 { display: block; border-bottom: 1px solid #ccc; padding-bottom: 5px; } h1 { font-size: 30px; } h2 { font-size: 24px; } h3 { font-size: 18px; } h4, h5, h6 { font-family: Arvo, Monaco, serif; font-weight: 700; } a { color:#C30000; font-weight:200; text-decoration:none; } a:hover { text-decoration: underline; } a small { font-size: 12px; } em { font-style: italic; } strong { font-weight:700; } ul li { list-style: inside; padding-left: 25px; } ol li { list-style: decimal inside; padding-left: 20px; } blockquote { margin: 0; padding: 0 0 0 20px; font-style: italic; } dl, dt, dd, dl p { font-color: #444; } dl dt { font-weight: bold; } dl dd { padding-left: 20px; font-style: italic; } dl p { padding-left: 20px; font-style: italic; } hr { border:0; background:#ccc; height:1px; margin:0 0 24px; } /* Images */ img { position: relative; margin: 0 auto; max-width: 650px; padding: 5px; margin: 10px 0 32px 0; border: 1px solid #ccc; } /* Code blocks */ code, pre { font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; color:#000; font-size:14px; } pre { padding: 4px 12px; background: #FDFEFB; border-radius:4px; border:1px solid #D7D8C8; overflow: auto; overflow-y: hidden; margin-bottom: 32px; } /* Tables */ table { width:100%; } table { border: 1px solid #ccc; margin-bottom: 32px; text-align: left; } th { font-family: 'Arvo', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: normal; padding: 10px; background: #232323; color: #FDFEFB; } td { padding: 10px; background: #ccc; } /* Wrapper */ .wrapper { width:960px; } /* Header */ header { background-color: #171717; color: #FDFDFB; width:170px; float:left; position:fixed; border: 1px solid #000; -webkit-border-top-right-radius: 4px; -webkit-border-bottom-right-radius: 4px; -moz-border-radius-topright: 4px; -moz-border-radius-bottomright: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; padding: 34px 25px 22px 50px; margin: 30px 25px 0 0; -webkit-font-smoothing: antialiased; } p.header { font-size: 16px; } h1.header { font-family: Arvo, sans-serif; font-size: 30px; font-weight: 300; line-height: 1.3em; border-bottom: none; margin-top: 0; } h1.header, a.header, a.name, header a{ color: #fff; } a.header { text-decoration: underline; } a.name { white-space: nowrap; } header ul { list-style:none; padding:0; } header li { list-style-type: none; width:132px; height:15px; margin-bottom: 12px; line-height: 1em; padding: 6px 6px 6px 7px; background: #AF0011; background: -moz-linear-gradient(top, #AF0011 0%, #820011 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd)); background: -webkit-linear-gradient(top, #AF0011 0%,#820011 100%); background: -o-linear-gradient(top, #AF0011 0%,#820011 100%); background: -ms-linear-gradient(top, #AF0011 0%,#820011 100%); background: linear-gradient(top, #AF0011 0%,#820011 100%); border-radius:4px; border:1px solid #0D0D0D; -webkit-box-shadow: inset 0px 1px 1px 0 rgba(233,2,38, 1); box-shadow: inset 0px 1px 1px 0 rgba(233,2,38, 1); } header li:hover { background: #C3001D; background: -moz-linear-gradient(top, #C3001D 0%, #950119 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd)); background: -webkit-linear-gradient(top, #C3001D 0%,#950119 100%); background: -o-linear-gradient(top, #C3001D 0%,#950119 100%); background: -ms-linear-gradient(top, #C3001D 0%,#950119 100%); background: linear-gradient(top, #C3001D 0%,#950119 100%); } a.buttons { -webkit-font-smoothing: antialiased; background: url(../images/arrow-down.png) no-repeat; font-weight: normal; text-shadow: rgba(0, 0, 0, 0.4) 0 -1px 0; padding: 2px 2px 2px 22px; height: 30px; } a.github { background: url(../images/octocat-small.png) no-repeat 1px; } a.buttons:hover { color: #fff; text-decoration: none; } /* Section - for main page content */ section { width:650px; float:right; padding-bottom:50px; } /* Footer */ footer { width:170px; float:left; position:fixed; bottom:10px; padding-left: 50px; } @media print, screen and (max-width: 960px) { div.wrapper { width:auto; margin:0; } header, section, footer { float:none; position:static; width:auto; } footer { border-top: 1px solid #ccc; margin:0 84px 0 50px; padding:0; } header { padding-right:320px; } section { padding:20px 84px 20px 50px; margin:0 0 20px; } header a small { display:inline; } header ul { position:absolute; right:130px; top:84px; } } @media print, screen and (max-width: 720px) { body { word-wrap:break-word; } header { padding:10px 20px 0; margin-right: 0; } section { padding:10px 0 10px 20px; margin:0 0 30px; } footer { margin: 0 0 0 30px; } header ul, header p.view { position:static; } } @media print, screen and (max-width: 480px) { header ul li.download { display:none; } footer { margin: 0 0 0 20px; } footer a{ display:block; } } @media print { body { padding:0.4in; font-size:12pt; color:#444; } } ================================================ FILE: version.json ================================================ { "version": "10.1", "publicReleaseRefSpec": [ "^refs/heads/main", // we release out of master "^refs/heads/rel/\\d+\\.\\d+\\.\\d+" // we also release branches starting with rel/N.N.N ], "nugetPackageVersion":{ "semVer": 2 }, "cloudBuild": { "setVersionVariables": true, "buildNumber": { "enabled": false } } }