📄 core/src/MessageOptions.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Options;

namespace Cfa;

public sealed class MessageOptions
{
    [Required]
    [ValidateEnumeratedItems]
    public List<MessageRoute> MessageRoutes { get; set; } = [];

    public static MessageOptions LoadJson(string path, bool optional = false)
    {
        var fullPath = Path.GetFullPath(path);
        using var files = new PhysicalFileProvider(
            Path.GetDirectoryName(fullPath) ?? Environment.CurrentDirectory,
            ExclusionFilters.None
        );
        using var configuration = new ConfigurationManager();
        configuration.AddJsonFile(files, Path.GetFileName(fullPath), optional, reloadOnChange: false);
        var services = new ServiceCollection();
        services.AddOptions<MessageOptions>().Bind(configuration).Validate<MessageOptionsValidator>();
        using var provider = services.BuildServiceProvider();
        return provider.GetRequiredService<IOptions<MessageOptions>>().Value;
    }
}

public sealed class MessageRoute
{
    [Required]
    [ValidateObjectMembers]
    public PublisherRule Publisher { get; set; } = new();

    [Required]
#pragma warning disable IL2026 // T[] implements ICollection; MinLength does not need reflection.
    [MinLength(1)]
#pragma warning restore IL2026
    [ValidateEnumeratedItems]
    public RegistrationRule[] Registrations { get; set; } = [];
}

public sealed class PublisherRule
{
    [Required]
    [ValidateObjectMembers]
    public MethodSelector Method { get; set; } = new();

    [Range(0, int.MaxValue)]
    public int MessageArgument { get; set; }
}

public sealed class RegistrationRule
{
    [Required]
    [ValidateObjectMembers]
    public MethodSelector Method { get; set; } = new();

    [Range(0, int.MaxValue)]
    public int MessageTypeArgument { get; set; }

    [Range(0, int.MaxValue)]
    public int HandlerArgument { get; set; }
}

public sealed class MethodSelector
{
    [Required]
    public string Type { get; set; } = "";

    [Required]
    public string Name { get; set; } = "";

    [Range(0, int.MaxValue)]
    public int? GenericArity { get; set; }

    [Range(0, int.MaxValue)]
    public int? ParameterCount { get; set; }
}

[OptionsValidator]
internal partial class MessageOptionsValidator : IValidateOptions<MessageOptions>;