using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.DeviceForms; using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Forms; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers; /// /// DriverType → typed driver-config form component, rendered by DriverConfigModal through /// <DynamicComponent>. /// Why this is a map and not the @switch it replaced. A Razor @switch compiles /// into BuildRenderTree's IL, so no test can enumerate its cases — which is exactly how the modal /// came to be missing a Calculation arm while the sibling driver-picker guard stayed green /// (deferment.md G-1/G-4). The picker test only works because RawDriverTypeDialog keeps its /// data in a field the markup enumerates; this type gives the two config modals the same property, and /// being public it needs none of that test's BindingFlags.NonPublic fragility. /// Modelled on TagConfigEditorMap. Guarded by DriverFormMapParityTests. /// public static class DriverConfigFormMap { private static readonly IReadOnlyDictionary Map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [DriverTypeNames.Modbus] = typeof(ModbusDriverForm), [DriverTypeNames.S7] = typeof(S7DriverForm), [DriverTypeNames.AbCip] = typeof(AbCipDriverForm), [DriverTypeNames.AbLegacy] = typeof(AbLegacyDriverForm), [DriverTypeNames.TwinCAT] = typeof(TwinCATDriverForm), [DriverTypeNames.FOCAS] = typeof(FocasDriverForm), [DriverTypeNames.OpcUaClient] = typeof(OpcUaClientDriverForm), [DriverTypeNames.Galaxy] = typeof(GalaxyDriverForm), [DriverTypeNames.Sql] = typeof(SqlDriverForm), [DriverTypeNames.Mqtt] = typeof(MqttDriverForm), [DriverTypeNames.MTConnect] = typeof(MTConnectDriverForm), [DriverTypeNames.Calculation] = typeof(CalculationDriverForm), }; /// The driver types that have a typed config form. public static IReadOnlyCollection MappedDriverTypes => (IReadOnlyCollection)Map.Keys; /// Resolves the form component for a driver type, or null when none is mapped (the modal /// then renders its "no typed config form" warning rather than crashing). /// The driver type name. /// The form component type, or . public static Type? Resolve(string? driverType) => driverType is not null && Map.TryGetValue(driverType, out var t) ? t : null; } /// /// DriverType → typed device-config form component, rendered by DeviceModal. /// Not every driver has one, by design. The types in /// hold ONE connection per driver instance and author it on the /// driver form, so a per-device endpoint editor would be meaningless. The parity test consults that set /// rather than demanding total coverage — which keeps it a real guard instead of one that has to be /// suppressed. /// public static class DeviceFormMap { private static readonly IReadOnlyDictionary Map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [DriverTypeNames.Modbus] = typeof(ModbusDeviceForm), [DriverTypeNames.S7] = typeof(S7DeviceForm), [DriverTypeNames.AbCip] = typeof(AbCipDeviceForm), [DriverTypeNames.AbLegacy] = typeof(AbLegacyDeviceForm), [DriverTypeNames.TwinCAT] = typeof(TwinCATDeviceForm), [DriverTypeNames.FOCAS] = typeof(FocasDeviceForm), [DriverTypeNames.OpcUaClient] = typeof(OpcUaClientDeviceForm), [DriverTypeNames.Galaxy] = typeof(GalaxyDeviceForm), [DriverTypeNames.Mqtt] = typeof(MqttDeviceForm), }; /// /// Driver types that hold a single connection at the DRIVER level, so they legitimately have no /// per-device form. Galaxy and Mqtt author a connection on the driver form and keep an informational /// device form; Sql (one connection string), MTConnect (one Agent URI) and Calculation (no /// connection at all) have no device form to render. /// Read by DriverConfigModal so the operator is told where the endpoint actually lives — /// it previously hardcoded Galaxy-or-Mqtt and told Sql/MTConnect authors to go and find an endpoint /// field on the device that does not exist (deferment.md G-3). /// public static readonly IReadOnlySet SingleConnectionDriverTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { DriverTypeNames.Galaxy, DriverTypeNames.Mqtt, DriverTypeNames.Sql, DriverTypeNames.MTConnect, DriverTypeNames.Calculation, }; /// The driver types that have a typed device form. public static IReadOnlyCollection MappedDriverTypes => (IReadOnlyCollection)Map.Keys; /// Resolves the device form component for a driver type, or null when none is mapped. /// The driver type name. /// The form component type, or . public static Type? Resolve(string? driverType) => driverType is not null && Map.TryGetValue(driverType, out var t) ? t : null; /// True when this driver authors its connection on the DRIVER form rather than per-device. /// The driver type name. /// when the connection is driver-level. public static bool IsSingleConnection(string? driverType) => driverType is not null && SingleConnectionDriverTypes.Contains(driverType); }