Files

73 lines
2.5 KiB
Swift

import Foundation
/// 写入侧窄 seam:设置工作流只依赖已提交快照、当前 runtime 的
/// typed listener facts 与提交入口。生产实现是 `ProxyRuntimeController`
/// 的薄扩展;测试注入 fake。Domain 快照类型只出现在本缝与 adapter。读取侧是
/// 运行时控制器的主 actor 状态,本缝随之主 actor 绑定。
@MainActor
protocol SettingsCommitting: AnyObject {
var committedSettings: ProxyRuntimeSettings { get }
var runtimeListenFacts: RuntimeListenFacts? { get }
var runtimeListenerProcessID: Int32? { get }
func updateListening(_ proposed: ProxyListeningConfiguration) async throws
func updateProxyExceptions(_ value: String) async
}
extension ProxyRuntimeController: SettingsCommitting {
var committedSettings: ProxyRuntimeSettings { settings }
var runtimeListenFacts: RuntimeListenFacts? { effectiveRuntimeListenFacts }
var runtimeListenerProcessID: Int32? { effectiveRuntimeListenerProcessID }
}
/// Settings field adapters keep port IDs, endpoints, and validation facts aligned.
enum SettingsPortAdapter {
/// UI 形状端口标识 → 端点类型(module 内部复用 Domain 端口语义)。
static func endpoint(for id: SettingsPortID, group: SettingsListeningGroup) -> ProxyEndpointKind {
switch (group, id) {
case (.local, .socks): .socks
case (.local, .http): .http
case (.lan, .socks): .lanSocks
case (.lan, .http): .lanHTTP
}
}
/// 全局端口校验保留四个端点的身份,错误只归位到当前编辑组涉及的字段。
static func fieldIssues(
from errors: [PortSettingError], group: SettingsListeningGroup
) -> [SettingsFieldIssue] {
errors.flatMap { error -> [SettingsFieldIssue] in
let endpoints: [ProxyEndpointKind]
switch error {
case .portOutOfRange(let endpoint, _):
endpoints = [endpoint]
case .duplicatePort(let endpoint, let otherEndpoint, _):
endpoints = [endpoint, otherEndpoint]
}
return endpoints.compactMap { endpoint in
guard
let id = SettingsPortID.allCases.first(where: {
Self.endpoint(for: $0, group: group) == endpoint
})
else { return nil }
return .port(id, error: error)
}
}
}
}
extension SettingsPortOccupancy {
/// 探测层判定 → UI 形状事实。
init(_ occupancy: PortOccupancy) {
switch occupancy {
case .free:
self = .free
case .occupied(let facts):
self = .occupied(occupier: facts.occupier)
case .unknown(let detail):
self = .unknown(detail: detail)
}
}
}