Coverage for nexusLIMS/tui/apps/config/validators.py: 100%
61 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
1"""Field validators for the NexusLIMS configuration TUI.
3Provides validation functions specific to configuration editing, extending
4the common validators with config-domain rules.
5"""
7import zoneinfo
9from nexusLIMS.tui.common.validators import validate_url
12def validate_nemo_address(value: str | None) -> tuple[bool, str]:
13 """
14 Validate a NEMO API address URL (must be a valid URL with trailing slash).
16 Parameters
17 ----------
18 value : str | None
19 NEMO address to validate
21 Returns
22 -------
23 tuple[bool, str]
24 (is_valid, error_message)
25 """
26 if not value or not value.strip():
27 return False, "NEMO address is required"
29 is_valid, error = validate_url(value, "NEMO address")
30 if not is_valid:
31 return False, error
33 if not value.rstrip().endswith("/"):
34 return False, "NEMO address must end with a trailing slash ('/')"
36 return True, ""
39def validate_optional_url(
40 value: str | None, field_name: str = "URL"
41) -> tuple[bool, str]:
42 """
43 Validate an optional HTTP(S) URL (empty value is accepted).
45 Parameters
46 ----------
47 value : str | None
48 URL to validate
49 field_name : str
50 Human-readable field name for error messages
52 Returns
53 -------
54 tuple[bool, str]
55 (is_valid, error_message)
56 """
57 if not value or not value.strip():
58 return True, ""
59 return validate_url(value, field_name)
62def validate_optional_iana_timezone(value: str | None) -> tuple[bool, str]:
63 """
64 Validate an optional IANA timezone string (empty value is accepted).
66 Parameters
67 ----------
68 value : str | None
69 Timezone string to validate (e.g., "America/New_York")
71 Returns
72 -------
73 tuple[bool, str]
74 (is_valid, error_message)
75 """
76 if not value or not value.strip():
77 return True, ""
79 try:
80 zoneinfo.ZoneInfo(value)
81 return True, ""
82 except (zoneinfo.ZoneInfoNotFoundError, KeyError):
83 return (
84 False,
85 f'Unknown timezone "{value}". Use IANA format (e.g., America/New_York)',
86 )
89def validate_float_positive(
90 value: str | None, field_name: str = "Value"
91) -> tuple[bool, str]:
92 """
93 Validate a positive float (> 0).
95 Parameters
96 ----------
97 value : str | None
98 String to validate as a positive float
99 field_name : str
100 Human-readable field name for error messages
102 Returns
103 -------
104 tuple[bool, str]
105 (is_valid, error_message)
106 """
107 if not value or not value.strip():
108 return False, f"{field_name} is required"
109 try:
110 f = float(value)
111 if f <= 0:
112 return False, f"{field_name} must be greater than 0"
113 return True, ""
114 except ValueError:
115 return False, f"{field_name} must be a number"
118def validate_float_nonneg(
119 value: str | None, field_name: str = "Value"
120) -> tuple[bool, str]:
121 """
122 Validate a non-negative float (>= 0).
124 Parameters
125 ----------
126 value : str | None
127 String to validate as a non-negative float
128 field_name : str
129 Human-readable field name for error messages
131 Returns
132 -------
133 tuple[bool, str]
134 (is_valid, error_message)
135 """
136 if not value or not value.strip():
137 return False, f"{field_name} is required"
138 try:
139 f = float(value)
140 if f < 0:
141 return False, f"{field_name} must be 0 or greater"
142 return True, ""
143 except ValueError:
144 return False, f"{field_name} must be a number"
147def validate_optional_int(
148 value: str | None, field_name: str = "Value"
149) -> tuple[bool, str]:
150 """
151 Validate an optional integer (empty value is accepted).
153 Parameters
154 ----------
155 value : str | None
156 String to validate as an integer
157 field_name : str
158 Human-readable field name for error messages
160 Returns
161 -------
162 tuple[bool, str]
163 (is_valid, error_message)
164 """
165 if not value or not value.strip():
166 return True, ""
167 try:
168 int(value)
169 return True, ""
170 except ValueError:
171 return False, f"{field_name} must be an integer"
174def validate_smtp_port(value: str | None) -> tuple[bool, str]:
175 """
176 Validate an SMTP port number.
178 Parameters
179 ----------
180 value : str | None
181 Port number string to validate
183 Returns
184 -------
185 tuple[bool, str]
186 (is_valid, error_message)
187 """
188 if not value or not value.strip():
189 return True, "" # Has a sensible default
190 try:
191 port = int(value)
192 if port < 1 or port > 65535:
193 return False, "SMTP port must be between 1 and 65535"
194 return True, ""
195 except ValueError:
196 return False, "SMTP port must be an integer"