Files
dnsmasq-webui/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/Validation/NftsetSemanticHandler.cs
T
alex 36a160dfc7 Refactor: option semantics to metadata + handlers, shared syntax, registry/save single path
- OptionSemantics: metadata only (no validator delegates); OptionValidationKind + PathPolicy
- IOptionSemanticHandler: handler-first validation; handlers registered via IApplicationMultiSingleton
- OptionSemanticValidator: single engine, handler dispatch then generic kind-based validation
- Shared syntax: DnsmasqScopedDomainSyntax, DnsmasqDhcpRangeValueParser, DnsmasqDhcpOptionSyntax,
  DnsmasqRelaySyntax, DnsmasqDhcpTagSyntax, DnsmasqIpPrefixSyntax for reuse across handlers
- New handlers: Server, Local, RevServer, Address, Alias, Ipset, Nftset, DhcpRange, DhcpHost,
  DhcpOption, DhcpRelay, DhcpProxy, IgnoreAddress, ConnmarkAllowlist, DhcpMatch, DhcpMac,
  PxeService, TrustAnchor, RebindDomainOk, BogusNxdomain, DhcpIgnoreNames, DhcpBoot, Slaac,
  BridgeInterface, SharedNetwork, DhcpOptionPxe, DhcpNameMatch, DhcpIgnore, DhcpVendorclass, DhcpUserclass, TagIf
- Registry and pre-save validation use engine only; placeholders via DnsmasqOptionPlaceholders
- DnsmasqConfigSetService uses DnsmasqDhcpRangeValueParser; DhcpRangeValueParserTests added
2026-03-10 23:44:51 +10:00

56 lines
1.8 KiB
C#

using DnsmasqWebUI.Infrastructure.Helpers.Config;
namespace DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Validation;
/// <summary>
/// Specialized semantic behavior for <c>nftset</c> values.
/// Validates scoped domain syntax followed by one or more nftables set specifications.
/// </summary>
public sealed class NftsetSemanticHandler : IOptionSemanticHandler
{
public bool CanHandle(string optionName) =>
optionName == DnsmasqConfKeys.Nftset;
public string? ValidateSingle(object? value) => null;
public string? ValidateMultiItem(string? value)
{
var s = value?.Trim() ?? "";
if (s.Length == 0)
return "Value cannot be empty.";
if (!DnsmasqScopedDomainSyntax.TrySplitScopedValue(s, out var domains, out var specList, out var error))
return error == "Value must start with '/'."
? "nftset must start with '/'."
: "nftset must use /domain[/domain...]/set-spec[,set-spec...] syntax.";
error = DnsmasqScopedDomainSyntax.ValidateDomainPatterns(domains);
if (error is not null)
return error.Replace("Value", "nftset", StringComparison.Ordinal);
var specs = specList.Split(',').Select(t => t.Trim()).ToArray();
if (specs.Length == 0 || specs.Any(string.IsNullOrWhiteSpace))
return "nftset must include at least one non-empty set specification.";
foreach (var spec in specs)
{
if (!IsValidSetSpec(spec))
return $"Invalid nftset specification '{spec}'.";
}
return null;
}
private static bool IsValidSetSpec(string spec)
{
var parts = spec.Split('#');
if (parts.Any(p => p.Length == 0))
return false;
if (parts[0] is "4" or "6")
return parts.Length is 3 or 4;
return parts.Length == 3;
}
}