Add remark not found warnings

This commit is contained in:
DHR60 2026-02-27 10:39:09 +08:00
parent 07a5619e60
commit 50d8d99336
9 changed files with 172 additions and 16 deletions

View file

@ -1,4 +1,4 @@
namespace ServiceLib.Handler.Builder; namespace ServiceLib.Handler.Builder;
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult) public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
{ {
@ -44,9 +44,17 @@ public class CoreConfigContextBuilder
var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet);
foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag))) foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag)))
{ {
if (ruleItem.OutboundTag.IsNullOrEmpty())
{
validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleEmptyOutboundTag, ruleItem.Remarks));
ruleItem.OutboundTag = Global.ProxyTag;
continue;
}
var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag); var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag);
if (ruleOutboundNode == null) if (ruleOutboundNode == null)
{ {
validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleOutboundNodeNotFound, ruleItem.Remarks, ruleItem.OutboundTag));
ruleItem.OutboundTag = Global.ProxyTag;
continue; continue;
} }
@ -83,43 +91,71 @@ public class CoreConfigContextBuilder
if (includeSubChain) if (includeSubChain)
{ {
var virtualChainNode = await BuildSubscriptionChainNodeAsync(node); var (virtualChainNode, chainValidatorResult) = await BuildSubscriptionChainNodeAsync(node);
if (virtualChainNode != null) if (virtualChainNode != null)
{ {
context.AllProxiesMap[virtualChainNode.IndexId] = virtualChainNode; context.AllProxiesMap[virtualChainNode.IndexId] = virtualChainNode;
return await ResolveNodeAsync(context, virtualChainNode, false); var (resolvedNode, resolvedResult) = await ResolveNodeAsync(context, virtualChainNode, false);
resolvedResult.Warnings.InsertRange(0, chainValidatorResult.Warnings);
return (resolvedNode, resolvedResult);
}
// Chain not built but warnings may still exist (e.g. missing profiles)
if (chainValidatorResult.Warnings.Count > 0)
{
var fillResult = await RegisterNodeAsync(context, node);
fillResult.Warnings.InsertRange(0, chainValidatorResult.Warnings);
return (node, fillResult);
} }
} }
var fillResult = await RegisterNodeAsync(context, node); var registerResult = await RegisterNodeAsync(context, node);
return (node, fillResult); return (node, registerResult);
} }
/// <summary> /// <summary>
/// If the node's subscription defines prev/next profiles, creates a virtual /// If the node's subscription defines prev/next profiles, creates a virtual
/// <see cref="EConfigType.ProxyChain"/> node that wraps them together. /// <see cref="EConfigType.ProxyChain"/> node that wraps them together.
/// Returns <c>null</c> when no chain is needed. /// Returns <c>null</c> as the chain item when no chain is needed.
/// Any warnings (e.g. missing prev/next profile) are returned in the validator result.
/// </summary> /// </summary>
private static async Task<ProfileItem?> BuildSubscriptionChainNodeAsync(ProfileItem node) private static async Task<(ProfileItem? ChainNode, NodeValidatorResult ValidatorResult)> BuildSubscriptionChainNodeAsync(ProfileItem node)
{ {
var result = NodeValidatorResult.Empty();
if (node.Subid.IsNullOrEmpty()) if (node.Subid.IsNullOrEmpty())
{ {
return null; return (null, result);
} }
var subItem = await AppManager.Instance.GetSubItem(node.Subid); var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem == null if (subItem == null)
|| (subItem.PrevProfile.IsNullOrEmpty()
&& subItem.NextProfile.IsNullOrEmpty()))
{ {
return null; return (null, result);
}
ProfileItem? prevNode = null;
ProfileItem? nextNode = null;
if (!subItem.PrevProfile.IsNullOrEmpty())
{
prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode == null)
{
result.Warnings.Add(string.Format(ResUI.MsgSubscriptionPrevProfileNotFound, subItem.PrevProfile));
}
}
if (!subItem.NextProfile.IsNullOrEmpty())
{
nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode == null)
{
result.Warnings.Add(string.Format(ResUI.MsgSubscriptionNextProfileNotFound, subItem.NextProfile));
}
} }
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (prevNode is null && nextNode is null) if (prevNode is null && nextNode is null)
{ {
return null; return (null, result);
} }
// Build new proxy chain node // Build new proxy chain node
@ -136,7 +172,7 @@ public class CoreConfigContextBuilder
ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())), ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())),
}; };
chainNode.SetProtocolExtra(chainExtraItem); chainNode.SetProtocolExtra(chainExtraItem);
return chainNode; return (chainNode, result);
} }
/// <summary> /// <summary>

View file

@ -2040,6 +2040,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Routing rule {0} has an empty outbound tag. Fallback to proxy node only. 的本地化字符串。
/// </summary>
public static string MsgRoutingRuleEmptyOutboundTag {
get {
return ResourceManager.GetString("MsgRoutingRuleEmptyOutboundTag", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. 的本地化字符串。 /// 查找类似 Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. 的本地化字符串。
/// </summary> /// </summary>
@ -2049,6 +2058,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Routing rule {0} outbound node {1} not found. Fallback to proxy node only. 的本地化字符串。
/// </summary>
public static string MsgRoutingRuleOutboundNodeNotFound {
get {
return ResourceManager.GetString("MsgRoutingRuleOutboundNodeNotFound", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Routing rule {0} outbound node {1} warning: {2} 的本地化字符串。 /// 查找类似 Routing rule {0} outbound node {1} warning: {2} 的本地化字符串。
/// </summary> /// </summary>
@ -2112,6 +2130,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Subscription next proxy {0} not found. Skipping. 的本地化字符串。
/// </summary>
public static string MsgSubscriptionNextProfileNotFound {
get {
return ResourceManager.GetString("MsgSubscriptionNextProfileNotFound", resourceCulture);
}
}
/// <summary>
/// 查找类似 Subscription previous proxy {0} not found. Skipping. 的本地化字符串。
/// </summary>
public static string MsgSubscriptionPrevProfileNotFound {
get {
return ResourceManager.GetString("MsgSubscriptionPrevProfileNotFound", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Unpacking... 的本地化字符串。 /// 查找类似 Unpacking... 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1674,4 +1674,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>Group {0} has no valid child node.</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
</root> </root>

View file

@ -1671,4 +1671,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>Group {0} has no valid child node.</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
</root> </root>

View file

@ -1674,4 +1674,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>Group {0} has no valid child node.</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
</root> </root>

View file

@ -1674,4 +1674,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>Group {0} has no valid child node.</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
</root> </root>

View file

@ -1674,4 +1674,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>Group {0} has no valid child node.</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
</root> </root>

View file

@ -1671,4 +1671,16 @@
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>节点组 {0} 下没有有效的子节点。</value> <value>节点组 {0} 下没有有效的子节点。</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>路由规则 {0} 的出站标签为空,已回退为仅使用代理节点。</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>路由规则 {0} 的出站节点 {1} 未找到,已回退为仅使用代理节点。</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>订阅前置节点 {0} 未找到,已跳过。</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>订阅后置节点 {0} 未找到,已跳过。</value>
</data>
</root> </root>

View file

@ -1671,4 +1671,16 @@
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>Group {0} has no valid child node.</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
</root> </root>