< 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
82%
Covered lines: 550
Uncovered lines: 118
Coverable lines: 668
Total lines: 1346
Line coverage: 82.3%
Branch coverage
64%
Covered branches: 361
Total branches: 563
Branch coverage: 64.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateOptionsValidatorsSource(...)100%2020100%
GenerateDataAnnotationsValidatorsSource(...)100%1010100%
GenerateDataAnnotationValidation(...)60%683570.12%
EscapeString(...)100%11100%
EscapeVerbatimString(...)100%11100%
GeneratePositionalRecordConstructorsSource(...)100%88100%
GetDefaultValueForType(...)46%410115044%
GenerateReflectionOptionsRegistration(...)100%2020100%
GenerateAotOptionsRegistration(...)100%88100%
GenerateConfigureBinding(...)100%66100%
GenerateInitOnlyBinding(...)100%88100%
GeneratePositionalRecordBinding(...)87.5%8895.23%
GeneratePropertyParseVariable(...)53.12%703266.66%
GenerateComplexTypeParseVariable(...)31.03%1662945.45%
GenerateNestedPropertyAssignment(...)50%171266.66%
GenerateChildPropertyAssignment(...)0%7280%
GeneratePropertyInitializer(...)100%11100%
GenerateParameterParseVariable(...)87.5%161692.3%
RegisterValidator(...)100%1212100%
RegisterValidatorForFactory(...)60%111076.92%
GeneratePropertyBinding(...)69.64%1175673.17%
GenerateComplexTypeBinding(...)80%55100%
GenerateNestedObjectBinding(...)100%66100%
GenerateNestedPropertyBinding(...)64.28%372877.77%
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(...)50%311661.53%
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    {
 1818        var builder = new StringBuilder();
 1819        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 20
 1821        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Options Validators");
 1822        builder.AppendLine("#nullable enable");
 1823        builder.AppendLine();
 1824        builder.AppendLine("using System.Collections.Generic;");
 1825        builder.AppendLine("using System.Linq;");
 1826        builder.AppendLine();
 1827        builder.AppendLine("using Microsoft.Extensions.Options;");
 1828        builder.AppendLine();
 1829        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 1830        builder.AppendLine();
 1831        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 1832        builder.AppendLine();
 33
 34        // Generate validator class for each options type with a validator method
 7235        foreach (var opt in optionsWithValidators)
 36        {
 1837            if (!opt.HasValidatorMethod || opt.ValidatorMethod == null)
 38                continue;
 39
 1840            var shortTypeName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 1841            var validatorClassName = shortTypeName + "Validator";
 42
 43            // Determine which type has the validator method
 1844            var validatorTargetType = opt.HasExternalValidator ? opt.ValidatorTypeName! : opt.TypeName;
 45
 1846            builder.AppendLine("/// <summary>");
 1847            builder.AppendLine($"/// Generated validator for <see cref=\"{opt.TypeName}\"/>.");
 1848            if (opt.HasExternalValidator)
 49            {
 650                builder.AppendLine($"/// Uses external validator <see cref=\"{validatorTargetType}\"/>.");
 51            }
 52            else
 53            {
 1254                builder.AppendLine("/// Calls the validation method on the options instance.");
 55            }
 1856            builder.AppendLine("/// </summary>");
 1857            builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\",
 1858            builder.AppendLine($"public sealed class {validatorClassName} : IValidateOptions<{opt.TypeName}>");
 1859            builder.AppendLine("{");
 60
 1861            if (opt.HasExternalValidator && !opt.ValidatorMethod.Value.IsStatic)
 62            {
 63                // External validator needs to be injected for instance methods
 464                builder.AppendLine($"    private readonly {validatorTargetType} _validator;");
 465                builder.AppendLine();
 466                builder.AppendLine($"    public {validatorClassName}({validatorTargetType} validator)");
 467                builder.AppendLine("    {");
 468                builder.AppendLine("        _validator = validator;");
 469                builder.AppendLine("    }");
 470                builder.AppendLine();
 71            }
 72
 1873            builder.AppendLine($"    public ValidateOptionsResult Validate(string? name, {opt.TypeName} options)");
 1874            builder.AppendLine("    {");
 1875            builder.AppendLine("        var errors = new List<string>();");
 76
 77            // Generate the foreach to iterate errors
 78            string validationCall;
 1879            if (opt.HasExternalValidator)
 80            {
 681                if (opt.ValidatorMethod.Value.IsStatic)
 82                {
 83                    // Static method on external type: ExternalValidator.ValidateMethod(options)
 284                    validationCall = $"{validatorTargetType}.{opt.ValidatorMethod.Value.MethodName}(options)";
 85                }
 86                else
 87                {
 88                    // Instance method on external type: _validator.ValidateMethod(options)
 489                    validationCall = $"_validator.{opt.ValidatorMethod.Value.MethodName}(options)";
 90                }
 91            }
 1292            else if (opt.ValidatorMethod.Value.IsStatic)
 93            {
 94                // Static method on options type: OptionsType.ValidateMethod(options)
 195                validationCall = $"{opt.TypeName}.{opt.ValidatorMethod.Value.MethodName}(options)";
 96            }
 97            else
 98            {
 99                // Instance method on options type: options.ValidateMethod()
 11100                validationCall = $"options.{opt.ValidatorMethod.Value.MethodName}()";
 101            }
 102
 18103            builder.AppendLine($"        foreach (var error in {validationCall})");
 18104            builder.AppendLine("        {");
 18105            builder.AppendLine("            // Support both string and ValidationError (ValidationError.ToString() retur
 18106            builder.AppendLine("            var errorMessage = error?.ToString() ?? string.Empty;");
 18107            builder.AppendLine("            if (!string.IsNullOrEmpty(errorMessage))");
 18108            builder.AppendLine("            {");
 18109            builder.AppendLine("                errors.Add(errorMessage);");
 18110            builder.AppendLine("            }");
 18111            builder.AppendLine("        }");
 18112            builder.AppendLine();
 18113            builder.AppendLine("        if (errors.Count > 0)");
 18114            builder.AppendLine("        {");
 18115            builder.AppendLine($"            return ValidateOptionsResult.Fail(errors);");
 18116            builder.AppendLine("        }");
 18117            builder.AppendLine();
 18118            builder.AppendLine("        return ValidateOptionsResult.Success;");
 18119            builder.AppendLine("    }");
 18120            builder.AppendLine("}");
 18121            builder.AppendLine();
 122        }
 123
 18124        return builder.ToString();
 125    }
 126
 127    /// <summary>
 128    /// Generates IValidateOptions implementations for options classes with DataAnnotation attributes.
 129    /// This enables AOT-compatible validation without reflection.
 130    /// </summary>
 131    internal static string GenerateDataAnnotationsValidatorsSource(
 132        IReadOnlyList<DiscoveredOptions> optionsWithDataAnnotations,
 133        string assemblyName,
 134        BreadcrumbWriter breadcrumbs,
 135        string? projectDirectory)
 136    {
 18137        var builder = new StringBuilder();
 18138        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 139
 18140        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated DataAnnotations Validators");
 18141        builder.AppendLine("#nullable enable");
 18142        builder.AppendLine();
 18143        builder.AppendLine("using System.Collections.Generic;");
 18144        builder.AppendLine("using System.Text.RegularExpressions;");
 18145        builder.AppendLine();
 18146        builder.AppendLine("using Microsoft.Extensions.Options;");
 18147        builder.AppendLine();
 18148        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 18149        builder.AppendLine();
 150
 74151        foreach (var opt in optionsWithDataAnnotations)
 152        {
 19153            if (!opt.HasDataAnnotations)
 154                continue;
 155
 19156            var shortTypeName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 19157            var validatorClassName = shortTypeName + "DataAnnotationsValidator";
 158
 19159            builder.AppendLine("/// <summary>");
 19160            builder.AppendLine($"/// Generated DataAnnotations validator for <see cref=\"{opt.TypeName}\"/>.");
 19161            builder.AppendLine("/// Validates DataAnnotation attributes without reflection (AOT-compatible).");
 19162            builder.AppendLine("/// </summary>");
 19163            builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\",
 19164            builder.AppendLine($"public sealed class {validatorClassName} : IValidateOptions<{opt.TypeName}>");
 19165            builder.AppendLine("{");
 19166            builder.AppendLine($"    public ValidateOptionsResult Validate(string? name, {opt.TypeName} options)");
 19167            builder.AppendLine("    {");
 19168            builder.AppendLine("        var errors = new List<string>();");
 19169            builder.AppendLine();
 170
 171            // Generate validation for each property with DataAnnotations
 82172            foreach (var prop in opt.Properties)
 173            {
 22174                if (!prop.HasDataAnnotations)
 175                    continue;
 176
 90177                foreach (var annotation in prop.DataAnnotations)
 178                {
 23179                    GenerateDataAnnotationValidation(builder, prop, annotation);
 180                }
 181            }
 182
 19183            builder.AppendLine("        if (errors.Count > 0)");
 19184            builder.AppendLine("        {");
 19185            builder.AppendLine("            return ValidateOptionsResult.Fail(errors);");
 19186            builder.AppendLine("        }");
 19187            builder.AppendLine();
 19188            builder.AppendLine("        return ValidateOptionsResult.Success;");
 19189            builder.AppendLine("    }");
 19190            builder.AppendLine("}");
 19191            builder.AppendLine();
 192        }
 193
 18194        return builder.ToString();
 195    }
 196
 197    private static void GenerateDataAnnotationValidation(StringBuilder builder, OptionsPropertyInfo prop, DataAnnotation
 198    {
 23199        var propName = prop.Name;
 23200        var errorMsg = annotation.ErrorMessage;
 201
 23202        switch (annotation.Kind)
 203        {
 204            case DataAnnotationKind.Required:
 12205                var requiredError = errorMsg ?? $"The {propName} field is required.";
 12206                if (prop.TypeName.Contains("string"))
 207                {
 12208                    builder.AppendLine($"        if (string.IsNullOrEmpty(options.{propName}))");
 209                }
 210                else
 211                {
 0212                    builder.AppendLine($"        if (options.{propName} == null)");
 213                }
 12214                builder.AppendLine("        {");
 12215                builder.AppendLine($"            errors.Add(\"{EscapeString(requiredError)}\");");
 12216                builder.AppendLine("        }");
 12217                builder.AppendLine();
 12218                break;
 219
 220            case DataAnnotationKind.Range:
 6221                var min = annotation.Minimum;
 6222                var max = annotation.Maximum;
 6223                var rangeError = errorMsg ?? $"The field {propName} must be between {min} and {max}.";
 6224                builder.AppendLine($"        if (options.{propName} < {min} || options.{propName} > {max})");
 6225                builder.AppendLine("        {");
 6226                builder.AppendLine($"            errors.Add(\"{EscapeString(rangeError)}\");");
 6227                builder.AppendLine("        }");
 6228                builder.AppendLine();
 6229                break;
 230
 231            case DataAnnotationKind.StringLength:
 2232                var maxLen = annotation.Maximum;
 2233                var minLen = annotation.MinimumLength ?? 0;
 2234                var strLenError = errorMsg ?? $"The field {propName} must be a string with a maximum length of {maxLen}.
 2235                if (minLen > 0)
 236                {
 2237                    builder.AppendLine($"        if (options.{propName}?.Length < {minLen} || options.{propName}?.Length
 238                }
 239                else
 240                {
 0241                    builder.AppendLine($"        if (options.{propName}?.Length > {maxLen})");
 242                }
 2243                builder.AppendLine("        {");
 2244                builder.AppendLine($"            errors.Add(\"{EscapeString(strLenError)}\");");
 2245                builder.AppendLine("        }");
 2246                builder.AppendLine();
 2247                break;
 248
 249            case DataAnnotationKind.MinLength:
 1250                var minLenVal = annotation.MinimumLength ?? 0;
 1251                var minLenError = errorMsg ?? $"The field {propName} must have a minimum length of {minLenVal}.";
 1252                builder.AppendLine($"        if (options.{propName}?.Length < {minLenVal})");
 1253                builder.AppendLine("        {");
 1254                builder.AppendLine($"            errors.Add(\"{EscapeString(minLenError)}\");");
 1255                builder.AppendLine("        }");
 1256                builder.AppendLine();
 1257                break;
 258
 259            case DataAnnotationKind.MaxLength:
 1260                var maxLenVal = annotation.Maximum;
 1261                var maxLenError = errorMsg ?? $"The field {propName} must have a maximum length of {maxLenVal}.";
 1262                builder.AppendLine($"        if (options.{propName}?.Length > {maxLenVal})");
 1263                builder.AppendLine("        {");
 1264                builder.AppendLine($"            errors.Add(\"{EscapeString(maxLenError)}\");");
 1265                builder.AppendLine("        }");
 1266                builder.AppendLine();
 1267                break;
 268
 269            case DataAnnotationKind.RegularExpression:
 1270                var pattern = annotation.Pattern ?? "";
 1271                var regexError = errorMsg ?? $"The field {propName} must match the regular expression '{pattern}'.";
 272                // Use verbatim string for pattern
 1273                builder.AppendLine($"        if (options.{propName} != null && !Regex.IsMatch(options.{propName}, @\"{Es
 1274                builder.AppendLine("        {");
 1275                builder.AppendLine($"            errors.Add(\"{EscapeString(regexError)}\");");
 1276                builder.AppendLine("        }");
 1277                builder.AppendLine();
 1278                break;
 279
 280            case DataAnnotationKind.EmailAddress:
 0281                var emailError = errorMsg ?? $"The {propName} field is not a valid e-mail address.";
 0282                builder.AppendLine($"        if (!string.IsNullOrEmpty(options.{propName}) && !Regex.IsMatch(options.{pr
 0283                builder.AppendLine("        {");
 0284                builder.AppendLine($"            errors.Add(\"{EscapeString(emailError)}\");");
 0285                builder.AppendLine("        }");
 0286                builder.AppendLine();
 0287                break;
 288
 289            case DataAnnotationKind.Url:
 0290                var urlError = errorMsg ?? $"The {propName} field is not a valid fully-qualified http, https, or ftp URL
 0291                builder.AppendLine($"        if (!string.IsNullOrEmpty(options.{propName}) && !global::System.Uri.TryCre
 0292                builder.AppendLine("        {");
 0293                builder.AppendLine($"            errors.Add(\"{EscapeString(urlError)}\");");
 0294                builder.AppendLine("        }");
 0295                builder.AppendLine();
 0296                break;
 297
 298            case DataAnnotationKind.Phone:
 0299                var phoneError = errorMsg ?? $"The {propName} field is not a valid phone number.";
 0300                builder.AppendLine($"        if (!string.IsNullOrEmpty(options.{propName}) && !Regex.IsMatch(options.{pr
 0301                builder.AppendLine("        {");
 0302                builder.AppendLine($"            errors.Add(\"{EscapeString(phoneError)}\");");
 0303                builder.AppendLine("        }");
 0304                builder.AppendLine();
 305                break;
 306
 307            case DataAnnotationKind.Unsupported:
 308                // Skip unsupported - will be handled by analyzer
 309                break;
 310        }
 0311    }
 312
 313    private static string EscapeString(string s)
 314    {
 23315        return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
 316    }
 317
 318    private static string EscapeVerbatimString(string s)
 319    {
 1320        return s.Replace("\"", "\"\"");
 321    }
 322
 323    /// <summary>
 324    /// Generates parameterless constructors for partial positional records with [Options].
 325    /// This enables configuration binding which requires a parameterless constructor.
 326    /// </summary>
 327    internal static string GeneratePositionalRecordConstructorsSource(
 328        IReadOnlyList<DiscoveredOptions> optionsNeedingConstructors,
 329        string assemblyName,
 330        BreadcrumbWriter breadcrumbs,
 331        string? projectDirectory)
 332    {
 7333        var builder = new StringBuilder();
 334
 7335        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Options Constructors");
 7336        builder.AppendLine("#nullable enable");
 7337        builder.AppendLine();
 338
 339        // Group by namespace for cleaner output
 7340        var byNamespace = optionsNeedingConstructors
 7341            .Where(o => o.PositionalRecordInfo != null)
 7342            .GroupBy(o => o.PositionalRecordInfo!.Value.ContainingNamespace)
 14343            .OrderBy(g => g.Key);
 344
 28345        foreach (var namespaceGroup in byNamespace)
 346        {
 7347            var namespaceName = namespaceGroup.Key;
 348
 7349            if (!string.IsNullOrEmpty(namespaceName))
 350            {
 7351                builder.AppendLine($"namespace {namespaceName};");
 7352                builder.AppendLine();
 353            }
 354
 28355            foreach (var opt in namespaceGroup)
 356            {
 7357                var info = opt.PositionalRecordInfo!.Value;
 358
 7359                builder.AppendLine("/// <summary>");
 7360                builder.AppendLine($"/// Generated parameterless constructor for configuration binding.");
 7361                builder.AppendLine($"/// Chains to primary constructor with default values.");
 7362                builder.AppendLine("/// </summary>");
 7363                builder.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generato
 7364                builder.AppendLine($"public partial record {info.ShortTypeName}");
 7365                builder.AppendLine("{");
 366
 367                // Build the constructor call with default values for each parameter
 7368                var defaultArgs = new List<string>();
 52369                foreach (var param in info.Parameters)
 370                {
 19371                    var defaultValue = GetDefaultValueForType(param.TypeName);
 19372                    defaultArgs.Add(defaultValue);
 373                }
 374
 7375                var argsString = string.Join(", ", defaultArgs);
 7376                builder.AppendLine($"    public {info.ShortTypeName}() : this({argsString}) {{ }}");
 7377                builder.AppendLine("}");
 7378                builder.AppendLine();
 379            }
 380        }
 381
 7382        return builder.ToString();
 383    }
 384
 385    /// <summary>
 386    /// Gets the default value expression for a given type.
 387    /// </summary>
 388    private static string GetDefaultValueForType(string fullyQualifiedTypeName)
 389    {
 390        // Handle common types with user-friendly defaults
 19391        return fullyQualifiedTypeName switch
 19392        {
 7393            "global::System.String" or "string" => "string.Empty",
 4394            "global::System.Boolean" or "bool" => "default",
 6395            "global::System.Int32" or "int" => "default",
 0396            "global::System.Int64" or "long" => "default",
 0397            "global::System.Int16" or "short" => "default",
 0398            "global::System.Byte" or "byte" => "default",
 0399            "global::System.SByte" or "sbyte" => "default",
 0400            "global::System.UInt32" or "uint" => "default",
 0401            "global::System.UInt64" or "ulong" => "default",
 0402            "global::System.UInt16" or "ushort" => "default",
 0403            "global::System.Single" or "float" => "default",
 1404            "global::System.Double" or "double" => "default",
 0405            "global::System.Decimal" or "decimal" => "default",
 0406            "global::System.Char" or "char" => "default",
 0407            "global::System.DateTime" => "default",
 0408            "global::System.DateTimeOffset" => "default",
 0409            "global::System.TimeSpan" => "default",
 0410            "global::System.Guid" => "default",
 19411            // For nullable types and reference types, use default (which gives null for reference types)
 19412            // For other value types, use default
 1413            _ when fullyQualifiedTypeName.EndsWith("?") => "default",
 1414            _ => "default!"  // Reference types need null-forgiving operator
 19415        };
 416    }
 417
 418    // -----------------------------------------------------------------------
 419    // Options registration code generation (moved from TypeRegistryGenerator)
 420    // -----------------------------------------------------------------------
 421
 422    internal static void GenerateReflectionOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> o
 423    {
 424        // Track external validators to register (avoid duplicates)
 68425        var externalValidatorsToRegister = new HashSet<string>();
 426
 292427        foreach (var opt in options)
 428        {
 78429            var typeName = opt.TypeName;
 430
 78431            if (opt.ValidateOnStart)
 432            {
 433                // Use AddOptions pattern for validation support
 434                // services.AddOptions<T>().BindConfiguration("Section").ValidateDataAnnotations().ValidateOnStart();
 23435                builder.Append($"        services.AddOptions<{typeName}>");
 436
 23437                if (opt.IsNamed)
 438                {
 1439                    builder.Append($"(\"{opt.Name}\")");
 440                }
 441                else
 442                {
 22443                    builder.Append("()");
 444                }
 445
 23446                builder.Append($".BindConfiguration(\"{opt.SectionName}\")");
 23447                builder.Append(".ValidateDataAnnotations()");
 23448                builder.AppendLine(".ValidateOnStart();");
 449
 450                // Register source-generated DataAnnotations validator if present
 451                // This runs alongside .ValidateDataAnnotations() - source-gen handles supported attributes,
 452                // reflection fallback handles unsupported attributes (like [CustomValidation])
 23453                if (opt.HasDataAnnotations)
 454                {
 2455                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 2456                    var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAn
 2457                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 458                }
 459
 460                // If there's a custom validator method, register the generated validator
 23461                if (opt.HasValidatorMethod)
 462                {
 12463                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 12464                    var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 12465                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 466
 467                    // If external validator with instance method, register it too
 12468                    if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 469                    {
 3470                        externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 471                    }
 472                }
 473            }
 55474            else if (opt.IsNamed)
 475            {
 476                // Named options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, "name", section
 8477                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 478            }
 479            else
 480            {
 481                // Default options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, section)
 47482                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 483            }
 484        }
 485
 486        // Register external validators that have instance methods
 142487        foreach (var validatorType in externalValidatorsToRegister)
 488        {
 3489            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 490        }
 68491    }
 492
 493    internal static void GenerateAotOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options,
 494    {
 78495        breadcrumbs.WriteInlineComment(builder, "        ", "AOT-compatible options binding (no reflection)");
 496
 78497        var externalValidatorsToRegister = new HashSet<string>();
 498
 330499        foreach (var opt in options)
 500        {
 87501            var typeName = opt.TypeName;
 87502            builder.AppendLine();
 87503            builder.AppendLine($"        // Bind {opt.SectionName} section to {GeneratorHelpers.GetShortTypeName(typeNam
 504
 505            // Choose binding pattern based on type characteristics
 87506            if (opt.IsPositionalRecord)
 507            {
 508                // Positional records: Use constructor binding with Options.Create
 5509                GeneratePositionalRecordBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 510            }
 82511            else if (opt.HasInitOnlyProperties)
 512            {
 513                // Classes/records with init-only properties: Use object initializer with Options.Create
 7514                GenerateInitOnlyBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 515            }
 516            else
 517            {
 518                // Regular classes with setters: Use Configure delegate pattern
 75519                GenerateConfigureBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 520            }
 521        }
 522
 523        // Register external validators that have instance methods
 158524        foreach (var validatorType in externalValidatorsToRegister)
 525        {
 1526            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 527        }
 78528    }
 529
 530    private static void GenerateConfigureBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, 
 531    {
 75532        var typeName = opt.TypeName;
 533
 75534        if (opt.IsNamed)
 535        {
 13536            builder.AppendLine($"        services.AddOptions<{typeName}>(\"{opt.Name}\")");
 537        }
 538        else
 539        {
 62540            builder.AppendLine($"        services.AddOptions<{typeName}>()");
 541        }
 542
 75543        builder.AppendLine("            .Configure<IConfiguration>((options, config) =>");
 75544        builder.AppendLine("            {");
 75545        builder.AppendLine($"                var section = config.GetSection(\"{opt.SectionName}\");");
 546
 547        // Generate property binding for each property
 75548        var propIndex = 0;
 390549        foreach (var prop in opt.Properties)
 550        {
 120551            GeneratePropertyBinding(builder, prop, propIndex);
 120552            propIndex++;
 553        }
 554
 75555        builder.Append("            })");
 556
 557        // Add validation chain if ValidateOnStart is enabled
 75558        if (opt.ValidateOnStart)
 559        {
 20560            builder.AppendLine();
 20561            builder.Append("            .ValidateOnStart()");
 562        }
 563
 75564        builder.AppendLine(";");
 565
 75566        RegisterValidator(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 75567    }
 568
 569    private static void GenerateInitOnlyBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, H
 570    {
 7571        var typeName = opt.TypeName;
 572
 573        // Use AddSingleton with IOptions<T> factory pattern for init-only
 7574        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 7575        builder.AppendLine("        {");
 7576        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 7577        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 578
 579        // Generate parsing variables first
 7580        var propIndex = 0;
 44581        foreach (var prop in opt.Properties)
 582        {
 15583            GeneratePropertyParseVariable(builder, prop, propIndex);
 15584            propIndex++;
 585        }
 586
 587        // Create object with initializer
 7588        builder.AppendLine($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}");
 7589        builder.AppendLine("            {");
 590
 7591        propIndex = 0;
 44592        foreach (var prop in opt.Properties)
 593        {
 15594            var comma = propIndex < opt.Properties.Count - 1 ? "," : "";
 15595            GeneratePropertyInitializer(builder, prop, propIndex, comma);
 15596            propIndex++;
 597        }
 598
 7599        builder.AppendLine("            });");
 7600        builder.AppendLine("        });");
 601
 602        // For validation with factory pattern, we need to register the validator separately
 7603        if (opt.ValidateOnStart)
 604        {
 1605            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 606        }
 7607    }
 608
 609    private static void GeneratePositionalRecordBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssembl
 610    {
 5611        var typeName = opt.TypeName;
 5612        var recordInfo = opt.PositionalRecordInfo!.Value;
 613
 614        // Use AddSingleton with IOptions<T> factory pattern for positional records
 5615        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 5616        builder.AppendLine("        {");
 5617        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 5618        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 619
 620        // Generate parsing variables for each constructor parameter
 5621        var paramIndex = 0;
 36622        foreach (var param in recordInfo.Parameters)
 623        {
 13624            GenerateParameterParseVariable(builder, param, paramIndex);
 13625            paramIndex++;
 626        }
 627
 628        // Create record with constructor
 5629        builder.Append($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}(");
 630
 5631        paramIndex = 0;
 36632        foreach (var param in recordInfo.Parameters)
 633        {
 21634            if (paramIndex > 0) builder.Append(", ");
 13635            builder.Append($"p{paramIndex}");
 13636            paramIndex++;
 637        }
 638
 5639        builder.AppendLine("));");
 5640        builder.AppendLine("        });");
 641
 642        // For validation with factory pattern, we need to register the validator separately
 5643        if (opt.ValidateOnStart)
 644        {
 0645            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 646        }
 5647    }
 648
 649    private static void GeneratePropertyParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 650    {
 15651        var varName = $"p{index}";
 15652        var typeName = prop.TypeName;
 15653        var baseTypeName = GetBaseTypeName(typeName);
 654
 655        // Handle complex types
 15656        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 657        {
 2658            GenerateComplexTypeParseVariable(builder, prop, index);
 2659            return;
 660        }
 661
 662        // Handle enums
 13663        if (prop.IsEnum && prop.EnumTypeName != null)
 664        {
 0665            var defaultVal = prop.IsNullable ? "null" : "default";
 0666            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && global::Syste
 0667            return;
 668        }
 669
 670        // Handle primitives
 13671        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 672        {
 7673            var defaultVal = prop.IsNullable ? "null" : "\"\"";
 7674            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] ?? {defaultVal};");
 675        }
 6676        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 677        {
 5678            var defaultVal = prop.IsNullable ? "null" : "0";
 5679            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && int.TryParse(
 680        }
 1681        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 682        {
 1683            var defaultVal = prop.IsNullable ? "null" : "false";
 1684            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && bool.TryParse
 685        }
 0686        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 687        {
 0688            var defaultVal = prop.IsNullable ? "null" : "0.0";
 0689            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && double.TryPar
 690        }
 691        else
 692        {
 693            // Default to default value for unsupported types
 0694            builder.AppendLine($"            var {varName} = default({typeName}); // Unsupported type");
 695        }
 0696    }
 697
 698    private static void GenerateComplexTypeParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 699    {
 2700        var varName = $"p{index}";
 2701        var sectionVar = $"sec{index}";
 702
 2703        switch (prop.ComplexTypeKind)
 704        {
 705            case ComplexTypeKind.NestedObject:
 1706                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 1707                builder.AppendLine($"            var {varName} = new {GetNonNullableTypeName(prop.TypeName)}();");
 1708                if (prop.NestedProperties != null)
 709                {
 1710                    var nestedIndex = index * 100;
 6711                    foreach (var nested in prop.NestedProperties)
 712                    {
 2713                        GenerateNestedPropertyAssignment(builder, nested, nestedIndex, varName, sectionVar);
 2714                        nestedIndex++;
 715                    }
 716                }
 717                break;
 718
 719            case ComplexTypeKind.List:
 1720                var listElemType = prop.ElementTypeName ?? "string";
 1721                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 1722                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.List<{listElemTy
 1723                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 1724                builder.AppendLine("            {");
 1725                if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 726                {
 0727                    builder.AppendLine($"                var item = new {listElemType}();");
 0728                    var ni = index * 100;
 0729                    foreach (var np in prop.NestedProperties)
 730                    {
 0731                        GenerateChildPropertyAssignment(builder, np, ni, "item", "child");
 0732                        ni++;
 733                    }
 0734                    builder.AppendLine($"                {varName}.Add(item);");
 735                }
 736                else
 737                {
 1738                    builder.AppendLine($"                if (child.Value is {{ }} val) {varName}.Add(val);");
 739                }
 1740                builder.AppendLine("            }");
 1741                break;
 742
 743            case ComplexTypeKind.Dictionary:
 0744                var dictValType = prop.ElementTypeName ?? "string";
 0745                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 0746                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.Dictionary<strin
 0747                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 0748                builder.AppendLine("            {");
 0749                if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 750                {
 0751                    builder.AppendLine($"                var item = new {dictValType}();");
 0752                    var ni = index * 100;
 0753                    foreach (var np in prop.NestedProperties)
 754                    {
 0755                        GenerateChildPropertyAssignment(builder, np, ni, "item", "child");
 0756                        ni++;
 757                    }
 0758                    builder.AppendLine($"                {varName}[child.Key] = item;");
 759                }
 0760                else if (dictValType == "int" || dictValType == "global::System.Int32")
 761                {
 0762                    builder.AppendLine($"                if (child.Value is {{ }} val && int.TryParse(val, out var iv)) 
 763                }
 764                else
 765                {
 0766                    builder.AppendLine($"                if (child.Value is {{ }} val) {varName}[child.Key] = val;");
 767                }
 0768                builder.AppendLine("            }");
 0769                break;
 770
 771            default:
 0772                builder.AppendLine($"            var {varName} = default({prop.TypeName}); // Complex type");
 773                break;
 774        }
 1775    }
 776
 777    private static void GenerateNestedPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, str
 778    {
 2779        var varName = $"nv{index}";
 2780        var baseTypeName = GetBaseTypeName(prop.TypeName);
 781
 2782        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 783        {
 1784            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}) {targetVar}.{prop.Nam
 785        }
 1786        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 787        {
 1788            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && int.TryParse({varNa
 789        }
 0790        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 791        {
 0792            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && bool.TryParse({varN
 793        }
 0794    }
 795
 796    private static void GenerateChildPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, stri
 797    {
 0798        var varName = $"cv{index}";
 0799        var baseTypeName = GetBaseTypeName(prop.TypeName);
 800
 0801        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 802        {
 0803            builder.AppendLine($"                if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}) {targetVar}.{prop
 804        }
 0805        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 806        {
 0807            builder.AppendLine($"                if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && int.TryParse({v
 808        }
 0809    }
 810
 811    private static void GeneratePropertyInitializer(StringBuilder builder, OptionsPropertyInfo prop, int index, string c
 812    {
 15813        builder.AppendLine($"                {prop.Name} = p{index}{comma}");
 15814    }
 815
 816    private static void GenerateParameterParseVariable(StringBuilder builder, PositionalRecordParameter param, int index
 817    {
 13818        var varName = $"p{index}";
 13819        var typeName = param.TypeName;
 13820        var baseTypeName = GetBaseTypeName(typeName);
 821
 822        // Check if it's an enum
 823        // For simplicity, check if it's a known primitive, otherwise assume it could be an enum
 13824        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 825        {
 5826            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] ?? \"\";");
 827        }
 8828        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 829        {
 4830            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && int.TryParse
 831        }
 4832        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 833        {
 3834            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && bool.TryPars
 835        }
 1836        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 837        {
 0838            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && double.TryPa
 839        }
 840        else
 841        {
 842            // Try enum parsing for other types
 1843            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && global::Syst
 844        }
 1845    }
 846
 847    private static void RegisterValidator(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, HashSet
 848    {
 75849        var typeName = opt.TypeName;
 75850        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 851
 852        // Register DataAnnotations validator if present
 75853        if (opt.HasDataAnnotations)
 854        {
 17855            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 17856            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 857        }
 858
 75859        if (opt.ValidateOnStart && opt.HasValidatorMethod)
 860        {
 4861            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 4862            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 863
 4864            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 865            {
 1866                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 867            }
 868        }
 75869    }
 870
 871    private static void RegisterValidatorForFactory(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyNam
 872    {
 1873        var typeName = opt.TypeName;
 1874        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 875
 876        // For factory pattern, we need to add OptionsBuilder validation manually
 877        // Since we're using AddSingleton<IOptions<T>>, we also need to register for IOptionsSnapshot and IOptionsMonito
 1878        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptionsSnapshot<{typeNa
 879
 880        // Add startup validation
 1881        builder.AppendLine($"        services.AddOptions<{typeName}>().ValidateOnStart();");
 882
 883        // Register DataAnnotations validator if present
 1884        if (opt.HasDataAnnotations)
 885        {
 0886            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 0887            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 888        }
 889
 1890        if (opt.HasValidatorMethod)
 891        {
 1892            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 1893            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 894
 1895            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 896            {
 0897                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 898            }
 899        }
 1900    }
 901
 902    private static void GeneratePropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 903    {
 904        // Handle complex types first
 120905        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 906        {
 18907            GenerateComplexTypeBinding(builder, prop, index, targetPath);
 18908            return;
 909        }
 910
 102911        var varName = $"v{index}";
 912
 913        // Determine how to parse the value based on type
 102914        var typeName = prop.TypeName;
 102915        var baseTypeName = GetBaseTypeName(typeName);
 916
 102917        builder.Append($"                if (section[\"{prop.Name}\"] is {{ }} {varName}");
 918
 919        // Check if it's an enum first
 102920        if (prop.IsEnum && prop.EnumTypeName != null)
 921        {
 12922            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var p{index}
 923        }
 90924        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 925        {
 926            // String: direct assignment
 50927            builder.AppendLine($") {targetPath}.{prop.Name} = {varName};");
 928        }
 40929        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 930        {
 23931            builder.AppendLine($" && int.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 932        }
 17933        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 934        {
 5935            builder.AppendLine($" && bool.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 936        }
 12937        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 938        {
 5939            builder.AppendLine($" && double.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globalizat
 940        }
 7941        else if (baseTypeName == "float" || baseTypeName == "global::System.Single")
 942        {
 0943            builder.AppendLine($" && float.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globalizati
 944        }
 7945        else if (baseTypeName == "decimal" || baseTypeName == "global::System.Decimal")
 946        {
 0947            builder.AppendLine($" && decimal.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globaliza
 948        }
 7949        else if (baseTypeName == "long" || baseTypeName == "global::System.Int64")
 950        {
 0951            builder.AppendLine($" && long.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 952        }
 7953        else if (baseTypeName == "short" || baseTypeName == "global::System.Int16")
 954        {
 0955            builder.AppendLine($" && short.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};")
 956        }
 7957        else if (baseTypeName == "byte" || baseTypeName == "global::System.Byte")
 958        {
 0959            builder.AppendLine($" && byte.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 960        }
 7961        else if (baseTypeName == "char" || baseTypeName == "global::System.Char")
 962        {
 0963            builder.AppendLine($" && {varName}.Length == 1) {targetPath}.{prop.Name} = {varName}[0];");
 964        }
 7965        else if (baseTypeName == "global::System.TimeSpan")
 966        {
 0967            builder.AppendLine($" && global::System.TimeSpan.TryParse({varName}, out var p{index})) {targetPath}.{prop.N
 968        }
 7969        else if (baseTypeName == "global::System.DateTime")
 970        {
 0971            builder.AppendLine($" && global::System.DateTime.TryParse({varName}, out var p{index})) {targetPath}.{prop.N
 972        }
 7973        else if (baseTypeName == "global::System.DateTimeOffset")
 974        {
 0975            builder.AppendLine($" && global::System.DateTimeOffset.TryParse({varName}, out var p{index})) {targetPath}.{
 976        }
 7977        else if (baseTypeName == "global::System.Guid")
 978        {
 0979            builder.AppendLine($" && global::System.Guid.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name}
 980        }
 7981        else if (baseTypeName == "global::System.Uri")
 982        {
 0983            builder.AppendLine($" && global::System.Uri.TryCreate({varName}, global::System.UriKind.RelativeOrAbsolute, 
 984        }
 985        else
 986        {
 987            // Unsupported type - skip silently (matching ConfigurationBinder behavior)
 7988            builder.AppendLine($") {{ }} // Skipped: {typeName} (not a supported primitive)");
 989        }
 7990    }
 991
 992    private static void GenerateComplexTypeBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string ta
 993    {
 18994        var sectionVar = $"sec{index}";
 995
 18996        switch (prop.ComplexTypeKind)
 997        {
 998            case ComplexTypeKind.NestedObject:
 6999                GenerateNestedObjectBinding(builder, prop, index, targetPath, sectionVar);
 61000                break;
 1001
 1002            case ComplexTypeKind.Array:
 31003                GenerateArrayBinding(builder, prop, index, targetPath, sectionVar);
 31004                break;
 1005
 1006            case ComplexTypeKind.List:
 51007                GenerateListBinding(builder, prop, index, targetPath, sectionVar);
 51008                break;
 1009
 1010            case ComplexTypeKind.Dictionary:
 41011                GenerateDictionaryBinding(builder, prop, index, targetPath, sectionVar);
 1012                break;
 1013        }
 41014    }
 1015
 1016    private static void GenerateNestedObjectBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string t
 1017    {
 61018        var nestedPath = $"{targetPath}.{prop.Name}";
 1019
 61020        builder.AppendLine($"                // Bind nested object: {prop.Name}");
 61021        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 1022
 1023        // Initialize if null (for nullable properties)
 61024        if (prop.IsNullable)
 1025        {
 11026            builder.AppendLine($"                {nestedPath} ??= new {GetNonNullableTypeName(prop.TypeName)}();");
 1027        }
 1028
 1029        // Generate bindings for nested properties
 61030        if (prop.NestedProperties != null)
 1031        {
 61032            var nestedIndex = index * 100; // Use offset to avoid variable name collisions
 301033            foreach (var nestedProp in prop.NestedProperties)
 1034            {
 1035                // Temporarily swap section context for nested binding
 91036                GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, nestedPath, sectionVar);
 91037                nestedIndex++;
 1038            }
 1039        }
 61040    }
 1041
 1042    private static void GenerateNestedPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 1043    {
 1044        // Handle complex types recursively
 101045        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 1046        {
 21047            var innerSectionVar = $"sec{index}";
 21048            switch (prop.ComplexTypeKind)
 1049            {
 1050                case ComplexTypeKind.NestedObject:
 11051                    builder.AppendLine($"                // Bind nested object: {prop.Name}");
 11052                    builder.AppendLine($"                var {innerSectionVar} = {sectionVarName}.GetSection(\"{prop.Nam
 11053                    if (prop.IsNullable)
 1054                    {
 01055                        builder.AppendLine($"                {targetPath}.{prop.Name} ??= new {GetNonNullableTypeName(pr
 1056                    }
 11057                    if (prop.NestedProperties != null)
 1058                    {
 11059                        var nestedIndex = index * 100;
 41060                        foreach (var nestedProp in prop.NestedProperties)
 1061                        {
 11062                            GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, $"{targetPath}.{prop.Name}",
 11063                            nestedIndex++;
 1064                        }
 1065                    }
 1066                    break;
 1067
 1068                case ComplexTypeKind.Array:
 1069                case ComplexTypeKind.List:
 1070                case ComplexTypeKind.Dictionary:
 1071                    // For collections inside nested objects, generate appropriate binding
 11072                    GenerateCollectionBindingInNested(builder, prop, index, targetPath, sectionVarName);
 1073                    break;
 1074            }
 21075            return;
 1076        }
 1077
 1078        // Generate primitive binding using the nested section
 81079        var varName = $"v{index}";
 81080        var baseTypeName = GetBaseTypeName(prop.TypeName);
 1081
 81082        builder.Append($"                if ({sectionVarName}[\"{prop.Name}\"] is {{ }} {varName}");
 1083
 81084        if (prop.IsEnum && prop.EnumTypeName != null)
 1085        {
 01086            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var p{index}
 1087        }
 81088        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1089        {
 61090            builder.AppendLine($") {targetPath}.{prop.Name} = {varName};");
 1091        }
 21092        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1093        {
 21094            builder.AppendLine($" && int.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 1095        }
 01096        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1097        {
 01098            builder.AppendLine($" && bool.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 1099        }
 1100        else
 1101        {
 1102            // For other types, generate appropriate TryParse
 01103            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 1104        }
 01105    }
 1106
 1107    private static void GenerateCollectionBindingInNested(StringBuilder builder, OptionsPropertyInfo prop, int index, st
 1108    {
 11109        var collectionSection = $"colSec{index}";
 11110        builder.AppendLine($"                var {collectionSection} = {sectionVarName}.GetSection(\"{prop.Name}\");");
 1111
 11112        switch (prop.ComplexTypeKind)
 1113        {
 1114            case ComplexTypeKind.List:
 11115                GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 11116                break;
 1117            case ComplexTypeKind.Array:
 01118                GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 01119                break;
 1120            case ComplexTypeKind.Dictionary:
 01121                GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 1122                break;
 1123        }
 01124    }
 1125
 1126    private static void GenerateArrayBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPa
 1127    {
 31128        builder.AppendLine($"                // Bind array: {prop.Name}");
 31129        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 31130        GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 31131    }
 1132
 1133    private static void GenerateArrayBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targ
 1134    {
 31135        var itemsVar = $"items{index}";
 31136        var elementType = prop.ElementTypeName ?? "string";
 31137        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 1138
 31139        builder.AppendLine($"                var {itemsVar} = new global::System.Collections.Generic.List<{elementType}>
 31140        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 31141        builder.AppendLine("                {");
 1142
 31143        if (hasNestedProps)
 1144        {
 1145            // Complex element type
 11146            var itemVar = $"item{index}";
 11147            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 11148            var nestedIndex = index * 100;
 61149            foreach (var nestedProp in prop.NestedProperties!)
 1150            {
 21151                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 21152                nestedIndex++;
 1153            }
 11154            builder.AppendLine($"                    {itemsVar}.Add({itemVar});");
 1155        }
 1156        else
 1157        {
 1158            // Primitive element type
 21159            GeneratePrimitiveCollectionAdd(builder, elementType, index, itemsVar);
 1160        }
 1161
 31162        builder.AppendLine("                }");
 31163        builder.AppendLine($"                {targetPath} = {itemsVar}.ToArray();");
 31164    }
 1165
 1166    private static void GenerateListBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPat
 1167    {
 51168        builder.AppendLine($"                // Bind list: {prop.Name}");
 51169        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 51170        GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 51171    }
 1172
 1173    private static void GenerateListBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 1174    {
 61175        var elementType = prop.ElementTypeName ?? "string";
 61176        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 1177
 61178        builder.AppendLine($"                {targetPath}.Clear();");
 61179        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 61180        builder.AppendLine("                {");
 1181
 61182        if (hasNestedProps)
 1183        {
 1184            // Complex element type
 11185            var itemVar = $"item{index}";
 11186            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 11187            var nestedIndex = index * 100;
 61188            foreach (var nestedProp in prop.NestedProperties!)
 1189            {
 21190                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 21191                nestedIndex++;
 1192            }
 11193            builder.AppendLine($"                    {targetPath}.Add({itemVar});");
 1194        }
 1195        else
 1196        {
 1197            // Primitive element type
 51198            GeneratePrimitiveListAdd(builder, elementType, index, targetPath);
 1199        }
 1200
 61201        builder.AppendLine("                }");
 61202    }
 1203
 1204    private static void GenerateDictionaryBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string tar
 1205    {
 41206        builder.AppendLine($"                // Bind dictionary: {prop.Name}");
 41207        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 41208        GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 41209    }
 1210
 1211    private static void GenerateDictionaryBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 1212    {
 41213        var elementType = prop.ElementTypeName ?? "string";
 41214        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 1215
 41216        builder.AppendLine($"                {targetPath}.Clear();");
 41217        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 41218        builder.AppendLine("                {");
 1219
 41220        if (hasNestedProps)
 1221        {
 1222            // Complex value type
 11223            var itemVar = $"item{index}";
 11224            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 11225            var nestedIndex = index * 100;
 61226            foreach (var nestedProp in prop.NestedProperties!)
 1227            {
 21228                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 21229                nestedIndex++;
 1230            }
 11231            builder.AppendLine($"                    {targetPath}[child.Key] = {itemVar};");
 1232        }
 1233        else
 1234        {
 1235            // Primitive value type
 31236            GeneratePrimitiveDictionaryAdd(builder, elementType, index, targetPath);
 1237        }
 1238
 41239        builder.AppendLine("                }");
 41240    }
 1241
 1242    private static void GenerateChildPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string 
 1243    {
 61244        var varName = $"cv{index}";
 61245        var baseTypeName = GetBaseTypeName(prop.TypeName);
 1246
 61247        builder.Append($"                    if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}");
 1248
 61249        if (prop.IsEnum && prop.EnumTypeName != null)
 1250        {
 01251            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var cp{index
 1252        }
 61253        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1254        {
 31255            builder.AppendLine($") {targetVar}.{prop.Name} = {varName};");
 1256        }
 31257        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1258        {
 31259            builder.AppendLine($" && int.TryParse({varName}, out var cp{index})) {targetVar}.{prop.Name} = cp{index};");
 1260        }
 01261        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1262        {
 01263            builder.AppendLine($" && bool.TryParse({varName}, out var cp{index})) {targetVar}.{prop.Name} = cp{index};")
 1264        }
 1265        else
 1266        {
 01267            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 1268        }
 01269    }
 1270
 1271    private static void GeneratePrimitiveCollectionAdd(StringBuilder builder, string elementType, int index, string list
 1272    {
 21273        var baseType = GetBaseTypeName(elementType);
 1274
 21275        if (baseType == "string" || baseType == "global::System.String")
 1276        {
 11277            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {listVar}.Add(val{index});");
 1278        }
 11279        else if (baseType == "int" || baseType == "global::System.Int32")
 1280        {
 11281            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 1282        }
 1283        else
 1284        {
 01285            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 1286        }
 01287    }
 1288
 1289    private static void GeneratePrimitiveListAdd(StringBuilder builder, string elementType, int index, string targetPath
 1290    {
 51291        var baseType = GetBaseTypeName(elementType);
 1292
 51293        if (baseType == "string" || baseType == "global::System.String")
 1294        {
 41295            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {targetPath}.Add(val{index});"
 1296        }
 11297        else if (baseType == "int" || baseType == "global::System.Int32")
 1298        {
 11299            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 1300        }
 1301        else
 1302        {
 01303            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 1304        }
 01305    }
 1306
 1307    private static void GeneratePrimitiveDictionaryAdd(StringBuilder builder, string elementType, int index, string targ
 1308    {
 31309        var baseType = GetBaseTypeName(elementType);
 1310
 31311        if (baseType == "string" || baseType == "global::System.String")
 1312        {
 11313            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {targetPath}[child.Key] = val{
 1314        }
 21315        else if (baseType == "int" || baseType == "global::System.Int32")
 1316        {
 21317            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 1318        }
 1319        else
 1320        {
 01321            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 1322        }
 01323    }
 1324
 1325    private static string GetNonNullableTypeName(string typeName)
 1326    {
 21327        if (typeName.EndsWith("?"))
 01328            return typeName.Substring(0, typeName.Length - 1);
 21329        return typeName;
 1330    }
 1331
 1332    private static string GetBaseTypeName(string typeName)
 1333    {
 1334        // Handle nullable types like "global::System.Nullable<int>" or "int?"
 1561335        if (typeName.StartsWith("global::System.Nullable<") && typeName.EndsWith(">"))
 1336        {
 01337            return typeName.Substring("global::System.Nullable<".Length, typeName.Length - "global::System.Nullable<".Le
 1338        }
 1561339        if (typeName.EndsWith("?"))
 1340        {
 41341            return typeName.Substring(0, typeName.Length - 1);
 1342        }
 1521343        return typeName;
 1344    }
 1345}
 1346

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(System.String)
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)
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)
GenerateParameterParseVariable(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.PositionalRecordParameter,System.Int32)
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)
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)