diff --git a/packages/service/ni_measurement_plugin_sdk_service/_internal/parameter/metadata.py b/packages/service/ni_measurement_plugin_sdk_service/_internal/parameter/metadata.py index 1abdadeaf..39ab664d5 100644 --- a/packages/service/ni_measurement_plugin_sdk_service/_internal/parameter/metadata.py +++ b/packages/service/ni_measurement_plugin_sdk_service/_internal/parameter/metadata.py @@ -23,6 +23,8 @@ SupportedEnumType = Union[Type[Enum], _EnumTypeWrapper] +_VALID_CHARS = set(" ().,;:!?-_'+") + class ParameterMetadata(NamedTuple): """Class that represents the metadata of parameters.""" @@ -58,6 +60,7 @@ class ParameterMetadata(NamedTuple): @staticmethod def initialize( + validate_type: bool, display_name: str, type: type_pb2.Field.Kind.ValueType, repeated: bool, @@ -67,14 +70,16 @@ def initialize( enum_type: Optional[SupportedEnumType] = None, ) -> "ParameterMetadata": """Initialize ParameterMetadata with field_name.""" + _validate_display_name(display_name) underscore_display_name = display_name.replace(" ", "_") + if all(char.isalnum() or char == "_" for char in underscore_display_name): field_name = underscore_display_name else: field_name = "".join( char for char in underscore_display_name if char.isalnum() or char == "_" ) - return ParameterMetadata( + parameter_metadata = ParameterMetadata( display_name, type, repeated, @@ -84,9 +89,26 @@ def initialize( field_name, enum_type, ) + if validate_type: + _validate_default_value_type(parameter_metadata) + return parameter_metadata + + +def _validate_display_name(display_name: str) -> None: + """Validate and raise exception if 'display_name' has invalid characters. + + Raises: + ValueError: If display_name has invalid characters. + """ + if not display_name: + raise ValueError("The display name cannot be an empty string.") + elif not display_name[0].isalpha(): + raise ValueError(f"The first character in display name '{display_name}' must be a letter.") + elif not all(char in _VALID_CHARS or char.isalnum() for char in display_name): + raise ValueError(f"There are invalid characters in display name '{display_name}'.") -def validate_default_value_type(parameter_metadata: ParameterMetadata) -> None: +def _validate_default_value_type(parameter_metadata: ParameterMetadata) -> None: """Validate and raise exception if the default value does not match the type info. Args: diff --git a/packages/service/ni_measurement_plugin_sdk_service/measurement/service.py b/packages/service/ni_measurement_plugin_sdk_service/measurement/service.py index 578200dcb..7574be1b5 100644 --- a/packages/service/ni_measurement_plugin_sdk_service/measurement/service.py +++ b/packages/service/ni_measurement_plugin_sdk_service/measurement/service.py @@ -410,6 +410,7 @@ def configuration( data_type_info.type_specialization, instrument_type=instrument_type, enum_type=enum_type ) parameter = parameter_metadata.ParameterMetadata.initialize( + True, display_name, data_type_info.grpc_field_type, data_type_info.repeated, @@ -418,7 +419,6 @@ def configuration( data_type_info.message_type, enum_type, ) - parameter_metadata.validate_default_value_type(parameter) self._configuration_parameter_list.append(parameter) def _configuration(func: _F) -> _F: @@ -471,6 +471,7 @@ def output( data_type_info.type_specialization, enum_type=enum_type ) parameter = parameter_metadata.ParameterMetadata.initialize( + False, display_name, data_type_info.grpc_field_type, data_type_info.repeated, diff --git a/packages/service/tests/unit/test_decoder.py b/packages/service/tests/unit/test_decoder.py index 9a69ae8e9..3c902329f 100644 --- a/packages/service/tests/unit/test_decoder.py +++ b/packages/service/tests/unit/test_decoder.py @@ -176,13 +176,15 @@ def _get_grpc_serialized_data(values): def _get_test_parameter_by_id(default_values): parameter_by_id = { 1: ParameterMetadata.initialize( - display_name="float_data!", + validate_type=False, + display_name="float_data", type=type_pb2.Field.TYPE_FLOAT, repeated=False, default_value=default_values[0], annotations={}, ), 2: ParameterMetadata.initialize( + validate_type=False, display_name="double_data", type=type_pb2.Field.TYPE_DOUBLE, repeated=False, @@ -190,6 +192,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 3: ParameterMetadata.initialize( + validate_type=False, display_name="int32_data", type=type_pb2.Field.TYPE_INT32, repeated=False, @@ -197,6 +200,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 4: ParameterMetadata.initialize( + validate_type=False, display_name="uint32_data", type=type_pb2.Field.TYPE_INT64, repeated=False, @@ -204,6 +208,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 5: ParameterMetadata.initialize( + validate_type=False, display_name="int64_data", type=type_pb2.Field.TYPE_UINT32, repeated=False, @@ -211,6 +216,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 6: ParameterMetadata.initialize( + validate_type=False, display_name="uint64_data", type=type_pb2.Field.TYPE_UINT64, repeated=False, @@ -218,6 +224,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 7: ParameterMetadata.initialize( + validate_type=False, display_name="bool_data", type=type_pb2.Field.TYPE_BOOL, repeated=False, @@ -225,6 +232,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 8: ParameterMetadata.initialize( + validate_type=False, display_name="string_data", type=type_pb2.Field.TYPE_STRING, repeated=False, @@ -232,6 +240,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 9: ParameterMetadata.initialize( + validate_type=False, display_name="double_array_data", type=type_pb2.Field.TYPE_DOUBLE, repeated=True, @@ -239,6 +248,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 10: ParameterMetadata.initialize( + validate_type=False, display_name="float_array_data", type=type_pb2.Field.TYPE_FLOAT, repeated=True, @@ -246,6 +256,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 11: ParameterMetadata.initialize( + validate_type=False, display_name="int32_array_data", type=type_pb2.Field.TYPE_INT32, repeated=True, @@ -253,6 +264,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 12: ParameterMetadata.initialize( + validate_type=False, display_name="uint32_array_data", type=type_pb2.Field.TYPE_UINT32, repeated=True, @@ -260,6 +272,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 13: ParameterMetadata.initialize( + validate_type=False, display_name="int64_array_data", type=type_pb2.Field.TYPE_INT64, repeated=True, @@ -267,6 +280,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 14: ParameterMetadata.initialize( + validate_type=False, display_name="uint64_array_data", type=type_pb2.Field.TYPE_UINT64, repeated=True, @@ -274,6 +288,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 15: ParameterMetadata.initialize( + validate_type=False, display_name="bool_array_data", type=type_pb2.Field.TYPE_BOOL, repeated=True, @@ -281,6 +296,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 16: ParameterMetadata.initialize( + validate_type=False, display_name="string_array_data", type=type_pb2.Field.TYPE_STRING, repeated=True, @@ -288,6 +304,7 @@ def _get_test_parameter_by_id(default_values): annotations={}, ), 17: ParameterMetadata.initialize( + validate_type=False, display_name="enum_data", type=type_pb2.Field.TYPE_ENUM, repeated=False, @@ -299,6 +316,7 @@ def _get_test_parameter_by_id(default_values): enum_type=DifferentColor, ), 18: ParameterMetadata.initialize( + validate_type=False, display_name="enum_array_data", type=type_pb2.Field.TYPE_ENUM, repeated=True, @@ -310,6 +328,7 @@ def _get_test_parameter_by_id(default_values): enum_type=DifferentColor, ), 19: ParameterMetadata.initialize( + validate_type=False, display_name="int_enum_data", type=type_pb2.Field.TYPE_ENUM, repeated=False, @@ -321,6 +340,7 @@ def _get_test_parameter_by_id(default_values): enum_type=Countries, ), 20: ParameterMetadata.initialize( + validate_type=False, display_name="int_enum_array_data", type=type_pb2.Field.TYPE_ENUM, repeated=True, @@ -332,6 +352,7 @@ def _get_test_parameter_by_id(default_values): enum_type=Countries, ), 21: ParameterMetadata.initialize( + validate_type=False, display_name="xy_data", type=type_pb2.Field.TYPE_MESSAGE, repeated=False, @@ -340,6 +361,7 @@ def _get_test_parameter_by_id(default_values): message_type=xydata_pb2.DoubleXYData.DESCRIPTOR.full_name, ), 22: ParameterMetadata.initialize( + validate_type=False, display_name="xy_data_array", type=type_pb2.Field.TYPE_MESSAGE, repeated=True, @@ -383,6 +405,7 @@ def _get_big_message_metadata_by_id() -> Dict[int, ParameterMetadata]: return { i + 1: ParameterMetadata.initialize( + validate_type=False, display_name=f"field{i + 1}", type=type_pb2.Field.TYPE_DOUBLE, repeated=False, diff --git a/packages/service/tests/unit/test_metadata.py b/packages/service/tests/unit/test_metadata.py index a4f0c5398..e5ca94dd0 100644 --- a/packages/service/tests/unit/test_metadata.py +++ b/packages/service/tests/unit/test_metadata.py @@ -10,7 +10,10 @@ TYPE_SPECIALIZATION_KEY, ) from ni_measurement_plugin_sdk_service._internal.parameter import metadata -from ni_measurement_plugin_sdk_service.measurement.info import DataType, TypeSpecialization +from ni_measurement_plugin_sdk_service.measurement.info import ( + DataType, + TypeSpecialization, +) class Color(Enum): @@ -90,7 +93,7 @@ def test___default_value_different_from_type___validate___raises_type_exception( ) with pytest.raises(TypeError): - metadata.validate_default_value_type(parameter_metadata) + metadata._validate_default_value_type(parameter_metadata) @pytest.mark.parametrize( @@ -147,4 +150,31 @@ def test___default_value_same_as_type___validate___raises_no_exception( annotations, ) - metadata.validate_default_value_type(parameter_metadata) # implicitly assert does not throw + metadata._validate_default_value_type(parameter_metadata) # implicitly assert does not throw + + +@pytest.mark.parametrize( + "display_name", + [ + " test_string", + "_____()!", + "test@string", + "", + ], +) +def test___display_name_invalid_characters___validate___raises_value_exception(display_name): + with pytest.raises(ValueError): + metadata._validate_display_name(display_name) + + +@pytest.mark.parametrize( + "display_name", + [ + "teststring()", + "tEsT StRIng?", + "test_string!", + "Test String: -10", + ], +) +def test___display_name_valid_characters___validate___raises_no_exception(display_name): + metadata._validate_display_name(display_name) diff --git a/packages/service/tests/unit/test_service.py b/packages/service/tests/unit/test_service.py index b62235f2f..46450ddd4 100644 --- a/packages/service/tests/unit/test_service.py +++ b/packages/service/tests/unit/test_service.py @@ -10,8 +10,13 @@ from ni_measurement_plugin_sdk_service import _datatypeinfo from ni_measurement_plugin_sdk_service._annotations import TYPE_SPECIALIZATION_KEY -from ni_measurement_plugin_sdk_service._internal.stubs.ni.protobuf.types import xydata_pb2 -from ni_measurement_plugin_sdk_service.measurement.info import DataType, TypeSpecialization +from ni_measurement_plugin_sdk_service._internal.stubs.ni.protobuf.types import ( + xydata_pb2, +) +from ni_measurement_plugin_sdk_service.measurement.info import ( + DataType, + TypeSpecialization, +) from ni_measurement_plugin_sdk_service.measurement.service import MeasurementService