| | | 1 | | using System; |
| | | 2 | | |
| | | 3 | | namespace NexusLabs.Needlr.Generators; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Marks a class as a named <c>HttpClient</c> configuration type. The source generator |
| | | 7 | | /// will emit both an <c>AddOptions<T>().BindConfiguration(...)</c> call and a |
| | | 8 | | /// matching <c>services.AddHttpClient(name, (sp, client) => { ... })</c> registration, |
| | | 9 | | /// so consumers never have to hand-write either one. |
| | | 10 | | /// </summary> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// <para> |
| | | 13 | | /// The decorated type MUST implement <see cref="INamedHttpClientOptions"/>. It opts into |
| | | 14 | | /// additional configurability by implementing capability interfaces such as |
| | | 15 | | /// <see cref="IHttpClientTimeout"/>, <see cref="IHttpClientUserAgent"/>, |
| | | 16 | | /// <see cref="IHttpClientBaseAddress"/>, and <see cref="IHttpClientDefaultHeaders"/>. |
| | | 17 | | /// The generator emits only the wiring for capabilities actually implemented, so there |
| | | 18 | | /// is no dead code and no attribute property churn when new capabilities are added. |
| | | 19 | | /// </para> |
| | | 20 | | /// <para> |
| | | 21 | | /// The resolved HttpClient name comes from, in order of precedence: |
| | | 22 | | /// <list type="number"> |
| | | 23 | | /// <item><description>The attribute's <see cref="Name"/> property, if set.</description></item> |
| | | 24 | | /// <item><description>A <c>ClientName</c> property on the decorated type with a literal expression body (e.g. <c>public |
| | | 25 | | /// <item><description>Inferred from the type name by stripping the suffixes <c>HttpClientOptions</c>, <c>HttpClientSett |
| | | 26 | | /// </list> |
| | | 27 | | /// </para> |
| | | 28 | | /// <para> |
| | | 29 | | /// The resolved configuration section comes from the attribute's constructor argument, or |
| | | 30 | | /// is inferred as <c>"HttpClients:<ResolvedName>"</c> when no section is given. |
| | | 31 | | /// </para> |
| | | 32 | | /// </remarks> |
| | | 33 | | /// <example> |
| | | 34 | | /// <code> |
| | | 35 | | /// // Minimal form — name inferred as "WebFetch", section inferred as "HttpClients:WebFetch" |
| | | 36 | | /// [HttpClientOptions] |
| | | 37 | | /// public sealed record WebFetchHttpClientOptions : IStandardHttpClientOptions |
| | | 38 | | /// { |
| | | 39 | | /// public string ClientName => "WebFetch"; // optional when suffix-stripping works |
| | | 40 | | /// public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(15); |
| | | 41 | | /// public string? UserAgent { get; init; } = "BrandGhost-Agent/1.0"; |
| | | 42 | | /// public Uri? BaseAddress { get; init; } |
| | | 43 | | /// public IReadOnlyDictionary<string, string>? DefaultHeaders { get; init; } |
| | | 44 | | /// } |
| | | 45 | | /// |
| | | 46 | | /// // Explicit section |
| | | 47 | | /// [HttpClientOptions("Upstream:Tavily")] |
| | | 48 | | /// public sealed record TavilyHttpClientOptions : IStandardHttpClientOptions { ... } |
| | | 49 | | /// |
| | | 50 | | /// // Explicit name override |
| | | 51 | | /// [HttpClientOptions(Name = "tavily-primary")] |
| | | 52 | | /// public sealed record TavilyPrimaryHttpClientOptions : IStandardHttpClientOptions { ... } |
| | | 53 | | /// </code> |
| | | 54 | | /// </example> |
| | | 55 | | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] |
| | | 56 | | public sealed class HttpClientOptionsAttribute : Attribute |
| | | 57 | | { |
| | | 58 | | /// <summary> |
| | | 59 | | /// Initializes a new instance of the <see cref="HttpClientOptionsAttribute"/> class |
| | | 60 | | /// with the section name inferred from the resolved client name |
| | | 61 | | /// (<c>"HttpClients:<ResolvedName>"</c>). |
| | | 62 | | /// </summary> |
| | 3280 | 63 | | public HttpClientOptionsAttribute() |
| | | 64 | | { |
| | 3280 | 65 | | } |
| | | 66 | | |
| | | 67 | | /// <summary> |
| | | 68 | | /// Initializes a new instance of the <see cref="HttpClientOptionsAttribute"/> class |
| | | 69 | | /// with an explicit configuration section name. |
| | | 70 | | /// </summary> |
| | | 71 | | /// <param name="sectionName"> |
| | | 72 | | /// The configuration section to bind to (e.g., <c>"HttpClients:WebFetch"</c> or |
| | | 73 | | /// <c>"Upstream:Tavily"</c>). |
| | | 74 | | /// </param> |
| | 820 | 75 | | public HttpClientOptionsAttribute(string sectionName) |
| | | 76 | | { |
| | 820 | 77 | | SectionName = sectionName; |
| | 820 | 78 | | } |
| | | 79 | | |
| | | 80 | | /// <summary> |
| | | 81 | | /// Gets the explicit configuration section name, or <c>null</c> to infer it from the |
| | | 82 | | /// resolved client name. |
| | | 83 | | /// </summary> |
| | 0 | 84 | | public string? SectionName { get; } |
| | | 85 | | |
| | | 86 | | /// <summary> |
| | | 87 | | /// Gets or sets the explicit HttpClient name. When set, this overrides both the |
| | | 88 | | /// <c>ClientName</c> property on the decorated type and the type-name inference |
| | | 89 | | /// fallback. |
| | | 90 | | /// </summary> |
| | 820 | 91 | | public string? Name { get; set; } |
| | | 92 | | } |