{"id":"105df1e6ca09b1bb","repo":"gofiber/fiber","slug":"v","errorCode":null,"errorMessage":"%v","messagePattern":"%v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/recover/recover.go","lineNumber":22,"sourceCode":"\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\n\t\"github.com/gofiber/fiber/v3\"\n)\n\n// Must not start with \"panic: \": the panic was recovered, and that exact prefix\n// makes gotestsum treat the whole run as crashed and skip --rerun-fails.\nfunc defaultStackTraceHandler(_ fiber.Ctx, e any) {\n\tfmt.Fprintf(os.Stderr, \"recovered panic: %v\\n\\n%s\\n\", e, debug.Stack())\n}\n\n// DefaultPanicHandler returns r directly if it's an error, and creates a new one with the %v verb otherwise.\nfunc DefaultPanicHandler(_ fiber.Ctx, r any) error {\n\tif err, ok := r.(error); ok {\n\t\treturn err\n\t}\n\treturn fmt.Errorf(\"%v\", r)\n}\n\n// New creates a new middleware handler\nfunc New(config ...Config) fiber.Handler {\n\t// Set default config\n\tcfg := configDefault(config...)\n\n\t// Return new handler\n\treturn func(c fiber.Ctx) (err error) { //nolint:nonamedreturns // Uses recover() to overwrite the error\n\t\t// Don't execute middleware if Next returns true\n\t\tif cfg.Next != nil && cfg.Next(c) {\n\t\t\treturn c.Next()\n\t\t}\n\n\t\t// Catch panics\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tif cfg.EnableStackTrace {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/recover/recover.go#L4-L40","documentation":"Returned by DefaultPanicHandler when the recovered panic value is not an error. The handler converts any non-error panic (string, int, struct) into an error via fmt.Errorf(\"%v\", r) so fiber's error chain can carry it. The '%v' deliberately avoids a 'panic: ' prefix that breaks gotestsum reruns.","triggerScenarios":"A handler panics with a non-error value (panic(\"boom\"), panic(42), panic(SomeStruct{})) and the recover middleware's default PanicHandler wraps it at recover.go:22.","commonSituations":"Third-party code panicking with a string; a nil-pointer dereference recovered as a runtime.Error (this branch only triggers for non-error values); legacy code using panic with primitives.","solutions":["Fix the underlying panic at its source.","If you control the panicking code, panic with a proper error so the original value is preserved unchanged.","Provide a custom Config.PanicHandler to render non-error panics however you need."],"exampleFix":"// before: panicking with a string loses structure\npanic(\"invalid state\")\n\n// after: panic with a typed error\nvar ErrInvalidState = errors.New(\"invalid state\")\npanic(ErrInvalidState)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// isError narrows a recovered panic value to the error interface so you\n// can preserve it verbatim instead of re-wrapping via %v.\nfunc isError(r any) (error, bool) {\n    e, ok := r.(error)\n    return e, ok\n}","tryCatchPattern":"// Install the recover middleware; DefaultPanicHandler wraps non-error\n// panics into an error automatically.\napp.Use(recover.New(recover.Config{\n    EnableStackTrace: true,\n    PanicHandler: func(c fiber.Ctx, r any) error {\n        if e, ok := r.(error); ok {\n            return e // preserve typed errors unchanged\n        }\n        return fiber.NewError(fiber.StatusInternalServerError,\n            fmt.Sprintf(\"panic: %v\", r))\n    },\n}))","preventionTips":["Always panic with a proper error, not a string or primitive.","Install recover.New early in the middleware chain.","Log stack traces (EnableStackTrace:true) to locate panic sources.","Add unit tests that exercise handlers likely to panic."],"tags":["recover","panic","middleware","error-handling"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}