Skip to content

Commit e1d01c2

Browse files
ajitpratap0Ajit Pratap Singhclaude
authored
feat: comprehensive architecture improvements for error handling, testing, and configuration (#133)
* feat: comprehensive architecture improvements for error handling and testing This PR implements a major architectural improvement initiative focused on: ## Structured Error Codes System - Tokenizer: Updated all errors to use structured error codes (E1001-E1005) - Parser: Updated 50+ errors across 8 files to use structured errors (E2001-E2012) - All errors now include: error codes, location info, helpful hints, and doc links ## CLI Enhancement - Added `Code` field to JSON output for validation and parse errors - Errors now propagate structured error codes to CLI output ## LSP Integration - Updated `createDiagnosticFromError` to extract and display error codes - VS Code Problems panel now shows structured error codes ## Unified Configuration Package - Created `pkg/config/` with Config struct, file/env loaders, LSP integration - Test coverage: 78.6% ## Testing Improvements - Added `pkg/lsp/handler_test.go` (660 lines) - Added `pkg/lsp/server_ratelimit_test.go` (400 lines) - Added `pkg/lsp/handler_errorcode_test.go` - Added `pkg/sql/security/scanner_bench_test.go` (16 benchmarks) - Added `pkg/lsp/handler_bench_test.go` (21+ benchmarks) ## Documentation Updates - Updated CLI_GUIDE.md, API_REFERENCE.md, ERROR_CODES.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: address PR #133 review feedback - Fix SA4010 lint error in LoadFromFiles() by renaming unused 'errs' to 'triedPaths' and including tried paths in the error message for better debugging - Add documentation comment to mergeInto() explaining the known limitation where boolean fields only merge when true, preventing explicit false override via environment variables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * docs: update issue reference to #134 in config mergeInto comment * fix: remove unused error type check in fuzz test (SA4006) * fix: use *bool pointers for config boolean fields (closes #134) This fixes the boolean merging logic issue where boolean fields could not be explicitly set to false via environment variables to override config file values. Changes: - Change all boolean config fields from `bool` to `*bool` pointers: - Format.UppercaseKeywords, Format.Compact - Validation.StrictMode, Validation.Recursive - Output.Verbose - Analyze.Security, Analyze.Performance, Analyze.Complexity, Analyze.All - Server.MetricsEnabled - Add helper functions in config.go: - Bool(v bool) *bool - creates a pointer to a bool - BoolValue(p *bool) bool - returns false if nil - BoolValueOr(p *bool, defaultVal bool) bool - returns default if nil - Update mergeInto() to use != nil checks instead of truthy checks, allowing explicit false values to override defaults - Update LoadFromEnvironment() to use Bool() helper - Update ToLSPSettings/FromLSPSettings to use BoolValue()/Bool() - Update all tests to use BoolValue() for assertions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * test: add integration tests for error propagation chain Add comprehensive integration tests that verify: - Error codes propagate correctly from tokenizer to parser - Error code extraction works with IsCode() helper - Error location information is preserved through parsing pipeline Tests cover various SQL error scenarios: - Unterminated string literals (tokenizer error) - Incomplete SQL statements - Missing table names (INSERT INTO VALUES) - Unexpected token usage (SELECT FROM users) - Multiline error location tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Ajit Pratap Singh <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 139ad0a commit e1d01c2

34 files changed

+5672
-134
lines changed

cmd/gosqlx/internal/output/json.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8+
goerrors "github.com/ajitpratap0/GoSQLX/pkg/errors"
89
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
910
)
1011

@@ -37,6 +38,7 @@ type JSONValidationResults struct {
3738
type JSONValidationError struct {
3839
File string `json:"file"`
3940
Message string `json:"message"`
41+
Code string `json:"code,omitempty"`
4042
Type string `json:"type"` // "tokenization", "parsing", "syntax", "io"
4143
}
4244

@@ -97,6 +99,7 @@ type JSONPosition struct {
9799
// JSONParseError represents a parsing error
98100
type JSONParseError struct {
99101
Message string `json:"message"`
102+
Code string `json:"code,omitempty"`
100103
Type string `json:"type"` // "tokenization", "parsing", "io"
101104
Position *JSONPosition `json:"position,omitempty"`
102105
}
@@ -130,10 +133,12 @@ func FormatValidationJSON(result *ValidationResult, inputFiles []string, include
130133
// Add errors
131134
for _, fileResult := range result.Files {
132135
if fileResult.Error != nil {
136+
errCode := extractErrorCode(fileResult.Error)
133137
output.Errors = append(output.Errors, JSONValidationError{
134138
File: fileResult.Path,
135139
Message: fileResult.Error.Error(),
136-
Type: categorizeError(fileResult.Error.Error()),
140+
Code: errCode,
141+
Type: categorizeByCode(errCode, fileResult.Error.Error()),
137142
})
138143
}
139144
}
@@ -219,6 +224,7 @@ func FormatParseJSON(astObj *ast.AST, inputSource string, showTokens bool, token
219224

220225
// FormatParseErrorJSON creates a JSON error output for parse failures
221226
func FormatParseErrorJSON(err error, inputSource string) ([]byte, error) {
227+
errCode := extractErrorCode(err)
222228
output := &JSONParseOutput{
223229
Command: "parse",
224230
Input: JSONInputInfo{
@@ -229,7 +235,8 @@ func FormatParseErrorJSON(err error, inputSource string) ([]byte, error) {
229235
Status: "error",
230236
Error: &JSONParseError{
231237
Message: err.Error(),
232-
Type: categorizeError(err.Error()),
238+
Code: errCode,
239+
Type: categorizeByCode(errCode, err.Error()),
233240
},
234241
}
235242

@@ -267,6 +274,34 @@ func determineStatus(result *ValidationResult) string {
267274
return "no_files"
268275
}
269276

277+
// extractErrorCode extracts the error code from an error
278+
func extractErrorCode(err error) string {
279+
if err == nil {
280+
return ""
281+
}
282+
if code, ok := goerrors.ExtractErrorCode(err); ok {
283+
return string(code)
284+
}
285+
return ""
286+
}
287+
288+
// categorizeByCode categorizes errors by error code if available
289+
func categorizeByCode(code, errMsg string) string {
290+
if code != "" {
291+
switch {
292+
case strings.HasPrefix(code, "E1"):
293+
return "tokenization"
294+
case strings.HasPrefix(code, "E2"):
295+
return "parsing"
296+
case strings.HasPrefix(code, "E3"):
297+
return "semantic"
298+
case strings.HasPrefix(code, "E4"):
299+
return "unsupported"
300+
}
301+
}
302+
return categorizeError(errMsg)
303+
}
304+
270305
// categorizeError categorizes error messages by type
271306
func categorizeError(errorMsg string) string {
272307
errorLower := errorMsg

docs/API_REFERENCE.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,36 @@ if err := gosqlx.Validate("SELECT * FROM users"); err != nil {
196196

197197
---
198198

199+
#### `ValidateMultiple(queries []string) error`
200+
201+
Validates multiple SQL queries in a batch operation.
202+
203+
```go
204+
queries := []string{
205+
"SELECT * FROM users",
206+
"INSERT INTO logs (msg) VALUES ('test')",
207+
"UPDATE users SET name = 'John' WHERE id = 1",
208+
}
209+
if err := gosqlx.ValidateMultiple(queries); err != nil {
210+
log.Fatal("Validation failed:", err)
211+
}
212+
```
213+
214+
**Parameters:**
215+
- `queries`: A slice of SQL query strings to validate
216+
217+
**Returns:**
218+
- `error`: First validation error encountered, or nil if all queries are valid
219+
220+
**Benefits:**
221+
- Reuses tokenizer and parser objects across queries
222+
- More efficient than calling `Validate()` individually
223+
- Ideal for batch validation scenarios
224+
225+
**Use Case:** Validating multiple SQL queries efficiently
226+
227+
---
228+
199229
### Metadata Extraction
200230

201231
#### `ExtractTables(astNode *ast.AST) []string`

docs/CLI_GUIDE.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,55 @@ gosqlx parse -f tree complex_query.sql
267267
gosqlx parse -f json query.sql > ast.json
268268
```
269269

270+
### `gosqlx watch`
271+
Monitor SQL files for changes and validate/format in real-time.
272+
273+
```bash
274+
# Watch current directory for SQL file changes
275+
gosqlx watch
276+
277+
# Watch specific directory with validation
278+
gosqlx watch ./queries --validate
279+
280+
# Watch with formatting on save
281+
gosqlx watch ./queries --format
282+
283+
# Watch with custom pattern
284+
gosqlx watch ./queries --pattern "*.sql"
285+
```
286+
287+
**Options:**
288+
- `--validate`: Run validation on file changes
289+
- `--format`: Auto-format files on save
290+
- `--pattern PATTERN`: File pattern to watch (default: "*.sql")
291+
292+
**Use Case:** Real-time SQL development with automatic validation/formatting
293+
294+
### `gosqlx lint`
295+
Check SQL files for style issues and best practices.
296+
297+
```bash
298+
# Lint SQL files
299+
gosqlx lint query.sql
300+
301+
# Lint with specific rules
302+
gosqlx lint --rules L001,L002,L005 query.sql
303+
304+
# Lint directory recursively
305+
gosqlx lint -r ./queries
306+
```
307+
308+
**Available lint rules:**
309+
- L001: Missing semicolon at end of statement
310+
- L002: Inconsistent keyword casing
311+
- L005: Unused table alias
312+
313+
**Options:**
314+
- `--rules RULES`: Comma-separated list of rule codes to check
315+
- `-r, --recursive`: Recursively process directories
316+
317+
**Use Case:** Enforce SQL coding standards and best practices
318+
270319
## Global Flags
271320

272321
- `-v, --verbose`: Enable verbose output

0 commit comments

Comments
 (0)