Fix the logger for the htmx handler to return errors inside the stdout and the app.log file (because it is rendering related, not http)
- commit
- 5785c6847d1e1a894db525b751c4ae20bad248f3
- author
- bnema · · 2023-09-02 14:41 UTC
- committer
- bnema · · 2023-09-04 06:59 UTC
- parent
- 42483f8
- signature
- unsigned
7 files · +27 -35
M cmd/main.go +2 -1
| @@ -15,11 +15,12 @@ func main() { | |||
| 15 | 15 | } | |
| 16 | 16 | ||
| 17 | 17 | // Initialize echo server with routes and middlewares | |
| 18 | - | e := routes.NewRouter(gordon.APPLogger.Logger, gordon.HTTPLogger.Logger) | |
| 18 | + | e := routes.NewRouter(gordon) // Pass the entire 'App' instance | |
| 19 | 19 | ||
| 20 | 20 | if err := e.Start(":1323"); err != nil { | |
| 21 | 21 | gordon.APPLogger.Error().Err(err).Msg("Failed to start the server") | |
| 22 | 22 | } | |
| 23 | + | ||
| 23 | 24 | // Graceful shutdown | |
| 24 | 25 | gordon.APPLogger.Info().Msg("Server is shutting down...") | |
| 25 | 26 | } | |
| @@ -15,11 +15,12 @@ func main() { | |||||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | // Initialize echo server with routes and middlewares | 17 | // Initialize echo server with routes and middlewares | ||
| 18 | - | e := routes.NewRouter(gordon.APPLogger.Logger, gordon.HTTPLogger.Logger) | 18 | + | e := routes.NewRouter(gordon) // Pass the entire 'App' instance |
| 19 | 19 | ||||
| 20 | if err := e.Start(":1323"); err != nil { | 20 | if err := e.Start(":1323"); err != nil { | ||
| 21 | gordon.APPLogger.Error().Err(err).Msg("Failed to start the server") | 21 | gordon.APPLogger.Error().Err(err).Msg("Failed to start the server") | ||
| 22 | } | 22 | } | ||
| 23 | + | ||||
| 23 | // Graceful shutdown | 24 | // Graceful shutdown | ||
| 24 | gordon.APPLogger.Info().Msg("Server is shutting down...") | 25 | gordon.APPLogger.Info().Msg("Server is shutting down...") | ||
| 25 | } | 26 | } | ||
M internal/app/app.go +5 -4
| @@ -18,24 +18,25 @@ type App struct { | |||
| 18 | 18 | Template *template.Template | |
| 19 | 19 | // Define the app renderer | |
| 20 | 20 | Renderer *utils.Renderer | |
| 21 | + | // Define the app router | |
| 21 | 22 | } | |
| 22 | 23 | ||
| 23 | 24 | func NewApp() (*App, error) { | |
| 24 | 25 | app := &App{} | |
| 25 | 26 | ||
| 26 | 27 | // Initialize the general application logger | |
| 27 | - | appLogger, err := initializeAppLogger() | |
| 28 | + | AppLogger, err := initializeAppLogger() | |
| 28 | 29 | if err != nil { | |
| 29 | 30 | return nil, err | |
| 30 | 31 | } | |
| 31 | - | app.APPLogger = &utils.Logger{Logger: appLogger} | |
| 32 | + | app.APPLogger = &utils.Logger{Logger: AppLogger} | |
| 32 | 33 | ||
| 33 | 34 | // Initialize the HTTP logger | |
| 34 | - | httpLogger, err := initializeHTTPLogger() | |
| 35 | + | HttpLogger, err := initializeHTTPLogger() | |
| 35 | 36 | if err != nil { | |
| 36 | 37 | return nil, err | |
| 37 | 38 | } | |
| 38 | - | app.HTTPLogger = &utils.Logger{Logger: httpLogger} | |
| 39 | + | app.HTTPLogger = &utils.Logger{Logger: HttpLogger} | |
| 39 | 40 | ||
| 40 | 41 | return app, nil | |
| 41 | 42 | } | |
| @@ -18,24 +18,25 @@ type App struct { | |||||
| 18 | Template *template.Template | 18 | Template *template.Template | ||
| 19 | // Define the app renderer | 19 | // Define the app renderer | ||
| 20 | Renderer *utils.Renderer | 20 | Renderer *utils.Renderer | ||
| 21 | + | // Define the app router | |||
| 21 | } | 22 | } | ||
| 22 | 23 | ||||
| 23 | func NewApp() (*App, error) { | 24 | func NewApp() (*App, error) { | ||
| 24 | app := &App{} | 25 | app := &App{} | ||
| 25 | 26 | ||||
| 26 | // Initialize the general application logger | 27 | // Initialize the general application logger | ||
| 27 | - | appLogger, err := initializeAppLogger() | 28 | + | AppLogger, err := initializeAppLogger() |
| 28 | if err != nil { | 29 | if err != nil { | ||
| 29 | return nil, err | 30 | return nil, err | ||
| 30 | } | 31 | } | ||
| 31 | - | app.APPLogger = &utils.Logger{Logger: appLogger} | 32 | + | app.APPLogger = &utils.Logger{Logger: AppLogger} |
| 32 | 33 | ||||
| 33 | // Initialize the HTTP logger | 34 | // Initialize the HTTP logger | ||
| 34 | - | httpLogger, err := initializeHTTPLogger() | 35 | + | HttpLogger, err := initializeHTTPLogger() |
| 35 | if err != nil { | 36 | if err != nil { | ||
| 36 | return nil, err | 37 | return nil, err | ||
| 37 | } | 38 | } | ||
| 38 | - | app.HTTPLogger = &utils.Logger{Logger: httpLogger} | 39 | + | app.HTTPLogger = &utils.Logger{Logger: HttpLogger} |
| 39 | 40 | ||||
| 40 | return app, nil | 41 | return app, nil | ||
| 41 | } | 42 | } | ||
M internal/http/handlers/htmx.go +10 -19
| @@ -1,7 +1,6 @@ | |||
| 1 | 1 | package handlers | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "fmt" | |
| 5 | 4 | "net/http" | |
| 6 | 5 | ||
| 7 | 6 | "github.com/labstack/echo/v4" | |
| @@ -9,49 +8,41 @@ import ( | |||
| 9 | 8 | "gogs.bnema.dev/gordon-echo/pkg/utils" | |
| 10 | 9 | ) | |
| 11 | 10 | ||
| 12 | - | var logger = utils.NewLogger() | |
| 13 | - | ||
| 14 | 11 | func HTMXHandler(c echo.Context) error { | |
| 12 | + | // Get the logger instance | |
| 13 | + | logger := utils.NewLogger().GetTypeLogger(utils.App) | |
| 15 | 14 | // Detect and extract HTMX data from the request | |
| 16 | 15 | htmxRequest, err := htmx.GetRequest(c) | |
| 17 | 16 | if err != nil { | |
| 18 | - | return logger.Debug().GetCtx().Err() | |
| 17 | + | logger.Debug().Err(err).Msg("Failed to extract HTMX data") | |
| 18 | + | return err | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | 21 | // If the request is not an HTMX request, return an error | |
| 22 | 22 | if !htmxRequest.Enabled { | |
| 23 | - | return logger.Error().GetCtx().Err() | |
| 23 | + | logger.Error().Msg("Request is not an HTMX request") | |
| 24 | + | return c.String(http.StatusBadRequest, "Not an HTMX request") | |
| 24 | 25 | } | |
| 25 | 26 | ||
| 26 | 27 | // If it's a GET request, handle the fragment | |
| 27 | 28 | if c.Request().Method == http.MethodGet { | |
| 28 | - | return HTMXFragmentHandler(c) | |
| 29 | + | return htmxFragmentHandler(c, logger) | |
| 29 | 30 | } | |
| 30 | 31 | ||
| 31 | 32 | // For other HTTP methods, return an error | |
| 32 | - | ||
| 33 | 33 | return c.String(http.StatusBadRequest, "Invalid request method") | |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | - | func HTMXFragmentHandler(c echo.Context) error { | |
| 36 | + | func htmxFragmentHandler(c echo.Context, logger *utils.Logger) error { | |
| 37 | 37 | fragment := c.Request().Header.Get("X-Fragment") | |
| 38 | - | fmt.Println(fragment) | |
| 39 | 38 | if fragment == "" { | |
| 39 | + | logger.Error().Msg("Missing fragment header") | |
| 40 | 40 | return c.String(http.StatusBadRequest, "Missing fragment header") | |
| 41 | 41 | } | |
| 42 | - | ||
| 43 | 42 | content, err := utils.GetHTMLFragmentByID(fragment) | |
| 44 | 43 | if err != nil { | |
| 45 | - | // You might want to differentiate between different types of errors: | |
| 46 | - | // 1. If the fragment doesn't match any known component (return a 404 error) | |
| 47 | - | // 2. If there's another error while fetching the component (return a 500 error) | |
| 48 | - | // For this example, I'm treating all errors as internal server errors for simplicity. | |
| 44 | + | logger.Error().Err(err).Msg("Failed to get fragment") | |
| 49 | 45 | return c.String(http.StatusInternalServerError, err.Error()) | |
| 50 | 46 | } | |
| 51 | - | ||
| 52 | - | // Create and apply an HTMX response if necessary | |
| 53 | - | // htmxResponse := htmx.Response{Refresh: true} | |
| 54 | - | // htmxResponse.Apply(c) | |
| 55 | - | ||
| 56 | 47 | return c.HTML(http.StatusOK, content) | |
| 57 | 48 | } | |
| @@ -1,7 +1,6 @@ | |||||
| 1 | package handlers | 1 | package handlers | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "fmt" | |||
| 5 | "net/http" | 4 | "net/http" | ||
| 6 | 5 | ||||
| 7 | "github.com/labstack/echo/v4" | 6 | "github.com/labstack/echo/v4" | ||
| @@ -9,49 +8,41 @@ import ( | |||||
| 9 | "gogs.bnema.dev/gordon-echo/pkg/utils" | 8 | "gogs.bnema.dev/gordon-echo/pkg/utils" | ||
| 10 | ) | 9 | ) | ||
| 11 | 10 | ||||
| 12 | - | var logger = utils.NewLogger() | |||
| 13 | - | ||||
| 14 | func HTMXHandler(c echo.Context) error { | 11 | func HTMXHandler(c echo.Context) error { | ||
| 12 | + | // Get the logger instance | |||
| 13 | + | logger := utils.NewLogger().GetTypeLogger(utils.App) | |||
| 15 | // Detect and extract HTMX data from the request | 14 | // Detect and extract HTMX data from the request | ||
| 16 | htmxRequest, err := htmx.GetRequest(c) | 15 | htmxRequest, err := htmx.GetRequest(c) | ||
| 17 | if err != nil { | 16 | if err != nil { | ||
| 18 | - | return logger.Debug().GetCtx().Err() | 17 | + | logger.Debug().Err(err).Msg("Failed to extract HTMX data") |
| 18 | + | return err | |||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| 21 | // If the request is not an HTMX request, return an error | 21 | // If the request is not an HTMX request, return an error | ||
| 22 | if !htmxRequest.Enabled { | 22 | if !htmxRequest.Enabled { | ||
| 23 | - | return logger.Error().GetCtx().Err() | 23 | + | logger.Error().Msg("Request is not an HTMX request") |
| 24 | + | return c.String(http.StatusBadRequest, "Not an HTMX request") | |||
| 24 | } | 25 | } | ||
| 25 | 26 | ||||
| 26 | // If it's a GET request, handle the fragment | 27 | // If it's a GET request, handle the fragment | ||
| 27 | if c.Request().Method == http.MethodGet { | 28 | if c.Request().Method == http.MethodGet { | ||
| 28 | - | return HTMXFragmentHandler(c) | 29 | + | return htmxFragmentHandler(c, logger) |
| 29 | } | 30 | } | ||
| 30 | 31 | ||||
| 31 | // For other HTTP methods, return an error | 32 | // For other HTTP methods, return an error | ||
| 32 | - | ||||
| 33 | return c.String(http.StatusBadRequest, "Invalid request method") | 33 | return c.String(http.StatusBadRequest, "Invalid request method") | ||
| 34 | } | 34 | } | ||
| 35 | 35 | ||||
| 36 | - | func HTMXFragmentHandler(c echo.Context) error { | 36 | + | func htmxFragmentHandler(c echo.Context, logger *utils.Logger) error { |
| 37 | fragment := c.Request().Header.Get("X-Fragment") | 37 | fragment := c.Request().Header.Get("X-Fragment") | ||
| 38 | - | fmt.Println(fragment) | |||
| 39 | if fragment == "" { | 38 | if fragment == "" { | ||
| 39 | + | logger.Error().Msg("Missing fragment header") | |||
| 40 | return c.String(http.StatusBadRequest, "Missing fragment header") | 40 | return c.String(http.StatusBadRequest, "Missing fragment header") | ||
| 41 | } | 41 | } | ||
| 42 | - | ||||
| 43 | content, err := utils.GetHTMLFragmentByID(fragment) | 42 | content, err := utils.GetHTMLFragmentByID(fragment) | ||
| 44 | if err != nil { | 43 | if err != nil { | ||
| 45 | - | // You might want to differentiate between different types of errors: | 44 | + | logger.Error().Err(err).Msg("Failed to get fragment") |
| 46 | - | // 1. If the fragment doesn't match any known component (return a 404 error) | |||
| 47 | - | // 2. If there's another error while fetching the component (return a 500 error) | |||
| 48 | - | // For this example, I'm treating all errors as internal server errors for simplicity. | |||
| 49 | return c.String(http.StatusInternalServerError, err.Error()) | 45 | return c.String(http.StatusInternalServerError, err.Error()) | ||
| 50 | } | 46 | } | ||
| 51 | - | ||||
| 52 | - | // Create and apply an HTMX response if necessary | |||
| 53 | - | // htmxResponse := htmx.Response{Refresh: true} | |||
| 54 | - | // htmxResponse.Apply(c) | |||
| 55 | - | ||||
| 56 | return c.HTML(http.StatusOK, content) | 47 | return c.HTML(http.StatusOK, content) | ||
| 57 | } | 48 | } | ||
M internal/http/routes/router.go +3 -3
| @@ -2,16 +2,16 @@ package routes | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "github.com/labstack/echo/v4" | |
| 5 | - | "github.com/rs/zerolog" | |
| 5 | + | "gogs.bnema.dev/gordon-echo/internal/app" | |
| 6 | 6 | "gogs.bnema.dev/gordon-echo/internal/http/handlers" | |
| 7 | 7 | "gogs.bnema.dev/gordon-echo/internal/http/middlewares" | |
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | - | func NewRouter(appLogger, echoLogger zerolog.Logger) *echo.Echo { | |
| 10 | + | func NewRouter(app *app.App) *echo.Echo { | |
| 11 | 11 | e := echo.New() | |
| 12 | 12 | ||
| 13 | 13 | // Register middlewares | |
| 14 | - | e.Use(middlewares.NewRequestLoggerMiddleware(echoLogger)) | |
| 14 | + | e.Use(middlewares.NewRequestLoggerMiddleware(app.HTTPLogger.Logger)) | |
| 15 | 15 | ||
| 16 | 16 | // Register routes | |
| 17 | 17 | e = bindStaticAdminUI(e) | |
| @@ -2,16 +2,16 @@ package routes | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "github.com/labstack/echo/v4" | 4 | "github.com/labstack/echo/v4" | ||
| 5 | - | "github.com/rs/zerolog" | 5 | + | "gogs.bnema.dev/gordon-echo/internal/app" |
| 6 | "gogs.bnema.dev/gordon-echo/internal/http/handlers" | 6 | "gogs.bnema.dev/gordon-echo/internal/http/handlers" | ||
| 7 | "gogs.bnema.dev/gordon-echo/internal/http/middlewares" | 7 | "gogs.bnema.dev/gordon-echo/internal/http/middlewares" | ||
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | - | func NewRouter(appLogger, echoLogger zerolog.Logger) *echo.Echo { | 10 | + | func NewRouter(app *app.App) *echo.Echo { |
| 11 | e := echo.New() | 11 | e := echo.New() | ||
| 12 | 12 | ||||
| 13 | // Register middlewares | 13 | // Register middlewares | ||
| 14 | - | e.Use(middlewares.NewRequestLoggerMiddleware(echoLogger)) | 14 | + | e.Use(middlewares.NewRequestLoggerMiddleware(app.HTTPLogger.Logger)) |
| 15 | 15 | ||||
| 16 | // Register routes | 16 | // Register routes | ||
| 17 | e = bindStaticAdminUI(e) | 17 | e = bindStaticAdminUI(e) | ||
M pkg/utils/fragments.go +2 -2
| @@ -9,9 +9,9 @@ import ( | |||
| 9 | 9 | ||
| 10 | 10 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | |
| 11 | 11 | func GetHTMLFragmentByID(id string) (string, error) { | |
| 12 | - | data, err := ui.TemplateFS.Open("components.html") | |
| 12 | + | data, err := ui.TemplateFS.Open("components.gohtmlll") | |
| 13 | 13 | if err != nil { | |
| 14 | - | return "", fmt.Errorf("failed to open components.html: %w", err) | |
| 14 | + | return "", fmt.Errorf("failed to open components: %w", err) | |
| 15 | 15 | } | |
| 16 | 16 | defer data.Close() | |
| 17 | 17 | ||
| @@ -9,9 +9,9 @@ import ( | |||||
| 9 | 9 | ||||
| 10 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | 10 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | ||
| 11 | func GetHTMLFragmentByID(id string) (string, error) { | 11 | func GetHTMLFragmentByID(id string) (string, error) { | ||
| 12 | - | data, err := ui.TemplateFS.Open("components.html") | 12 | + | data, err := ui.TemplateFS.Open("components.gohtmlll") |
| 13 | if err != nil { | 13 | if err != nil { | ||
| 14 | - | return "", fmt.Errorf("failed to open components.html: %w", err) | 14 | + | return "", fmt.Errorf("failed to open components: %w", err) |
| 15 | } | 15 | } | ||
| 16 | defer data.Close() | 16 | defer data.Close() | ||
| 17 | 17 | ||||
M pkg/utils/logger.go +5 -5
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | ||
| 10 | 10 | const ( | |
| 11 | 11 | App LoggerType = iota | |
| 12 | - | HTTP | |
| 12 | + | Access | |
| 13 | 13 | Database | |
| 14 | 14 | Admin | |
| 15 | 15 | User | |
| @@ -48,8 +48,8 @@ func (l *Logger) GetTypeLogger(loggerType LoggerType) *Logger { | |||
| 48 | 48 | switch loggerType { | |
| 49 | 49 | case App: | |
| 50 | 50 | return &Logger{l.With().Str("type", "app").Logger()} | |
| 51 | - | case HTTP: | |
| 52 | - | return &Logger{l.With().Str("type", "http").Logger()} | |
| 51 | + | case Access: | |
| 52 | + | return &Logger{l.With().Str("type", "access").Logger()} | |
| 53 | 53 | case Database: | |
| 54 | 54 | return &Logger{l.With().Str("type", "database").Logger()} | |
| 55 | 55 | case Admin: | |
| @@ -97,7 +97,7 @@ func GetLogFileForType(loggerType LoggerType) string { | |||
| 97 | 97 | switch loggerType { | |
| 98 | 98 | case App, Database, Utils, Render: | |
| 99 | 99 | return "logs/app.log" | |
| 100 | - | case HTTP, Admin, User, Auth: | |
| 100 | + | case Access, Admin, User, Auth: | |
| 101 | 101 | return "logs/http.log" | |
| 102 | 102 | default: | |
| 103 | 103 | return "logs/undefined.log" | |
| @@ -119,7 +119,7 @@ func GetLogLevel(loggerType LoggerType) zerolog.Level { | |||
| 119 | 119 | switch loggerType { | |
| 120 | 120 | case App, Database, Utils, Render: | |
| 121 | 121 | return zerolog.InfoLevel | |
| 122 | - | case HTTP, Admin, User, Auth: | |
| 122 | + | case Access, Admin, User, Auth: | |
| 123 | 123 | return zerolog.DebugLevel | |
| 124 | 124 | default: | |
| 125 | 125 | return zerolog.DebugLevel | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | 9 | ||||
| 10 | const ( | 10 | const ( | ||
| 11 | App LoggerType = iota | 11 | App LoggerType = iota | ||
| 12 | - | HTTP | 12 | + | Access |
| 13 | Database | 13 | Database | ||
| 14 | Admin | 14 | Admin | ||
| 15 | User | 15 | User | ||
| @@ -48,8 +48,8 @@ func (l *Logger) GetTypeLogger(loggerType LoggerType) *Logger { | |||||
| 48 | switch loggerType { | 48 | switch loggerType { | ||
| 49 | case App: | 49 | case App: | ||
| 50 | return &Logger{l.With().Str("type", "app").Logger()} | 50 | return &Logger{l.With().Str("type", "app").Logger()} | ||
| 51 | - | case HTTP: | 51 | + | case Access: |
| 52 | - | return &Logger{l.With().Str("type", "http").Logger()} | 52 | + | return &Logger{l.With().Str("type", "access").Logger()} |
| 53 | case Database: | 53 | case Database: | ||
| 54 | return &Logger{l.With().Str("type", "database").Logger()} | 54 | return &Logger{l.With().Str("type", "database").Logger()} | ||
| 55 | case Admin: | 55 | case Admin: | ||
| @@ -97,7 +97,7 @@ func GetLogFileForType(loggerType LoggerType) string { | |||||
| 97 | switch loggerType { | 97 | switch loggerType { | ||
| 98 | case App, Database, Utils, Render: | 98 | case App, Database, Utils, Render: | ||
| 99 | return "logs/app.log" | 99 | return "logs/app.log" | ||
| 100 | - | case HTTP, Admin, User, Auth: | 100 | + | case Access, Admin, User, Auth: |
| 101 | return "logs/http.log" | 101 | return "logs/http.log" | ||
| 102 | default: | 102 | default: | ||
| 103 | return "logs/undefined.log" | 103 | return "logs/undefined.log" | ||
| @@ -119,7 +119,7 @@ func GetLogLevel(loggerType LoggerType) zerolog.Level { | |||||
| 119 | switch loggerType { | 119 | switch loggerType { | ||
| 120 | case App, Database, Utils, Render: | 120 | case App, Database, Utils, Render: | ||
| 121 | return zerolog.InfoLevel | 121 | return zerolog.InfoLevel | ||
| 122 | - | case HTTP, Admin, User, Auth: | 122 | + | case Access, Admin, User, Auth: |
| 123 | return zerolog.DebugLevel | 123 | return zerolog.DebugLevel | ||
| 124 | default: | 124 | default: | ||
| 125 | return zerolog.DebugLevel | 125 | return zerolog.DebugLevel | ||
M pkg/utils/render.go +0 -1
| @@ -17,7 +17,6 @@ type Renderer struct { | |||
| 17 | 17 | func (r *Renderer) Render(data interface{}) (string, error) { | |
| 18 | 18 | if r.ParseError != nil { | |
| 19 | 19 | r.Logger.Error().Err(r.ParseError).Msg("Parse error") | |
| 20 | - | ||
| 21 | 20 | return "", r.ParseError | |
| 22 | 21 | } | |
| 23 | 22 | ||
| @@ -17,7 +17,6 @@ type Renderer struct { | |||||
| 17 | func (r *Renderer) Render(data interface{}) (string, error) { | 17 | func (r *Renderer) Render(data interface{}) (string, error) { | ||
| 18 | if r.ParseError != nil { | 18 | if r.ParseError != nil { | ||
| 19 | r.Logger.Error().Err(r.ParseError).Msg("Parse error") | 19 | r.Logger.Error().Err(r.ParseError).Msg("Parse error") | ||
| 20 | - | ||||
| 21 | return "", r.ParseError | 20 | return "", r.ParseError | ||
| 22 | } | 21 | } | ||
| 23 | 22 | ||||