#!/usr/bin/env python3 """ Migrate Gin handlers to Echo handlers """ import re import sys def migrate_handler_file(content): """Migrate a single handler file from Gin to Echo""" # 1. Import changes content = re.sub(r'"github\.com/gin-gonic/gin"', '"github.com/labstack/echo/v4"', content) # Add validator import if not present if '"github.com/treytartt/casera-api/internal/validator"' not in content: content = re.sub( r'("github\.com/treytartt/casera-api/internal/services"\n)', r'\1\t"github.com/treytartt/casera-api/internal/validator"\n', content ) # 2. Handler signatures - must return error content = re.sub( r'func \(h \*(\w+Handler)\) (\w+)\(c \*gin\.Context\) {', r'func (h *\1) \2(c echo.Context) error {', content ) # 3. c.MustGet -> c.Get content = re.sub(r'c\.MustGet\(', 'c.Get(', content) # 4. Bind and validate separately # Match ShouldBindJSON pattern and replace with Bind + Validate def replace_bind_validate(match): indent = match.group(1) var_name = match.group(2) error_block = match.group(3) # Replace the error block to use 'return' error_block_fixed = re.sub(r'\n(\t+)c\.JSON\(', r'\n\1return c.JSON(', error_block) error_block_fixed = re.sub(r'\n(\t+)\}\n(\t+)return\n', r'\n\1}\n', error_block_fixed) return (f'{indent}if err := c.Bind(&{var_name}); err != nil {{\n{error_block_fixed}' f'{indent}}}\n' f'{indent}if err := c.Validate(&{var_name}); err != nil {{\n' f'{indent}\treturn c.JSON(http.StatusBadRequest, validator.FormatValidationErrors(err))\n' f'{indent}}}') # Handle ShouldBindJSON with error handling content = re.sub( r'(\t+)if err := c\.ShouldBindJSON\(&(\w+)\); err != nil \{((?:\n(?:\1\t.*|))*\n\1\}\n\1\treturn\n)', replace_bind_validate, content ) # Handle optional ShouldBindJSON (no error check) content = re.sub(r'c\.ShouldBindJSON\(&(\w+)\)', r'c.Bind(&\1)', content) # 5. gin.H -> map[string]interface{} content = re.sub(r'gin\.H\{', 'map[string]interface{}{', content) # 6. c.Query -> c.QueryParam content = re.sub(r'c\.Query\(', 'c.QueryParam(', content) # 7. c.PostForm -> c.FormValue content = re.sub(r'c\.PostForm\(', 'c.FormValue(', content) # 8. c.GetHeader -> c.Request().Header.Get content = re.sub(r'c\.GetHeader\(', 'c.Request().Header.Get(', content) # 9. c.Request.Context() -> c.Request().Context() content = re.sub(r'c\.Request\.Context\(\)', 'c.Request().Context()', content) # 10. All c.JSON, c.Status calls must have 'return' # Match c.JSON without return content = re.sub( r'(\n\t+)c\.JSON\(([^)]+\))', r'\1return c.JSON(\2', content ) # Match c.Status -> c.NoContent content = re.sub( r'(\n\t+)c\.Status\(([^)]+)\)', r'\1return c.NoContent(\2)', content ) # 11. Fix double 'return return' issues content = re.sub(r'return return c\.', 'return c.', content) # 12. Remove standalone 'return' at end of functions (now returns values) # This is tricky - we need to remove lines that are just '\treturn\n}' at function end content = re.sub(r'\n\treturn\n\}$', r'\n}', content, flags=re.MULTILINE) content = re.sub(r'\n(\t+)return\n(\1)\}', r'\n\2}', content) return content if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: migrate_handlers.py ") sys.exit(1) filename = sys.argv[1] with open(filename, 'r') as f: content = f.read() migrated = migrate_handler_file(content) with open(filename, 'w') as f: f.write(migrated) print(f"Migrated {filename}")