fix(templates): hoist (DataType,ElementDataType,Value) attribute validation into TemplateService (#92)
This commit is contained in:
@@ -2,6 +2,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine;
|
||||
|
||||
@@ -266,6 +267,16 @@ public class TemplateService
|
||||
string user,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Defense-in-depth (#92): hoist the (DataType, ElementDataType, Value)
|
||||
// guard — previously only enforced on the CLI/API path in
|
||||
// ManagementActor.ValidateAttributeTypes — into the service so the
|
||||
// Central UI's direct TemplateService write path shares one server-side
|
||||
// check. Mirrors ValidateAttributeTypes exactly, reusing AttributeValueCodec.
|
||||
var typeError = ValidateAttributeTypes(
|
||||
attribute.Name, attribute.DataType, attribute.ElementDataType, attribute.Value);
|
||||
if (typeError != null)
|
||||
return Result<TemplateAttribute>.Failure(typeError);
|
||||
|
||||
var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
|
||||
if (template == null)
|
||||
return Result<TemplateAttribute>.Failure($"Template with ID {templateId} not found.");
|
||||
@@ -312,6 +323,17 @@ public class TemplateService
|
||||
if (existing == null)
|
||||
return Result<TemplateAttribute>.Failure($"Attribute with ID {attributeId} not found.");
|
||||
|
||||
// Defense-in-depth (#92): hoist the (DataType, ElementDataType, Value)
|
||||
// guard out of ManagementActor.ValidateAttributeTypes so the Central UI's
|
||||
// direct TemplateService write path shares one server-side check. DataType
|
||||
// and ElementDataType are fixed by the defining level (not copied from the
|
||||
// proposed attribute below), so the effective persisted triple is the
|
||||
// existing type pairing with the proposed value. Reuses AttributeValueCodec.
|
||||
var typeError = ValidateAttributeTypes(
|
||||
existing.Name, existing.DataType, existing.ElementDataType, proposed.Value);
|
||||
if (typeError != null)
|
||||
return Result<TemplateAttribute>.Failure(typeError);
|
||||
|
||||
// Validate override rules if this is an override (parent has same-named attribute)
|
||||
var template = await _repository.GetTemplateByIdAsync(existing.TemplateId, cancellationToken);
|
||||
if (template?.ParentTemplateId != null)
|
||||
@@ -412,6 +434,52 @@ public class TemplateService
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the (DataType, ElementDataType, Value) triple for an attribute.
|
||||
/// Returns a human-readable failure message (for <c>Result.Failure</c>), or
|
||||
/// <c>null</c> when the triple is valid. Mirrors the CLI/API-path guard in
|
||||
/// <c>ManagementActor.ValidateAttributeTypes</c> so both server write paths
|
||||
/// share one server-side check (#92), reusing <see cref="AttributeValueCodec"/>
|
||||
/// for the element-type and value-decode logic:
|
||||
/// <list type="bullet">
|
||||
/// <item>DataType must be a defined enum value.</item>
|
||||
/// <item>List attributes require a valid scalar element type, and a present
|
||||
/// default value must decode against it.</item>
|
||||
/// <item>Scalar attributes may not carry an element type.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
private static string? ValidateAttributeTypes(
|
||||
string name, DataType dataType, DataType? elementType, string? value)
|
||||
{
|
||||
if (!Enum.IsDefined(dataType))
|
||||
return $"Attribute '{name}' has an unrecognised data type.";
|
||||
|
||||
if (dataType == DataType.List)
|
||||
{
|
||||
if (elementType is null || !AttributeValueCodec.IsValidElementType(elementType.Value))
|
||||
return $"List attribute '{name}' requires a valid element type " +
|
||||
"(String, Int32, Float, Double, Boolean, DateTime).";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
try
|
||||
{
|
||||
AttributeValueCodec.Decode(value, DataType.List, elementType);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
return $"List attribute '{name}' has an invalid list value: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (elementType is not null)
|
||||
{
|
||||
return $"Attribute '{name}': ElementDataType is only valid on List attributes.";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-3: Alarm Definitions
|
||||
// ========================================================================
|
||||
|
||||
Reference in New Issue
Block a user