< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.HttpClientCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/HttpClientCodeGenerator.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 30
Coverable lines: 30
Total lines: 89
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 10
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
EmitHttpClientRegistrations(...)0%110100%
EscapeStringLiteral(...)100%210%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/HttpClientCodeGenerator.cs

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Collections.Generic;
 5using System.Text;
 6
 7using NexusLabs.Needlr.Generators.Models;
 8
 9namespace NexusLabs.Needlr.Generators.CodeGen;
 10
 11/// <summary>
 12/// Emits the body of the <c>RegisterHttpClients</c> method for discovered
 13/// <c>[HttpClientOptions]</c> types. Each discovered type produces:
 14/// <list type="number">
 15/// <item><description>An <c>AddOptions&lt;T&gt;().BindConfiguration(section)</c> call so <c>IOptions&lt;T&gt;</c> and f
 16/// <item><description>A <c>services.AddHttpClient(clientName, (sp, client) =&gt; { ... })</c> call whose body condition
 17/// </list>
 18/// The conditional emission is the load-bearing design choice: future capabilities
 19/// (resilience, handler chain, handler lifetime, etc.) are added by introducing a
 20/// new capability flag + a new emission block here, with no change to the attribute
 21/// or existing consumers.
 22/// </summary>
 23internal static class HttpClientCodeGenerator
 24{
 25    /// <summary>
 26    /// Emits HttpClient registration statements into the supplied <see cref="StringBuilder"/>.
 27    /// The caller is responsible for opening/closing the containing method.
 28    /// </summary>
 29    /// <param name="builder">The builder to append to (already positioned inside a method body).</param>
 30    /// <param name="httpClients">The discovered HttpClient options types to emit registrations for.</param>
 31    public static void EmitHttpClientRegistrations(
 32        StringBuilder builder,
 33        IReadOnlyList<DiscoveredHttpClient> httpClients)
 34    {
 035        foreach (var http in httpClients)
 36        {
 037            builder.AppendLine();
 038            builder.AppendLine($"        // Named HttpClient: \"{http.ClientName}\" (bound to \"{http.SectionName}\")");
 39
 40            // Step 1: IOptions<T> binding so consumers can inject IOptions<WebFetchHttpClientOptions>
 41            // if they need runtime access to the record alongside the HttpClient.
 042            builder.AppendLine($"        services.AddOptions<{http.TypeName}>().BindConfiguration(\"{http.SectionName}\"
 43
 44            // Step 2: the named AddHttpClient call with a capability-driven configuration callback.
 045            builder.AppendLine($"        services.AddHttpClient(\"{EscapeStringLiteral(http.ClientName)}\", (sp, client)
 046            builder.AppendLine("        {");
 047            builder.AppendLine($"            var options = sp.GetRequiredService<global::Microsoft.Extensions.Options.IO
 48
 049            if ((http.Capabilities & HttpClientCapabilities.Timeout) != 0)
 50            {
 051                builder.AppendLine("            client.Timeout = options.Timeout;");
 52            }
 53
 054            if ((http.Capabilities & HttpClientCapabilities.BaseAddress) != 0)
 55            {
 056                builder.AppendLine("            if (options.BaseAddress is not null)");
 057                builder.AppendLine("            {");
 058                builder.AppendLine("                client.BaseAddress = options.BaseAddress;");
 059                builder.AppendLine("            }");
 60            }
 61
 062            if ((http.Capabilities & HttpClientCapabilities.UserAgent) != 0)
 63            {
 064                builder.AppendLine("            if (!string.IsNullOrEmpty(options.UserAgent))");
 065                builder.AppendLine("            {");
 066                builder.AppendLine("                client.DefaultRequestHeaders.UserAgent.ParseAdd(options.UserAgent);"
 067                builder.AppendLine("            }");
 68            }
 69
 070            if ((http.Capabilities & HttpClientCapabilities.Headers) != 0)
 71            {
 072                builder.AppendLine("            if (options.DefaultHeaders is not null)");
 073                builder.AppendLine("            {");
 074                builder.AppendLine("                foreach (var kvp in options.DefaultHeaders)");
 075                builder.AppendLine("                {");
 076                builder.AppendLine("                    client.DefaultRequestHeaders.Add(kvp.Key, kvp.Value);");
 077                builder.AppendLine("                }");
 078                builder.AppendLine("            }");
 79            }
 80
 081            builder.AppendLine("        });");
 82        }
 083    }
 84
 85    private static string EscapeStringLiteral(string value)
 86    {
 087        return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
 88    }
 89}