|
18 | 18 |
|
19 | 19 | from __future__ import annotations |
20 | 20 |
|
| 21 | +import re |
21 | 22 | from typing import Any |
22 | 23 |
|
| 24 | +import caseconverter |
23 | 25 | import pandas as pd |
24 | 26 | from graphql import ( |
25 | 27 | GraphQLArgument, |
@@ -171,29 +173,85 @@ def create_allowed_enums( |
171 | 173 | for fqn, row in leaves_df[leaves_df["allowed"].notna()].iterrows(): |
172 | 174 | if allowed := row.get("allowed"): |
173 | 175 | enum_name = f"{convert_name_for_graphql_schema(fqn, GraphQLElementType.TYPE, S2DM_CONVERSIONS)}_Enum" |
174 | | - values = {_clean_enum_name(str(v)): GraphQLEnumValue(v) for v in allowed} |
| 176 | + |
| 177 | + # Track values and their modifications |
| 178 | + values = {} |
| 179 | + modified_values = {} |
| 180 | + |
| 181 | + for v in allowed: |
| 182 | + sanitized, was_modified = _sanitize_enum_value_for_graphql(str(v)) |
| 183 | + values[sanitized] = GraphQLEnumValue(v) |
| 184 | + |
| 185 | + # Track if value was modified for metadata annotation |
| 186 | + if was_modified: |
| 187 | + modified_values[sanitized] = str(v) |
| 188 | + |
175 | 189 | enums[enum_name] = GraphQLEnumType(enum_name, values, description=f"Allowed values for {fqn}.") |
176 | 190 |
|
177 | 191 | vss_type = row.get("type", "").upper() |
178 | 192 | if vss_type not in VSS_LEAF_TYPES: |
179 | 193 | vss_type = "ATTRIBUTE" |
180 | 194 |
|
181 | | - allowed_values_graphql = {_clean_enum_name(str(v)): str(v).replace('"', "'") for v in allowed} |
| 195 | + allowed_values_graphql = { |
| 196 | + _sanitize_enum_value_for_graphql(str(v))[0]: str(v).replace('"', "'") for v in allowed |
| 197 | + } |
182 | 198 |
|
183 | 199 | metadata[enum_name] = { |
184 | 200 | "fqn": fqn, |
185 | 201 | "vss_type": vss_type, |
186 | 202 | "allowed_values": allowed_values_graphql, |
| 203 | + "modified_values": modified_values, # Store modified values for directive annotations |
187 | 204 | } |
188 | 205 |
|
189 | 206 | return enums, metadata |
190 | 207 |
|
191 | 208 |
|
192 | | -def _clean_enum_name(value: str) -> str: |
193 | | - """Sanitize enum value names for GraphQL.""" |
194 | | - if value[0].isdigit(): |
195 | | - value = f"_{value}" |
196 | | - return value.replace(".", "_DOT_").replace("-", "_DASH_") |
| 209 | +def _sanitize_enum_value_for_graphql(original_value: str) -> tuple[str, bool]: |
| 210 | + """ |
| 211 | + Sanitize enum value for GraphQL schema compliance. |
| 212 | +
|
| 213 | + Converts values with spaces, camelCase, or other invalid characters to valid GraphQL enum values. |
| 214 | + Uses caseconverter to properly handle camelCase word boundaries. |
| 215 | +
|
| 216 | + Examples: |
| 217 | + "some value" -> "SOME_VALUE" |
| 218 | + "SOME VALUE" -> "SOME_VALUE" |
| 219 | + "PbCa" -> "PB_CA" |
| 220 | + "HTTPSConnection" -> "HTTPS_CONNECTION" |
| 221 | + "value-with-dash" -> "VALUE_WITH_DASH" |
| 222 | +
|
| 223 | + Args: |
| 224 | + original_value: The original enum value from VSS |
| 225 | +
|
| 226 | + Returns: |
| 227 | + Tuple of (sanitized_value, was_modified) |
| 228 | + - sanitized_value: Valid GraphQL enum value name |
| 229 | + - was_modified: True if the value was changed, False otherwise |
| 230 | + """ |
| 231 | + |
| 232 | + # Handle empty or whitespace-only strings |
| 233 | + if not original_value or not original_value.strip(): |
| 234 | + raise ValueError(f"Cannot create GraphQL enum value from empty or whitespace-only string: {original_value!r}") |
| 235 | + |
| 236 | + # Replace with underscore all the special characters that are not allowed in GraphQL enum names |
| 237 | + sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", original_value) |
| 238 | + |
| 239 | + # Convert to caseconverter MACRO_CASE (i.e., SCREAMING_SNAKE_CASE) |
| 240 | + if re.search(r"[A-Z]{2,}", sanitized) and re.search(r"[a-z]", sanitized): |
| 241 | + words: list[str] = [] |
| 242 | + for segment in sanitized.split("_"): |
| 243 | + words.extend(re.findall(r"[A-Z]+(?![a-z])|[A-Z]?[a-z]+|[0-9]+", segment)) |
| 244 | + sanitized = "_".join(caseconverter.macrocase(word, strip_punctuation=False) for word in words if word) |
| 245 | + else: |
| 246 | + sanitized = caseconverter.macrocase(sanitized, strip_punctuation=False) |
| 247 | + |
| 248 | + # Handle enum values starting with digits |
| 249 | + sanitized = f"_{sanitized}" if sanitized[0].isdigit() else sanitized |
| 250 | + |
| 251 | + # Check if modification occurred |
| 252 | + was_modified = sanitized != original_value |
| 253 | + |
| 254 | + return sanitized, was_modified |
197 | 255 |
|
198 | 256 |
|
199 | 257 | def create_struct_types( |
|
0 commit comments