|
| 1 | +# Enum Utilities in Proto Parser |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The `generateEnumValueFunctions` function is a key utility in the proto-parser package that generates TypeScript code for bidirectional enum value lookups. It creates a runtime utility function that can convert between enum string names and their numeric values. |
| 6 | + |
| 7 | +## Function Location |
| 8 | + |
| 9 | +- **File**: `src/ast/enums/enums.ts` |
| 10 | +- **Export**: Named export from the enums module |
| 11 | + |
| 12 | +## Function Signature |
| 13 | + |
| 14 | +```typescript |
| 15 | +export const generateEnumValueFunctions = (enumData: Enum[]) => { |
| 16 | + // Returns an array of two AST nodes: |
| 17 | + // 1. EnumType type alias export |
| 18 | + // 2. getEnumValue function export |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +## Purpose |
| 23 | + |
| 24 | +The function generates a TypeScript utility that provides: |
| 25 | + |
| 26 | +1. **Bidirectional enum lookups**: Convert enum names to values and values to names |
| 27 | +2. **Type safety**: Uses a union type of all enum names for compile-time checking |
| 28 | +3. **Runtime validation**: Throws descriptive errors for invalid inputs |
| 29 | + |
| 30 | +## Generated Code Structure |
| 31 | + |
| 32 | +### 1. EnumType Type Alias |
| 33 | + |
| 34 | +```typescript |
| 35 | +export type EnumType = "Enum1" | "Enum2" | "Enum3" | ...; |
| 36 | +``` |
| 37 | + |
| 38 | +A union type of all enum names in the protobuf schema, providing type safety when calling the utility function. |
| 39 | + |
| 40 | +### 2. getEnumValue Function |
| 41 | + |
| 42 | +```typescript |
| 43 | +export const getEnumValue = (enumType: EnumType, key: string | number) => { |
| 44 | + switch (enumType) { |
| 45 | + case "EnumName": |
| 46 | + { |
| 47 | + switch (key) { |
| 48 | + case "ENUM_VALUE_1": return 0; |
| 49 | + case "ENUM_VALUE_2": return 1; |
| 50 | + case 0: return "ENUM_VALUE_1"; |
| 51 | + case 1: return "ENUM_VALUE_2"; |
| 52 | + default: throw new Error("Key not recognized in enum EnumName"); |
| 53 | + } |
| 54 | + } |
| 55 | + // ... more enum cases |
| 56 | + default: throw new Error("Enum type not recognized"); |
| 57 | + } |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +## Implementation Details |
| 62 | + |
| 63 | +### AST Generation Process |
| 64 | + |
| 65 | +1. **Type Union Creation**: |
| 66 | + - Creates a union type of all enum names as string literals |
| 67 | + - Exported as `EnumType` for type-safe enum selection |
| 68 | + |
| 69 | +2. **Function Structure**: |
| 70 | + - Uses nested switch statements for performance |
| 71 | + - Outer switch: Selects the enum by name |
| 72 | + - Inner switch: Performs the bidirectional lookup |
| 73 | + |
| 74 | +3. **Bidirectional Mapping**: |
| 75 | + - String → Number: `case "ENUM_VALUE": return numericValue;` |
| 76 | + - Number → String: `case numericValue: return "ENUM_VALUE";` |
| 77 | + |
| 78 | +4. **Error Handling**: |
| 79 | + - Throws specific errors for unrecognized enum types |
| 80 | + - Throws specific errors for unrecognized keys within an enum |
| 81 | + |
| 82 | +### Code Generation Using Babel AST |
| 83 | + |
| 84 | +The function uses Babel's AST builders to construct the code: |
| 85 | + |
| 86 | +```typescript |
| 87 | +// Type alias creation |
| 88 | +const enumTypeUnion = t.tsUnionType( |
| 89 | + enumData.map(enumDef => t.tsLiteralType(t.stringLiteral(enumDef.name))) |
| 90 | +); |
| 91 | + |
| 92 | +// Function parameter with type annotation |
| 93 | +const enumTypeParam = t.identifier('enumType'); |
| 94 | +enumTypeParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(enumTypeIdentifier)); |
| 95 | + |
| 96 | +// Switch case generation |
| 97 | +const outerCases = enumData.map(enumDef => { |
| 98 | + const innerCases = Object.entries(enumDef.values).map(([key, value]) => { |
| 99 | + // Forward mapping (string to number) |
| 100 | + return t.switchCase(t.stringLiteral(key), [t.returnStatement(t.numericLiteral(value))]); |
| 101 | + }); |
| 102 | + |
| 103 | + // Reverse mapping (number to string) |
| 104 | + innerCases.push(...Object.entries(enumDef.values).map(([key, value]) => { |
| 105 | + return t.switchCase(t.numericLiteral(value), [t.returnStatement(t.stringLiteral(key))]); |
| 106 | + })); |
| 107 | + |
| 108 | + // ... error handling |
| 109 | +}); |
| 110 | +``` |
| 111 | + |
| 112 | +## Usage Configuration |
| 113 | + |
| 114 | +The enum utilities are controlled by the following configuration: |
| 115 | + |
| 116 | +```typescript |
| 117 | +utils: { |
| 118 | + enums: { |
| 119 | + enabled: boolean; // Default: false |
| 120 | + filename: string; // Default: 'utils.ts' |
| 121 | + } |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +### Enabling Enum Utilities |
| 126 | + |
| 127 | +```typescript |
| 128 | +const options = { |
| 129 | + utils: { |
| 130 | + enums: { |
| 131 | + enabled: true, |
| 132 | + filename: 'enum-utils.ts' |
| 133 | + } |
| 134 | + } |
| 135 | +}; |
| 136 | +``` |
| 137 | + |
| 138 | +## Generated File Example |
| 139 | + |
| 140 | +When enabled, the function generates a file (default: `utils.ts`) containing: |
| 141 | + |
| 142 | +```typescript |
| 143 | +/** |
| 144 | +* This file was automatically generated by pg-proto-parser@latest. |
| 145 | +* DO NOT MODIFY IT BY HAND. Instead, modify the source proto file, |
| 146 | +* and run the pg-proto-parser generate command to regenerate this file. |
| 147 | +*/ |
| 148 | + |
| 149 | +export type EnumType = "OverridingKind" | "QuerySource" | "SortByDir" | ...; |
| 150 | + |
| 151 | +export const getEnumValue = (enumType: EnumType, key: string | number) => { |
| 152 | + switch (enumType) { |
| 153 | + case "OverridingKind": |
| 154 | + { |
| 155 | + switch (key) { |
| 156 | + case "OVERRIDING_NOT_SET": return 0; |
| 157 | + case "OVERRIDING_USER_VALUE": return 1; |
| 158 | + case "OVERRIDING_SYSTEM_VALUE": return 2; |
| 159 | + case 0: return "OVERRIDING_NOT_SET"; |
| 160 | + case 1: return "OVERRIDING_USER_VALUE"; |
| 161 | + case 2: return "OVERRIDING_SYSTEM_VALUE"; |
| 162 | + default: throw new Error("Key not recognized in enum OverridingKind"); |
| 163 | + } |
| 164 | + } |
| 165 | + // ... more cases |
| 166 | + } |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +## Use Cases |
| 171 | + |
| 172 | +### 1. Dynamic Enum Value Resolution |
| 173 | + |
| 174 | +```typescript |
| 175 | +// Get numeric value from string |
| 176 | +const value = getEnumValue("OverridingKind", "OVERRIDING_USER_VALUE"); // Returns: 1 |
| 177 | + |
| 178 | +// Get string from numeric value |
| 179 | +const name = getEnumValue("OverridingKind", 1); // Returns: "OVERRIDING_USER_VALUE" |
| 180 | +``` |
| 181 | + |
| 182 | +### 2. Type-Safe Enum Operations |
| 183 | + |
| 184 | +```typescript |
| 185 | +// TypeScript will enforce valid enum names |
| 186 | +getEnumValue("InvalidEnum", "VALUE"); // Type error! |
| 187 | + |
| 188 | +// Runtime validation for values |
| 189 | +getEnumValue("OverridingKind", "INVALID_VALUE"); // Throws: "Key not recognized in enum OverridingKind" |
| 190 | +``` |
| 191 | + |
| 192 | +### 3. Protocol Message Parsing |
| 193 | + |
| 194 | +Useful when parsing protocol buffer messages where you need to convert between wire format (numbers) and readable format (strings). |
| 195 | + |
| 196 | +## Performance Considerations |
| 197 | + |
| 198 | +1. **Switch Statement Optimization**: Modern JavaScript engines optimize switch statements well, making this approach efficient |
| 199 | +2. **No Object Allocation**: The function doesn't create intermediate objects, reducing memory pressure |
| 200 | +3. **Direct Returns**: Each case immediately returns, avoiding unnecessary computation |
| 201 | + |
| 202 | +## Integration with Other Features |
| 203 | + |
| 204 | +The enum utilities work alongside: |
| 205 | + |
| 206 | +1. **Enum Declarations**: Regular TypeScript enum exports (`enums.ts`) |
| 207 | +2. **Enum Maps**: JSON/TypeScript enum mapping files (`enumMap` feature) |
| 208 | +3. **Type Definitions**: Interface definitions that use the enums |
| 209 | + |
| 210 | +## Best Practices |
| 211 | + |
| 212 | +1. **Enable When Needed**: Only enable if you need runtime enum conversions |
| 213 | +2. **Import Selectively**: Import only the `getEnumValue` function where needed |
| 214 | +3. **Type Safety**: Always use the `EnumType` type for the first parameter |
| 215 | +4. **Error Handling**: Wrap calls in try-catch if handling untrusted input |
| 216 | + |
| 217 | +## Limitations |
| 218 | + |
| 219 | +1. **File Size**: For schemas with many enums, the generated file can be large (4000+ lines) |
| 220 | +2. **No Partial Generation**: Currently generates utilities for all enums, not selectively |
| 221 | +3. **No Custom Names**: The function is always named `getEnumValue` |
| 222 | + |
| 223 | +## Future Enhancements |
| 224 | + |
| 225 | +Potential improvements could include: |
| 226 | + |
| 227 | +1. **Selective Generation**: Generate utilities only for specified enums |
| 228 | +2. **Multiple Output Files**: Split large utility files |
| 229 | +3. **Custom Function Names**: Allow configuration of the generated function name |
| 230 | +4. **Tree Shaking**: Better support for dead code elimination |
| 231 | +5. **Enum Filtering**: Option to exclude certain enums from utility generation |
0 commit comments