diff --git a/common.go b/common.go index 9b56e98..956e268 100644 --- a/common.go +++ b/common.go @@ -1,8 +1,6 @@ package fiberoapi import ( - "encoding/json" - "errors" "fmt" "reflect" "strconv" @@ -52,12 +50,33 @@ func parseInput[TInput any](app *OApiApp, c *fiber.Ctx, path string, options *Op // It's OK, the POST has no body - ignore the error } else { // Transform JSON unmarshal type errors into readable validation errors - // Using errors.As for more robust error handling (handles wrapped errors) - var unmarshalErr *json.UnmarshalTypeError - if errors.As(err, &unmarshalErr) { - return input, fmt.Errorf("invalid type for field '%s': expected %s but got %s", - unmarshalErr.Field, unmarshalErr.Type.String(), unmarshalErr.Value) + // Check if error message contains unmarshal type error pattern + errMsg := err.Error() + if strings.Contains(errMsg, "json: cannot unmarshal") && strings.Contains(errMsg, "into Go struct field") { + // Parse the error message to extract field name and type info + // Format: "json: cannot unmarshal into Go struct field . of type " + parts := strings.Split(errMsg, "into Go struct field ") + if len(parts) == 2 { + afterField := parts[1] + fieldParts := strings.Split(afterField, " of type ") + if len(fieldParts) == 2 { + // Extract field name (after the last dot) + fullFieldName := fieldParts[0] + fieldNameParts := strings.Split(fullFieldName, ".") + fieldName := fieldNameParts[len(fieldNameParts)-1] + + // Extract expected type + expectedType := fieldParts[1] + + // Extract actual type from the first part + typePart := strings.TrimPrefix(parts[0], "json: cannot unmarshal ") + + return input, fmt.Errorf("invalid type for field '%s': expected %s but got %s", + fieldName, expectedType, typePart) + } + } } + return input, err } } diff --git a/json_type_error_test.go b/json_type_error_test.go index 5e47be6..38965b4 100644 --- a/json_type_error_test.go +++ b/json_type_error_test.go @@ -18,11 +18,14 @@ import ( // the raw Go error message is not user-friendly: // "json: cannot unmarshal number into Go struct field Request.Description of type string" // -// Solution: Detect json.UnmarshalTypeError and transform it into a readable message: +// Solution: Parse the error message to extract field name and type information, +// then transform it into a readable message: // "invalid type for field 'description': expected string but got number" // -// Implementation: Uses errors.As (not type assertion) to handle wrapped errors correctly. -// This ensures the error detection works even if the error is wrapped by Fiber or middleware. +// Implementation: The error type from c.BodyParser() is *errors.UnmarshalTypeError +// (not *json.UnmarshalTypeError), so we parse the error message string to extract +// the field name, expected type, and actual type. This approach works reliably +// across different Fiber versions and handles all JSON unmarshal type errors. // Test for JSON type mismatch errors func TestJSONTypeMismatchErrors(t *testing.T) { @@ -115,48 +118,6 @@ func TestJSONTypeMismatchErrors(t *testing.T) { } } -// Test that errors.As correctly handles wrapped errors -func TestJSONTypeMismatchWithWrappedError(t *testing.T) { - app := fiber.New() - oapi := New(app) - - type TestRequest struct { - Value string `json:"value"` - } - - type TestResponse struct { - Result string `json:"result"` - } - - Post(oapi, "/test", func(c *fiber.Ctx, input TestRequest) (TestResponse, TestError) { - return TestResponse{Result: "OK"}, TestError{} - }, OpenAPIOptions{}) - - // Test with wrong type - even if the error is wrapped, errors.As should detect it - req := httptest.NewRequest("POST", "/test", strings.NewReader(`{"value": 123}`)) - req.Header.Set("Content-Type", "application/json") - resp, err := app.Test(req) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - if resp.StatusCode != 400 { - t.Errorf("Expected status 400, got %d", resp.StatusCode) - } - - body, _ := io.ReadAll(resp.Body) - bodyStr := string(body) - - // Should contain our custom error message - if !strings.Contains(bodyStr, "invalid type for field 'value'") { - t.Errorf("Expected 'invalid type for field' in error message, got %s", bodyStr) - } - - if !strings.Contains(bodyStr, "expected string but got number") { - t.Errorf("Expected 'expected string but got number' in error message, got %s", bodyStr) - } -} - // Test with custom validation error handler func TestJSONTypeMismatchWithCustomHandler(t *testing.T) { app := fiber.New()