< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.OptionsCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/OptionsCodeGenerator.cs
Line coverage
91%
Covered lines: 572
Uncovered lines: 56
Coverable lines: 628
Total lines: 1264
Line coverage: 91%
Branch coverage
82%
Covered branches: 293
Total branches: 357
Branch coverage: 82%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateOptionsValidatorsSource(...)100%2222100%
GenerateDataAnnotationsValidatorsSource(...)100%1010100%
GenerateDataAnnotationValidation(...)60%683570.12%
EscapeString(...)100%11100%
EscapeVerbatimString(...)100%11100%
GeneratePositionalRecordConstructorsSource(...)100%88100%
GetDefaultValueForType(...)100%88100%
GenerateReflectionOptionsRegistration(...)100%2020100%
GenerateAotOptionsRegistration(...)100%88100%
GenerateConfigureBinding(...)100%66100%
GenerateInitOnlyBinding(...)100%88100%
GeneratePositionalRecordBinding(...)87.5%8895.23%
GeneratePropertyParseVariable(...)87.5%333291.66%
GenerateComplexTypeParseVariable(...)73.33%151597.36%
GenerateComplexTypeCollectionItem(...)92.85%1414100%
GenerateNestedPropertyAssignment(...)50%2260%
GenerateChildPropertyAssignment(...)100%11100%
GeneratePropertyInitializer(...)100%11100%
RegisterValidator(...)100%1212100%
RegisterValidatorForFactory(...)60%111076.92%
GeneratePropertyBinding(...)100%22100%
GenerateComplexTypeBinding(...)80%55100%
GenerateNestedObjectBinding(...)100%66100%
GenerateNestedPropertyBinding(...)83.33%121295.65%
GenerateCollectionBindingInNested(...)25%5455.55%
GenerateArrayBinding(...)100%11100%
GenerateArrayBindingCore(...)87.5%88100%
GenerateListBinding(...)100%11100%
GenerateListBindingCore(...)87.5%88100%
GenerateDictionaryBinding(...)100%11100%
GenerateDictionaryBindingCore(...)87.5%88100%
GenerateChildPropertyBinding(...)100%11100%
GenerateScalarPropertyAssignment(...)68.51%1255471.05%
GeneratePrimitiveCollectionAdd(...)75%9871.42%
GeneratePrimitiveListAdd(...)75%9871.42%
GeneratePrimitiveDictionaryAdd(...)75%9871.42%
GetNonNullableTypeName(...)50%2266.66%
GetBaseTypeName(...)66.66%6680%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/OptionsCodeGenerator.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/// Generates IValidateOptions implementations for options classes with validation.
 13/// </summary>
 14internal static class OptionsCodeGenerator
 15{
 16    internal static string GenerateOptionsValidatorsSource(IReadOnlyList<DiscoveredOptions> optionsWithValidators, strin
 17    {
 2218        var builder = new StringBuilder();
 2219        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 20
 2221        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Options Validators");
 2222        builder.AppendLine("#nullable enable");
 2223        builder.AppendLine();
 2224        builder.AppendLine("using System.Collections.Generic;");
 2225        builder.AppendLine("using System.Linq;");
 2226        builder.AppendLine();
 2227        builder.AppendLine("using Microsoft.Extensions.Options;");
 2228        builder.AppendLine();
 2229        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 2230        builder.AppendLine();
 2231        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 2232        builder.AppendLine();
 33
 34        // Generate validator class for each options type with a validator method
 8835        foreach (var opt in optionsWithValidators)
 36        {
 2237            if (!opt.HasValidatorMethod || opt.ValidatorMethod == null)
 38                continue;
 39
 2240            var shortTypeName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 2241            var validatorClassName = shortTypeName + "Validator";
 42
 43            // Determine which type has the validator method
 2244            var validatorTargetType = opt.HasExternalValidator ? opt.ValidatorTypeName! : opt.TypeName;
 45
 2246            builder.AppendLine("/// <summary>");
 2247            builder.AppendLine($"/// Generated validator for <see cref=\"{opt.TypeName}\"/>.");
 2248            if (opt.HasExternalValidator)
 49            {
 1050                builder.AppendLine($"/// Uses external validator <see cref=\"{validatorTargetType}\"/>.");
 51            }
 52            else
 53            {
 1254                builder.AppendLine("/// Calls the validation method on the options instance.");
 55            }
 2256            builder.AppendLine("/// </summary>");
 2257            builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\",
 2258            builder.AppendLine($"public sealed class {validatorClassName} : IValidateOptions<{opt.TypeName}>");
 2259            builder.AppendLine("{");
 60
 2261            if (opt.HasExternalValidator && !opt.ValidatorMethod.Value.IsStatic)
 62            {
 63                // External validator needs to be injected for instance methods
 864                builder.AppendLine($"    private readonly {validatorTargetType} _validator;");
 865                builder.AppendLine();
 866                builder.AppendLine($"    public {validatorClassName}({validatorTargetType} validator)");
 867                builder.AppendLine("    {");
 868                builder.AppendLine("        _validator = validator;");
 869                builder.AppendLine("    }");
 870                builder.AppendLine();
 71            }
 72
 2273            builder.AppendLine($"    public ValidateOptionsResult Validate(string? name, {opt.TypeName} options)");
 2274            builder.AppendLine("    {");
 2275            builder.AppendLine("        var errors = new List<string>();");
 76
 77            // Generate the foreach to iterate errors
 78            string validationCall;
 2279            if (opt.HasExternalValidator)
 80            {
 1081                if (opt.ValidatorMethod.Value.UsesOptionsValidatorInterface)
 82                {
 283                    validationCall =
 284                        $"((global::NexusLabs.Needlr.Generators.IOptionsValidator<{opt.TypeName}>)_validator).Validate(o
 85                }
 886                else if (opt.ValidatorMethod.Value.IsStatic)
 87                {
 88                    // Static method on external type: ExternalValidator.ValidateMethod(options)
 289                    validationCall = $"{validatorTargetType}.{opt.ValidatorMethod.Value.MethodName}(options)";
 90                }
 91                else
 92                {
 93                    // Instance method on external type: _validator.ValidateMethod(options)
 694                    validationCall = $"_validator.{opt.ValidatorMethod.Value.MethodName}(options)";
 95                }
 96            }
 1297            else if (opt.ValidatorMethod.Value.IsStatic)
 98            {
 99                // Static method on options type: OptionsType.ValidateMethod(options)
 1100                validationCall = $"{opt.TypeName}.{opt.ValidatorMethod.Value.MethodName}(options)";
 101            }
 102            else
 103            {
 104                // Instance method on options type: options.ValidateMethod()
 11105                validationCall = $"options.{opt.ValidatorMethod.Value.MethodName}()";
 106            }
 107
 22108            builder.AppendLine($"        foreach (var error in {validationCall})");
 22109            builder.AppendLine("        {");
 22110            builder.AppendLine("            // Support both string and ValidationError (ValidationError.ToString() retur
 22111            builder.AppendLine("            var errorMessage = error?.ToString() ?? string.Empty;");
 22112            builder.AppendLine("            if (!string.IsNullOrEmpty(errorMessage))");
 22113            builder.AppendLine("            {");
 22114            builder.AppendLine("                errors.Add(errorMessage);");
 22115            builder.AppendLine("            }");
 22116            builder.AppendLine("        }");
 22117            builder.AppendLine();
 22118            builder.AppendLine("        if (errors.Count > 0)");
 22119            builder.AppendLine("        {");
 22120            builder.AppendLine($"            return ValidateOptionsResult.Fail(errors);");
 22121            builder.AppendLine("        }");
 22122            builder.AppendLine();
 22123            builder.AppendLine("        return ValidateOptionsResult.Success;");
 22124            builder.AppendLine("    }");
 22125            builder.AppendLine("}");
 22126            builder.AppendLine();
 127        }
 128
 22129        return builder.ToString();
 130    }
 131
 132    /// <summary>
 133    /// Generates IValidateOptions implementations for options classes with DataAnnotation attributes.
 134    /// This enables AOT-compatible validation without reflection.
 135    /// </summary>
 136    internal static string GenerateDataAnnotationsValidatorsSource(
 137        IReadOnlyList<DiscoveredOptions> optionsWithDataAnnotations,
 138        string assemblyName,
 139        BreadcrumbWriter breadcrumbs,
 140        string? projectDirectory)
 141    {
 18142        var builder = new StringBuilder();
 18143        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 144
 18145        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated DataAnnotations Validators");
 18146        builder.AppendLine("#nullable enable");
 18147        builder.AppendLine();
 18148        builder.AppendLine("using System.Collections.Generic;");
 18149        builder.AppendLine("using System.Text.RegularExpressions;");
 18150        builder.AppendLine();
 18151        builder.AppendLine("using Microsoft.Extensions.Options;");
 18152        builder.AppendLine();
 18153        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 18154        builder.AppendLine();
 155
 74156        foreach (var opt in optionsWithDataAnnotations)
 157        {
 19158            if (!opt.HasDataAnnotations)
 159                continue;
 160
 19161            var shortTypeName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 19162            var validatorClassName = shortTypeName + "DataAnnotationsValidator";
 163
 19164            builder.AppendLine("/// <summary>");
 19165            builder.AppendLine($"/// Generated DataAnnotations validator for <see cref=\"{opt.TypeName}\"/>.");
 19166            builder.AppendLine("/// Validates DataAnnotation attributes without reflection (AOT-compatible).");
 19167            builder.AppendLine("/// </summary>");
 19168            builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\",
 19169            builder.AppendLine($"public sealed class {validatorClassName} : IValidateOptions<{opt.TypeName}>");
 19170            builder.AppendLine("{");
 19171            builder.AppendLine($"    public ValidateOptionsResult Validate(string? name, {opt.TypeName} options)");
 19172            builder.AppendLine("    {");
 19173            builder.AppendLine("        var errors = new List<string>();");
 19174            builder.AppendLine();
 175
 176            // Generate validation for each property with DataAnnotations
 82177            foreach (var prop in opt.Properties)
 178            {
 22179                if (!prop.HasDataAnnotations)
 180                    continue;
 181
 90182                foreach (var annotation in prop.DataAnnotations)
 183                {
 23184                    GenerateDataAnnotationValidation(builder, prop, annotation);
 185                }
 186            }
 187
 19188            builder.AppendLine("        if (errors.Count > 0)");
 19189            builder.AppendLine("        {");
 19190            builder.AppendLine("            return ValidateOptionsResult.Fail(errors);");
 19191            builder.AppendLine("        }");
 19192            builder.AppendLine();
 19193            builder.AppendLine("        return ValidateOptionsResult.Success;");
 19194            builder.AppendLine("    }");
 19195            builder.AppendLine("}");
 19196            builder.AppendLine();
 197        }
 198
 18199        return builder.ToString();
 200    }
 201
 202    private static void GenerateDataAnnotationValidation(StringBuilder builder, OptionsPropertyInfo prop, DataAnnotation
 203    {
 23204        var propName = prop.Name;
 23205        var errorMsg = annotation.ErrorMessage;
 206
 23207        switch (annotation.Kind)
 208        {
 209            case DataAnnotationKind.Required:
 12210                var requiredError = errorMsg ?? $"The {propName} field is required.";
 12211                if (prop.TypeName.Contains("string"))
 212                {
 12213                    builder.AppendLine($"        if (string.IsNullOrEmpty(options.{propName}))");
 214                }
 215                else
 216                {
 0217                    builder.AppendLine($"        if (options.{propName} == null)");
 218                }
 12219                builder.AppendLine("        {");
 12220                builder.AppendLine($"            errors.Add(\"{EscapeString(requiredError)}\");");
 12221                builder.AppendLine("        }");
 12222                builder.AppendLine();
 12223                break;
 224
 225            case DataAnnotationKind.Range:
 6226                var min = annotation.Minimum;
 6227                var max = annotation.Maximum;
 6228                var rangeError = errorMsg ?? $"The field {propName} must be between {min} and {max}.";
 6229                builder.AppendLine($"        if (options.{propName} < {min} || options.{propName} > {max})");
 6230                builder.AppendLine("        {");
 6231                builder.AppendLine($"            errors.Add(\"{EscapeString(rangeError)}\");");
 6232                builder.AppendLine("        }");
 6233                builder.AppendLine();
 6234                break;
 235
 236            case DataAnnotationKind.StringLength:
 2237                var maxLen = annotation.Maximum;
 2238                var minLen = annotation.MinimumLength ?? 0;
 2239                var strLenError = errorMsg ?? $"The field {propName} must be a string with a maximum length of {maxLen}.
 2240                if (minLen > 0)
 241                {
 2242                    builder.AppendLine($"        if (options.{propName}?.Length < {minLen} || options.{propName}?.Length
 243                }
 244                else
 245                {
 0246                    builder.AppendLine($"        if (options.{propName}?.Length > {maxLen})");
 247                }
 2248                builder.AppendLine("        {");
 2249                builder.AppendLine($"            errors.Add(\"{EscapeString(strLenError)}\");");
 2250                builder.AppendLine("        }");
 2251                builder.AppendLine();
 2252                break;
 253
 254            case DataAnnotationKind.MinLength:
 1255                var minLenVal = annotation.MinimumLength ?? 0;
 1256                var minLenError = errorMsg ?? $"The field {propName} must have a minimum length of {minLenVal}.";
 1257                builder.AppendLine($"        if (options.{propName}?.Length < {minLenVal})");
 1258                builder.AppendLine("        {");
 1259                builder.AppendLine($"            errors.Add(\"{EscapeString(minLenError)}\");");
 1260                builder.AppendLine("        }");
 1261                builder.AppendLine();
 1262                break;
 263
 264            case DataAnnotationKind.MaxLength:
 1265                var maxLenVal = annotation.Maximum;
 1266                var maxLenError = errorMsg ?? $"The field {propName} must have a maximum length of {maxLenVal}.";
 1267                builder.AppendLine($"        if (options.{propName}?.Length > {maxLenVal})");
 1268                builder.AppendLine("        {");
 1269                builder.AppendLine($"            errors.Add(\"{EscapeString(maxLenError)}\");");
 1270                builder.AppendLine("        }");
 1271                builder.AppendLine();
 1272                break;
 273
 274            case DataAnnotationKind.RegularExpression:
 1275                var pattern = annotation.Pattern ?? "";
 1276                var regexError = errorMsg ?? $"The field {propName} must match the regular expression '{pattern}'.";
 277                // Use verbatim string for pattern
 1278                builder.AppendLine($"        if (options.{propName} != null && !Regex.IsMatch(options.{propName}, @\"{Es
 1279                builder.AppendLine("        {");
 1280                builder.AppendLine($"            errors.Add(\"{EscapeString(regexError)}\");");
 1281                builder.AppendLine("        }");
 1282                builder.AppendLine();
 1283                break;
 284
 285            case DataAnnotationKind.EmailAddress:
 0286                var emailError = errorMsg ?? $"The {propName} field is not a valid e-mail address.";
 0287                builder.AppendLine($"        if (!string.IsNullOrEmpty(options.{propName}) && !Regex.IsMatch(options.{pr
 0288                builder.AppendLine("        {");
 0289                builder.AppendLine($"            errors.Add(\"{EscapeString(emailError)}\");");
 0290                builder.AppendLine("        }");
 0291                builder.AppendLine();
 0292                break;
 293
 294            case DataAnnotationKind.Url:
 0295                var urlError = errorMsg ?? $"The {propName} field is not a valid fully-qualified http, https, or ftp URL
 0296                builder.AppendLine($"        if (!string.IsNullOrEmpty(options.{propName}) && !global::System.Uri.TryCre
 0297                builder.AppendLine("        {");
 0298                builder.AppendLine($"            errors.Add(\"{EscapeString(urlError)}\");");
 0299                builder.AppendLine("        }");
 0300                builder.AppendLine();
 0301                break;
 302
 303            case DataAnnotationKind.Phone:
 0304                var phoneError = errorMsg ?? $"The {propName} field is not a valid phone number.";
 0305                builder.AppendLine($"        if (!string.IsNullOrEmpty(options.{propName}) && !Regex.IsMatch(options.{pr
 0306                builder.AppendLine("        {");
 0307                builder.AppendLine($"            errors.Add(\"{EscapeString(phoneError)}\");");
 0308                builder.AppendLine("        }");
 0309                builder.AppendLine();
 310                break;
 311
 312            case DataAnnotationKind.Unsupported:
 313                // Skip unsupported - will be handled by analyzer
 314                break;
 315        }
 0316    }
 317
 318    private static string EscapeString(string s)
 319    {
 23320        return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
 321    }
 322
 323    private static string EscapeVerbatimString(string s)
 324    {
 1325        return s.Replace("\"", "\"\"");
 326    }
 327
 328    /// <summary>
 329    /// Generates parameterless constructors for partial positional records with [Options].
 330    /// This enables configuration binding which requires a parameterless constructor.
 331    /// </summary>
 332    internal static string GeneratePositionalRecordConstructorsSource(
 333        IReadOnlyList<DiscoveredOptions> optionsNeedingConstructors,
 334        string assemblyName,
 335        BreadcrumbWriter breadcrumbs,
 336        string? projectDirectory)
 337    {
 12338        var builder = new StringBuilder();
 339
 12340        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Options Constructors");
 12341        builder.AppendLine("#nullable enable");
 12342        builder.AppendLine();
 343
 344        // Group by namespace for cleaner output
 12345        var byNamespace = optionsNeedingConstructors
 12346            .Where(o => o.PositionalRecordInfo != null)
 12347            .GroupBy(o => o.PositionalRecordInfo!.Value.ContainingNamespace)
 24348            .OrderBy(g => g.Key);
 349
 48350        foreach (var namespaceGroup in byNamespace)
 351        {
 12352            var namespaceName = namespaceGroup.Key;
 353
 12354            if (!string.IsNullOrEmpty(namespaceName))
 355            {
 12356                builder.AppendLine($"namespace {namespaceName};");
 12357                builder.AppendLine();
 358            }
 359
 48360            foreach (var opt in namespaceGroup)
 361            {
 12362                var info = opt.PositionalRecordInfo!.Value;
 363
 12364                builder.AppendLine("/// <summary>");
 12365                builder.AppendLine($"/// Generated parameterless constructor for configuration binding.");
 12366                builder.AppendLine($"/// Chains to primary constructor with default values.");
 12367                builder.AppendLine("/// </summary>");
 12368                builder.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generato
 12369                builder.AppendLine($"public partial record {info.ShortTypeName}");
 12370                builder.AppendLine("{");
 371
 372                // Build the constructor call with default values for each parameter
 12373                var defaultArgs = new List<string>();
 216374                foreach (var param in info.Parameters)
 375                {
 96376                    var defaultValue = GetDefaultValueForType(param);
 96377                    defaultArgs.Add(defaultValue);
 378                }
 379
 12380                var argsString = string.Join(", ", defaultArgs);
 12381                builder.AppendLine($"    public {info.ShortTypeName}() : this({argsString}) {{ }}");
 12382                builder.AppendLine("}");
 12383                builder.AppendLine();
 384            }
 385        }
 386
 12387        return builder.ToString();
 388    }
 389
 390    /// <summary>
 391    /// Gets the default value expression for a given type.
 392    /// </summary>
 393    private static string GetDefaultValueForType(PositionalRecordParameter parameter)
 394    {
 96395        var property = parameter.Property;
 96396        if (property.TypeName == "global::System.String" || property.TypeName == "string")
 397        {
 17398            return "string.Empty";
 399        }
 400
 79401        return property.IsNullable || parameter.IsValueType
 79402            ? "default"
 79403            : "default!";
 404    }
 405
 406    // -----------------------------------------------------------------------
 407    // Options registration code generation (moved from TypeRegistryGenerator)
 408    // -----------------------------------------------------------------------
 409
 410    internal static void GenerateReflectionOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> o
 411    {
 412        // Track external validators to register (avoid duplicates)
 73413        var externalValidatorsToRegister = new HashSet<string>();
 414
 312415        foreach (var opt in options)
 416        {
 83417            var typeName = opt.TypeName;
 418
 83419            if (opt.ValidateOnStart)
 420            {
 421                // Use AddOptions pattern for validation support
 422                // services.AddOptions<T>().BindConfiguration("Section").ValidateDataAnnotations().ValidateOnStart();
 27423                builder.Append($"        services.AddOptions<{typeName}>");
 424
 27425                if (opt.IsNamed)
 426                {
 1427                    builder.Append($"(\"{opt.Name}\")");
 428                }
 429                else
 430                {
 26431                    builder.Append("()");
 432                }
 433
 27434                builder.Append($".BindConfiguration(\"{opt.SectionName}\")");
 27435                builder.Append(".ValidateDataAnnotations()");
 27436                builder.AppendLine(".ValidateOnStart();");
 437
 438                // Register source-generated DataAnnotations validator if present
 439                // This runs alongside .ValidateDataAnnotations() - source-gen handles supported attributes,
 440                // reflection fallback handles unsupported attributes (like [CustomValidation])
 27441                if (opt.HasDataAnnotations)
 442                {
 2443                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 2444                    var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAn
 2445                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 446                }
 447
 448                // If there's a custom validator method, register the generated validator
 27449                if (opt.HasValidatorMethod)
 450                {
 16451                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 16452                    var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 16453                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 454
 455                    // If external validator with instance method, register it too
 16456                    if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 457                    {
 7458                        externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 459                    }
 460                }
 461            }
 56462            else if (opt.IsNamed)
 463            {
 464                // Named options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, "name", section
 8465                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 466            }
 467            else
 468            {
 469                // Default options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, section)
 48470                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 471            }
 472        }
 473
 474        // Register external validators that have instance methods
 160475        foreach (var validatorType in externalValidatorsToRegister)
 476        {
 7477            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 478        }
 73479    }
 480
 481    internal static void GenerateAotOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options,
 482    {
 82483        breadcrumbs.WriteInlineComment(builder, "        ", "AOT-compatible options binding (no reflection)");
 484
 82485        var externalValidatorsToRegister = new HashSet<string>();
 486
 346487        foreach (var opt in options)
 488        {
 91489            var typeName = opt.TypeName;
 91490            builder.AppendLine();
 91491            builder.AppendLine($"        // Bind {opt.SectionName} section to {GeneratorHelpers.GetShortTypeName(typeNam
 492
 493            // Choose binding pattern based on type characteristics
 91494            if (opt.IsPositionalRecord)
 495            {
 496                // Positional records: Use constructor binding with Options.Create
 9497                GeneratePositionalRecordBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 498            }
 82499            else if (opt.HasInitOnlyProperties)
 500            {
 501                // Classes/records with init-only properties: Use object initializer with Options.Create
 7502                GenerateInitOnlyBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 503            }
 504            else
 505            {
 506                // Regular classes with setters: Use Configure delegate pattern
 75507                GenerateConfigureBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 508            }
 509        }
 510
 511        // Register external validators that have instance methods
 166512        foreach (var validatorType in externalValidatorsToRegister)
 513        {
 1514            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 515        }
 82516    }
 517
 518    private static void GenerateConfigureBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, 
 519    {
 75520        var typeName = opt.TypeName;
 521
 75522        if (opt.IsNamed)
 523        {
 13524            builder.AppendLine($"        services.AddOptions<{typeName}>(\"{opt.Name}\")");
 525        }
 526        else
 527        {
 62528            builder.AppendLine($"        services.AddOptions<{typeName}>()");
 529        }
 530
 75531        builder.AppendLine("            .Configure<IConfiguration>((options, config) =>");
 75532        builder.AppendLine("            {");
 75533        builder.AppendLine($"                var section = config.GetSection(\"{opt.SectionName}\");");
 534
 535        // Generate property binding for each property
 75536        var propIndex = 0;
 390537        foreach (var prop in opt.Properties)
 538        {
 120539            GeneratePropertyBinding(builder, prop, propIndex);
 120540            propIndex++;
 541        }
 542
 75543        builder.Append("            })");
 544
 545        // Add validation chain if ValidateOnStart is enabled
 75546        if (opt.ValidateOnStart)
 547        {
 20548            builder.AppendLine();
 20549            builder.Append("            .ValidateOnStart()");
 550        }
 551
 75552        builder.AppendLine(";");
 553
 75554        RegisterValidator(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 75555    }
 556
 557    private static void GenerateInitOnlyBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, H
 558    {
 7559        var typeName = opt.TypeName;
 560
 561        // Use AddSingleton with IOptions<T> factory pattern for init-only
 7562        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 7563        builder.AppendLine("        {");
 7564        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 7565        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 566
 567        // Generate parsing variables first
 7568        var propIndex = 0;
 44569        foreach (var prop in opt.Properties)
 570        {
 15571            GeneratePropertyParseVariable(builder, prop, propIndex);
 15572            propIndex++;
 573        }
 574
 575        // Create object with initializer
 7576        builder.AppendLine($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}");
 7577        builder.AppendLine("            {");
 578
 7579        propIndex = 0;
 44580        foreach (var prop in opt.Properties)
 581        {
 15582            var comma = propIndex < opt.Properties.Count - 1 ? "," : "";
 15583            GeneratePropertyInitializer(builder, prop, propIndex, comma);
 15584            propIndex++;
 585        }
 586
 7587        builder.AppendLine("            });");
 7588        builder.AppendLine("        });");
 589
 590        // For validation with factory pattern, we need to register the validator separately
 7591        if (opt.ValidateOnStart)
 592        {
 1593            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 594        }
 7595    }
 596
 597    private static void GeneratePositionalRecordBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssembl
 598    {
 9599        var typeName = opt.TypeName;
 9600        var recordInfo = opt.PositionalRecordInfo!.Value;
 601
 602        // Use AddSingleton with IOptions<T> factory pattern for positional records
 9603        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 9604        builder.AppendLine("        {");
 9605        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 9606        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 607
 608        // Generate parsing variables for each constructor parameter
 9609        var paramIndex = 0;
 188610        foreach (var param in recordInfo.Parameters)
 611        {
 85612            GeneratePropertyParseVariable(builder, param.Property, paramIndex);
 85613            paramIndex++;
 614        }
 615
 616        // Create record with constructor
 9617        builder.Append($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}(");
 618
 9619        paramIndex = 0;
 188620        foreach (var param in recordInfo.Parameters)
 621        {
 161622            if (paramIndex > 0) builder.Append(", ");
 85623            builder.Append($"p{paramIndex}");
 85624            paramIndex++;
 625        }
 626
 9627        builder.AppendLine("));");
 9628        builder.AppendLine("        });");
 629
 630        // For validation with factory pattern, we need to register the validator separately
 9631        if (opt.ValidateOnStart)
 632        {
 0633            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 634        }
 9635    }
 636
 637    private static void GeneratePropertyParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 638    {
 100639        var varName = $"p{index}";
 100640        var typeName = prop.TypeName;
 100641        var baseTypeName = GetBaseTypeName(typeName);
 642
 643        // Handle complex types
 100644        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 645        {
 42646            GenerateComplexTypeParseVariable(builder, prop, index);
 42647            return;
 648        }
 649
 650        // Handle enums
 58651        if (prop.IsEnum && prop.EnumTypeName != null)
 652        {
 5653            var defaultVal = prop.IsNullable ? $"default({typeName})" : "default";
 5654            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && global::Syste
 5655            return;
 656        }
 657
 658        // Handle primitives
 53659        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 660        {
 20661            var defaultVal = prop.IsNullable ? "null" : "\"\"";
 20662            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] ?? {defaultVal};");
 663        }
 33664        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 665        {
 17666            var defaultVal = prop.IsNullable ? $"default({typeName})" : "0";
 17667            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && int.TryParse(
 668        }
 16669        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 670        {
 8671            var defaultVal = prop.IsNullable ? $"default({typeName})" : "false";
 8672            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && bool.TryParse
 673        }
 8674        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 675        {
 8676            var defaultVal = prop.IsNullable ? $"default({typeName})" : "0.0";
 8677            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && double.TryPar
 678        }
 679        else
 680        {
 681            // Default to default value for unsupported types
 0682            builder.AppendLine($"            var {varName} = default({typeName}); // Unsupported type");
 683        }
 0684    }
 685
 686    private static void GenerateComplexTypeParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 687    {
 42688        var varName = $"p{index}";
 42689        var sectionVar = $"sec{index}";
 690
 42691        switch (prop.ComplexTypeKind)
 692        {
 693            case ComplexTypeKind.NestedObject:
 5694                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 5695                builder.AppendLine($"            var {varName} = new {GetNonNullableTypeName(prop.TypeName)}();");
 5696                if (prop.NestedProperties != null)
 697                {
 5698                    var nestedIndex = index * 100;
 62699                    foreach (var nested in prop.NestedProperties)
 700                    {
 26701                        GenerateNestedPropertyAssignment(builder, nested, nestedIndex, varName, sectionVar);
 26702                        nestedIndex++;
 703                    }
 704                }
 705                break;
 706
 707            case ComplexTypeKind.Array:
 12708                var arrayElemType = prop.ElementTypeName ?? "string";
 12709                var itemsVar = $"{varName}Items";
 12710                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 12711                builder.AppendLine($"            var {itemsVar} = new global::System.Collections.Generic.List<{arrayElem
 12712                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 12713                builder.AppendLine("            {");
 12714                GenerateComplexTypeCollectionItem(builder, prop, index, itemsVar, false);
 12715                builder.AppendLine("            }");
 12716                builder.AppendLine($"            var {varName} = {itemsVar}.ToArray();");
 12717                break;
 718
 719            case ComplexTypeKind.List:
 13720                var listElemType = prop.ElementTypeName ?? "string";
 13721                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 13722                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.List<{listElemTy
 13723                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 13724                builder.AppendLine("            {");
 13725                GenerateComplexTypeCollectionItem(builder, prop, index, varName, false);
 13726                builder.AppendLine("            }");
 13727                break;
 728
 729            case ComplexTypeKind.Dictionary:
 12730                var dictValType = prop.ElementTypeName ?? "string";
 12731                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 12732                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.Dictionary<strin
 12733                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 12734                builder.AppendLine("            {");
 12735                GenerateComplexTypeCollectionItem(builder, prop, index, varName, true);
 12736                builder.AppendLine("            }");
 12737                break;
 738
 739            default:
 0740                builder.AppendLine($"            var {varName} = default({prop.TypeName}); // Complex type");
 741                break;
 742        }
 5743    }
 744
 745    private static void GenerateComplexTypeCollectionItem(
 746        StringBuilder builder,
 747        OptionsPropertyInfo prop,
 748        int index,
 749        string targetVar,
 750        bool isDictionary)
 751    {
 37752        var elementType = prop.ElementTypeName ?? "string";
 37753        if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 754        {
 12755            var itemVar = $"item{index}";
 12756            builder.AppendLine($"                var {itemVar} = new {elementType}();");
 12757            var nestedIndex = index * 100;
 168758            foreach (var nestedProperty in prop.NestedProperties)
 759            {
 72760                GenerateChildPropertyAssignment(builder, nestedProperty, nestedIndex, itemVar, "child");
 72761                nestedIndex++;
 762            }
 763
 12764            var target = isDictionary ? $"{targetVar}[child.Key]" : $"{targetVar}.Add";
 12765            builder.AppendLine(isDictionary
 12766                ? $"                {target} = {itemVar};"
 12767                : $"                {target}({itemVar});");
 12768            return;
 769        }
 770
 25771        if (isDictionary)
 772        {
 8773            GeneratePrimitiveDictionaryAdd(builder, elementType, index, targetVar);
 774        }
 775        else
 776        {
 17777            GeneratePrimitiveCollectionAdd(builder, elementType, index, targetVar);
 778        }
 17779    }
 780
 781    private static void GenerateNestedPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, str
 782    {
 26783        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 784        {
 0785            GenerateNestedPropertyBinding(builder, prop, index, targetVar, sectionVar);
 0786            return;
 787        }
 788
 26789        GenerateScalarPropertyAssignment(builder, prop, index, targetVar, sectionVar, "            ", "np");
 26790    }
 791
 792    private static void GenerateChildPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, stri
 793    {
 72794        GenerateScalarPropertyAssignment(builder, prop, index, targetVar, sectionVar, "                ", "cp");
 72795    }
 796
 797    private static void GeneratePropertyInitializer(StringBuilder builder, OptionsPropertyInfo prop, int index, string c
 798    {
 15799        builder.AppendLine($"                {prop.Name} = p{index}{comma}");
 15800    }
 801
 802    private static void RegisterValidator(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, HashSet
 803    {
 75804        var typeName = opt.TypeName;
 75805        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 806
 807        // Register DataAnnotations validator if present
 75808        if (opt.HasDataAnnotations)
 809        {
 17810            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 17811            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 812        }
 813
 75814        if (opt.ValidateOnStart && opt.HasValidatorMethod)
 815        {
 4816            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 4817            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 818
 4819            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 820            {
 1821                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 822            }
 823        }
 75824    }
 825
 826    private static void RegisterValidatorForFactory(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyNam
 827    {
 1828        var typeName = opt.TypeName;
 1829        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 830
 831        // For factory pattern, we need to add OptionsBuilder validation manually
 832        // Since we're using AddSingleton<IOptions<T>>, we also need to register for IOptionsSnapshot and IOptionsMonito
 1833        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptionsSnapshot<{typeNa
 834
 835        // Add startup validation
 1836        builder.AppendLine($"        services.AddOptions<{typeName}>().ValidateOnStart();");
 837
 838        // Register DataAnnotations validator if present
 1839        if (opt.HasDataAnnotations)
 840        {
 0841            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 0842            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 843        }
 844
 1845        if (opt.HasValidatorMethod)
 846        {
 1847            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 1848            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 849
 1850            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 851            {
 0852                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 853            }
 854        }
 1855    }
 856
 857    private static void GeneratePropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 858    {
 859        // Handle complex types first
 120860        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 861        {
 18862            GenerateComplexTypeBinding(builder, prop, index, targetPath);
 18863            return;
 864        }
 865
 102866        GenerateScalarPropertyAssignment(builder, prop, index, targetPath, "section", "                ", "p");
 102867    }
 868
 869    private static void GenerateComplexTypeBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string ta
 870    {
 18871        var sectionVar = $"sec{index}";
 872
 18873        switch (prop.ComplexTypeKind)
 874        {
 875            case ComplexTypeKind.NestedObject:
 6876                GenerateNestedObjectBinding(builder, prop, index, targetPath, sectionVar);
 6877                break;
 878
 879            case ComplexTypeKind.Array:
 3880                GenerateArrayBinding(builder, prop, index, targetPath, sectionVar);
 3881                break;
 882
 883            case ComplexTypeKind.List:
 5884                GenerateListBinding(builder, prop, index, targetPath, sectionVar);
 5885                break;
 886
 887            case ComplexTypeKind.Dictionary:
 4888                GenerateDictionaryBinding(builder, prop, index, targetPath, sectionVar);
 889                break;
 890        }
 4891    }
 892
 893    private static void GenerateNestedObjectBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string t
 894    {
 6895        var nestedPath = $"{targetPath}.{prop.Name}";
 896
 6897        builder.AppendLine($"                // Bind nested object: {prop.Name}");
 6898        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 899
 900        // Initialize if null (for nullable properties)
 6901        if (prop.IsNullable)
 902        {
 1903            builder.AppendLine($"                {nestedPath} ??= new {GetNonNullableTypeName(prop.TypeName)}();");
 904        }
 905
 906        // Generate bindings for nested properties
 6907        if (prop.NestedProperties != null)
 908        {
 6909            var nestedIndex = index * 100; // Use offset to avoid variable name collisions
 30910            foreach (var nestedProp in prop.NestedProperties)
 911            {
 912                // Temporarily swap section context for nested binding
 9913                GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, nestedPath, sectionVar);
 9914                nestedIndex++;
 915            }
 916        }
 6917    }
 918
 919    private static void GenerateNestedPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 920    {
 921        // Handle complex types recursively
 10922        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 923        {
 2924            var innerSectionVar = $"sec{index}";
 2925            switch (prop.ComplexTypeKind)
 926            {
 927                case ComplexTypeKind.NestedObject:
 1928                    builder.AppendLine($"                // Bind nested object: {prop.Name}");
 1929                    builder.AppendLine($"                var {innerSectionVar} = {sectionVarName}.GetSection(\"{prop.Nam
 1930                    if (prop.IsNullable)
 931                    {
 0932                        builder.AppendLine($"                {targetPath}.{prop.Name} ??= new {GetNonNullableTypeName(pr
 933                    }
 1934                    if (prop.NestedProperties != null)
 935                    {
 1936                        var nestedIndex = index * 100;
 4937                        foreach (var nestedProp in prop.NestedProperties)
 938                        {
 1939                            GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, $"{targetPath}.{prop.Name}",
 1940                            nestedIndex++;
 941                        }
 942                    }
 943                    break;
 944
 945                case ComplexTypeKind.Array:
 946                case ComplexTypeKind.List:
 947                case ComplexTypeKind.Dictionary:
 948                    // For collections inside nested objects, generate appropriate binding
 1949                    GenerateCollectionBindingInNested(builder, prop, index, targetPath, sectionVarName);
 950                    break;
 951            }
 2952            return;
 953        }
 954
 8955        GenerateScalarPropertyAssignment(
 8956            builder,
 8957            prop,
 8958            index,
 8959            targetPath,
 8960            sectionVarName,
 8961            "                ",
 8962            "np");
 8963    }
 964
 965    private static void GenerateCollectionBindingInNested(StringBuilder builder, OptionsPropertyInfo prop, int index, st
 966    {
 1967        var collectionSection = $"colSec{index}";
 1968        builder.AppendLine($"                var {collectionSection} = {sectionVarName}.GetSection(\"{prop.Name}\");");
 969
 1970        switch (prop.ComplexTypeKind)
 971        {
 972            case ComplexTypeKind.List:
 1973                GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 1974                break;
 975            case ComplexTypeKind.Array:
 0976                GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 0977                break;
 978            case ComplexTypeKind.Dictionary:
 0979                GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 980                break;
 981        }
 0982    }
 983
 984    private static void GenerateArrayBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPa
 985    {
 3986        builder.AppendLine($"                // Bind array: {prop.Name}");
 3987        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 3988        GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 3989    }
 990
 991    private static void GenerateArrayBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targ
 992    {
 3993        var itemsVar = $"items{index}";
 3994        var elementType = prop.ElementTypeName ?? "string";
 3995        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 996
 3997        builder.AppendLine($"                var {itemsVar} = new global::System.Collections.Generic.List<{elementType}>
 3998        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 3999        builder.AppendLine("                {");
 1000
 31001        if (hasNestedProps)
 1002        {
 1003            // Complex element type
 11004            var itemVar = $"item{index}";
 11005            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 11006            var nestedIndex = index * 100;
 61007            foreach (var nestedProp in prop.NestedProperties!)
 1008            {
 21009                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 21010                nestedIndex++;
 1011            }
 11012            builder.AppendLine($"                    {itemsVar}.Add({itemVar});");
 1013        }
 1014        else
 1015        {
 1016            // Primitive element type
 21017            GeneratePrimitiveCollectionAdd(builder, elementType, index, itemsVar);
 1018        }
 1019
 31020        builder.AppendLine("                }");
 31021        builder.AppendLine($"                {targetPath} = {itemsVar}.ToArray();");
 31022    }
 1023
 1024    private static void GenerateListBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPat
 1025    {
 51026        builder.AppendLine($"                // Bind list: {prop.Name}");
 51027        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 51028        GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 51029    }
 1030
 1031    private static void GenerateListBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 1032    {
 61033        var elementType = prop.ElementTypeName ?? "string";
 61034        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 1035
 61036        builder.AppendLine($"                {targetPath}.Clear();");
 61037        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 61038        builder.AppendLine("                {");
 1039
 61040        if (hasNestedProps)
 1041        {
 1042            // Complex element type
 11043            var itemVar = $"item{index}";
 11044            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 11045            var nestedIndex = index * 100;
 61046            foreach (var nestedProp in prop.NestedProperties!)
 1047            {
 21048                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 21049                nestedIndex++;
 1050            }
 11051            builder.AppendLine($"                    {targetPath}.Add({itemVar});");
 1052        }
 1053        else
 1054        {
 1055            // Primitive element type
 51056            GeneratePrimitiveListAdd(builder, elementType, index, targetPath);
 1057        }
 1058
 61059        builder.AppendLine("                }");
 61060    }
 1061
 1062    private static void GenerateDictionaryBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string tar
 1063    {
 41064        builder.AppendLine($"                // Bind dictionary: {prop.Name}");
 41065        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 41066        GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 41067    }
 1068
 1069    private static void GenerateDictionaryBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 1070    {
 41071        var elementType = prop.ElementTypeName ?? "string";
 41072        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 1073
 41074        builder.AppendLine($"                {targetPath}.Clear();");
 41075        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 41076        builder.AppendLine("                {");
 1077
 41078        if (hasNestedProps)
 1079        {
 1080            // Complex value type
 11081            var itemVar = $"item{index}";
 11082            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 11083            var nestedIndex = index * 100;
 61084            foreach (var nestedProp in prop.NestedProperties!)
 1085            {
 21086                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 21087                nestedIndex++;
 1088            }
 11089            builder.AppendLine($"                    {targetPath}[child.Key] = {itemVar};");
 1090        }
 1091        else
 1092        {
 1093            // Primitive value type
 31094            GeneratePrimitiveDictionaryAdd(builder, elementType, index, targetPath);
 1095        }
 1096
 41097        builder.AppendLine("                }");
 41098    }
 1099
 1100    private static void GenerateChildPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string 
 1101    {
 61102        GenerateScalarPropertyAssignment(builder, prop, index, targetVar, sectionVar, "                    ", "cp");
 61103    }
 1104
 1105    private static void GenerateScalarPropertyAssignment(
 1106        StringBuilder builder,
 1107        OptionsPropertyInfo prop,
 1108        int index,
 1109        string targetPath,
 1110        string sectionVar,
 1111        string indent,
 1112        string variablePrefix)
 1113    {
 2141114        var valueVar = $"{variablePrefix}v{index}";
 2141115        var parsedVar = $"{variablePrefix}p{index}";
 2141116        var baseTypeName = GetBaseTypeName(prop.TypeName);
 1117
 2141118        builder.Append($"{indent}if ({sectionVar}[\"{prop.Name}\"] is {{ }} {valueVar}");
 1119
 2141120        if (prop.IsEnum && prop.EnumTypeName != null)
 1121        {
 281122            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({valueVar}, true, out var {parsed
 1123        }
 1861124        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1125        {
 761126            builder.AppendLine($") {targetPath}.{prop.Name} = {valueVar};");
 1127        }
 1101128        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1129        {
 611130            builder.AppendLine($" && int.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{prop.Name} = {parsedVa
 1131        }
 491132        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1133        {
 211134            builder.AppendLine($" && bool.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{prop.Name} = {parsedV
 1135        }
 281136        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 1137        {
 211138            builder.AppendLine($" && double.TryParse({valueVar}, global::System.Globalization.NumberStyles.Any, global::
 1139        }
 71140        else if (baseTypeName == "float" || baseTypeName == "global::System.Single")
 1141        {
 01142            builder.AppendLine($" && float.TryParse({valueVar}, global::System.Globalization.NumberStyles.Any, global::S
 1143        }
 71144        else if (baseTypeName == "decimal" || baseTypeName == "global::System.Decimal")
 1145        {
 01146            builder.AppendLine($" && decimal.TryParse({valueVar}, global::System.Globalization.NumberStyles.Any, global:
 1147        }
 71148        else if (baseTypeName == "long" || baseTypeName == "global::System.Int64")
 1149        {
 01150            builder.AppendLine($" && long.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{prop.Name} = {parsedV
 1151        }
 71152        else if (baseTypeName == "short" || baseTypeName == "global::System.Int16")
 1153        {
 01154            builder.AppendLine($" && short.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{prop.Name} = {parsed
 1155        }
 71156        else if (baseTypeName == "byte" || baseTypeName == "global::System.Byte")
 1157        {
 01158            builder.AppendLine($" && byte.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{prop.Name} = {parsedV
 1159        }
 71160        else if (baseTypeName == "char" || baseTypeName == "global::System.Char")
 1161        {
 01162            builder.AppendLine($" && {valueVar}.Length == 1) {targetPath}.{prop.Name} = {valueVar}[0];");
 1163        }
 71164        else if (baseTypeName == "global::System.TimeSpan")
 1165        {
 01166            builder.AppendLine($" && global::System.TimeSpan.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{pr
 1167        }
 71168        else if (baseTypeName == "global::System.DateTime")
 1169        {
 01170            builder.AppendLine($" && global::System.DateTime.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{pr
 1171        }
 71172        else if (baseTypeName == "global::System.DateTimeOffset")
 1173        {
 01174            builder.AppendLine($" && global::System.DateTimeOffset.TryParse({valueVar}, out var {parsedVar})) {targetPat
 1175        }
 71176        else if (baseTypeName == "global::System.Guid")
 1177        {
 01178            builder.AppendLine($" && global::System.Guid.TryParse({valueVar}, out var {parsedVar})) {targetPath}.{prop.N
 1179        }
 71180        else if (baseTypeName == "global::System.Uri")
 1181        {
 01182            builder.AppendLine($" && global::System.Uri.TryCreate({valueVar}, global::System.UriKind.RelativeOrAbsolute,
 1183        }
 1184        else
 1185        {
 71186            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 1187        }
 71188    }
 1189
 1190    private static void GeneratePrimitiveCollectionAdd(StringBuilder builder, string elementType, int index, string list
 1191    {
 191192        var baseType = GetBaseTypeName(elementType);
 1193
 191194        if (baseType == "string" || baseType == "global::System.String")
 1195        {
 101196            builder.AppendLine($"                    if (child.Value is {{ }} collectionValue{index}) {listVar}.Add(coll
 1197        }
 91198        else if (baseType == "int" || baseType == "global::System.Int32")
 1199        {
 91200            builder.AppendLine($"                    if (child.Value is {{ }} collectionValue{index} && int.TryParse(col
 1201        }
 1202        else
 1203        {
 01204            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 1205        }
 01206    }
 1207
 1208    private static void GeneratePrimitiveListAdd(StringBuilder builder, string elementType, int index, string targetPath
 1209    {
 51210        var baseType = GetBaseTypeName(elementType);
 1211
 51212        if (baseType == "string" || baseType == "global::System.String")
 1213        {
 41214            builder.AppendLine($"                    if (child.Value is {{ }} listValue{index}) {targetPath}.Add(listVal
 1215        }
 11216        else if (baseType == "int" || baseType == "global::System.Int32")
 1217        {
 11218            builder.AppendLine($"                    if (child.Value is {{ }} listValue{index} && int.TryParse(listValue
 1219        }
 1220        else
 1221        {
 01222            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 1223        }
 01224    }
 1225
 1226    private static void GeneratePrimitiveDictionaryAdd(StringBuilder builder, string elementType, int index, string targ
 1227    {
 111228        var baseType = GetBaseTypeName(elementType);
 1229
 111230        if (baseType == "string" || baseType == "global::System.String")
 1231        {
 51232            builder.AppendLine($"                    if (child.Value is {{ }} dictionaryValue{index}) {targetPath}[child
 1233        }
 61234        else if (baseType == "int" || baseType == "global::System.Int32")
 1235        {
 61236            builder.AppendLine($"                    if (child.Value is {{ }} dictionaryValue{index} && int.TryParse(dic
 1237        }
 1238        else
 1239        {
 01240            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 1241        }
 01242    }
 1243
 1244    private static string GetNonNullableTypeName(string typeName)
 1245    {
 61246        if (typeName.EndsWith("?"))
 01247            return typeName.Substring(0, typeName.Length - 1);
 61248        return typeName;
 1249    }
 1250
 1251    private static string GetBaseTypeName(string typeName)
 1252    {
 1253        // Handle nullable types like "global::System.Nullable<int>" or "int?"
 3491254        if (typeName.StartsWith("global::System.Nullable<") && typeName.EndsWith(">"))
 1255        {
 01256            return typeName.Substring("global::System.Nullable<".Length, typeName.Length - "global::System.Nullable<".Le
 1257        }
 3491258        if (typeName.EndsWith("?"))
 1259        {
 281260            return typeName.Substring(0, typeName.Length - 1);
 1261        }
 3211262        return typeName;
 1263    }
 1264}

Methods/Properties

GenerateOptionsValidatorsSource(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateDataAnnotationsValidatorsSource(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateDataAnnotationValidation(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,NexusLabs.Needlr.Generators.Models.DataAnnotationInfo)
EscapeString(System.String)
EscapeVerbatimString(System.String)
GeneratePositionalRecordConstructorsSource(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GetDefaultValueForType(NexusLabs.Needlr.Generators.Models.PositionalRecordParameter)
GenerateReflectionOptionsRegistration(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter)
GenerateAotOptionsRegistration(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateConfigureBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GenerateInitOnlyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GeneratePositionalRecordBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GeneratePropertyParseVariable(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32)
GenerateComplexTypeParseVariable(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32)
GenerateComplexTypeCollectionItem(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.Boolean)
GenerateNestedPropertyAssignment(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateChildPropertyAssignment(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GeneratePropertyInitializer(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String)
RegisterValidator(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
RegisterValidatorForFactory(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GeneratePropertyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String)
GenerateComplexTypeBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String)
GenerateNestedObjectBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateNestedPropertyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateCollectionBindingInNested(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateArrayBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateArrayBindingCore(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateListBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateListBindingCore(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateDictionaryBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateDictionaryBindingCore(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateChildPropertyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateScalarPropertyAssignment(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String,System.String,System.String)
GeneratePrimitiveCollectionAdd(System.Text.StringBuilder,System.String,System.Int32,System.String)
GeneratePrimitiveListAdd(System.Text.StringBuilder,System.String,System.Int32,System.String)
GeneratePrimitiveDictionaryAdd(System.Text.StringBuilder,System.String,System.Int32,System.String)
GetNonNullableTypeName(System.String)
GetBaseTypeName(System.String)