Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use WriteErr function for handling error returned by handler #640

Conversation

notjustmoney
Copy link
Contributor

@notjustmoney notjustmoney commented Nov 6, 2024

First of all, thank you for �maintating the Huma framework active. I'm using it for personal projects, and I can write code more conveniently and elegantly than before.

What is this PR?

This PR refactors error handling logic in Register function to use WriteErr function.

Context

Register function can register handler and include handling logic for overall request-response lifecycle. If the registered handler returns an error, the error is handled according to the error type. For now, there is 2 types of error(StatusError and plain error).

In the previous code, �if the error returned from the handler is StatusError use it for response body, else(in case of plain error) instantiate response body using the NewError function. And then write response by calling the transformAndWrite function within the same file.

Why do this?

I think if the returned from the handler is plain error, it should be use NewErrorWithContext instead of NewError �to ensure consistent behavior within framework. �That's exactly why the NewErrorWithContext and WriteErr functions exist for this as I understand. Error handling in other part of the function already use WriteErr, so the code in this PR may be changed together.

What will be changed?

  • Add writeStatusError function
    For StatusError, it works as same before using new writeStatusError function. writeStatusError function acts like WriteErr function include content negotitation, set status code, but the only difference is that NewErrorWithContext does not create a response body, and the received StatusError by function parameter is used immediately for the response.

  • Use WriteErr function for plain error
    WriteErr function do the almost same thing with transformAndWrite function except the transformAndWrite function receive the response body by parameter. So I refactor this part to use WriteErr function.

Expectations

  • By using custom NewErrorWIthContext, Huma can intercept handler error. It will be a great support in implementing global error handler using Huma middleware or Sentry alerting using context.Context of request.

What �can do in next step?

I think the StatusError handling in the same part should also be able to use huma.Context so that every error inside Huma can be handled with consistent behavior.

Summary by CodeRabbit

  • New Features

    • Introduced a centralized error handling function for improved status error management.
    • Added a new response writing function to enhance content type negotiation and error handling.
    • Implemented a test case for simulating and validating generic error responses.
  • Bug Fixes

    • Improved robustness of error reporting and response structure.
  • Documentation

    • Updated comments for clarity regarding error handling methods.

Copy link

coderabbitai bot commented Nov 6, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The changes introduce a new function, writeStatusError, in error.go to handle status errors more effectively, including content negotiation and HTTP status code management. The huma.go file sees improvements in response handling and error management, particularly through the addition of writeResponse and writeResponseWithPanic. Additionally, the Operation struct in openapi.go is updated to clarify error schema generation methods. These updates enhance the API framework's robustness and maintain backward compatibility.

Changes

File Change Summary
error.go - Added func writeStatusError(api API, ctx Context, err StatusError) error.
- Updated var ErrorFormatter to directly reference fmt.Sprintf.
huma.go - Added func writeResponse(api API, ctx Context, status int, ct string, body any) error.
- Added func writeResponseWithPanic(api API, ctx Context, status int, ct string, body any).
- Updated func transformAndWrite(api API, ctx Context, status int, ct string, body any) to return an error.
openapi.go - Updated comment for Errors field in Operation struct to reflect new error schema generation options.
error_test.go - Updated context initialization in TestNegotiateError and TestTransformError to use a new Operation instance.
- Confirmed functionality of error models and HTTP status codes in tests.
huma_test.go - Added new test case handler-generic-error in TestFeatures to validate error handling for a simulated error response.

Possibly related PRs

🐰 In the code, I hop and play,
With errors fixed in a clever way.
A status here, a format there,
Our API's strong, beyond compare!
So let us code, and let us cheer,
For structured errors, we hold dear! 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
error.go (1)

283-306: LGTM with minor suggestions for error wrapping consistency.

The implementation effectively handles status errors with proper content negotiation and special cases. Consider wrapping transformation and marshaling errors for better error context:

 func writeStatusError(api API, ctx Context, err StatusError) error {
 	ct, negotiateErr := api.Negotiate(ctx.Header("Accept"))
 	if negotiateErr != nil {
 		return fmt.Errorf("failed to write status error: %w", negotiateErr)
 	}
 	if ctf, ok := err.(ContentTypeFilter); ok {
 		ct = ctf.ContentType(ct)
 	}
 	ctx.SetHeader("Content-Type", ct)

 	status := err.GetStatus()
 	ctx.SetStatus(status)

 	// If request accept no output, just set the status code and return.
 	if status == http.StatusNoContent || status == http.StatusNotModified {
 		return nil
 	}

 	tval, terr := api.Transform(ctx, strconv.Itoa(status), err)
 	if terr != nil {
-		return terr
+		return fmt.Errorf("failed to transform status error: %w", terr)
 	}
-	return api.Marshal(ctx.BodyWriter(), ct, tval)
+	if err := api.Marshal(ctx.BodyWriter(), ct, tval); err != nil {
+		return fmt.Errorf("failed to marshal status error: %w", err)
+	}
+	return nil
 }
huma.go (1)

Line range hint 965-971: Ensure type safety when setting string values for enums

The new code supports slices of string subtypes (enums) by dynamically creating and setting values. However, there is a potential for a panic if f.Type().Elem().Kind() is not reflect.String.

Consider adding a type check before calling SetString(val) to ensure that f.Type().Elem().Kind() is indeed reflect.String.

Apply this diff to add the type check:

+ if f.Type().Elem().Kind() != reflect.String {
+     res.Add(pb, value, "invalid enum type")
+     return
+ }
  enumVal := reflect.New(f.Type().Elem()).Elem()
  enumVal.SetString(val)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a74067b and b66498a.

📒 Files selected for processing (3)
  • error.go (2 hunks)
  • huma.go (2 hunks)
  • openapi.go (1 hunks)
🔇 Additional comments (4)
error.go (2)

400-400: LGTM! Clean simplification of error formatting.

Direct assignment to fmt.Sprintf is a good simplification that maintains the same functionality while reducing indirection.


283-306: Verify integration with existing error handling patterns.

Since this introduces a new error handling function, let's verify its integration:

✅ Verification successful

Integration pattern is consistent and properly implemented

The verification shows:

  • writeStatusError is correctly used in huma.go to handle StatusError instances
  • It complements the existing WriteErr function which is used extensively throughout the codebase
  • The error handling flow is consistent:
    • WriteErr creates and writes structured errors
    • writeStatusError specifically handles StatusError implementations
  • The implementation aligns with the documented error handling patterns in the docs
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage patterns of error handling functions to ensure consistent approach

# Check where writeStatusError is being called
echo "Checking writeStatusError usage:"
rg "writeStatusError\(" -A 2

# Check existing error handling patterns
echo -e "\nChecking WriteErr usage for comparison:"
rg "WriteErr\(" -A 2

# Look for potential places where writeStatusError should be used
echo -e "\nChecking StatusError handling patterns:"
rg "StatusError\)" -A 5

Length of output: 7218

openapi.go (1)

866-867: LGTM! Documentation update accurately reflects the error schema generation options.

The comment update properly documents that error schemas can now be generated from either huma.NewError() or huma.NewErrorWithContext, which aligns with the PR's objective to enhance error handling capabilities.

huma.go (1)

1405-1415: Refactored error handling aligns with error management improvements

The introduction of the writeStatusError function and its usage to handle StatusError enhances consistency in error responses and leverages content negotiation. This change aligns with the PR objectives to streamline error handling.

Copy link

codecov bot commented Nov 7, 2024

Codecov Report

Attention: Patch coverage is 97.29730% with 1 line in your changes missing coverage. Please review.

Project coverage is 92.97%. Comparing base (a74067b) to head (f628ea0).
Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
huma.go 96.87% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #640      +/-   ##
==========================================
+ Coverage   92.70%   92.97%   +0.26%     
==========================================
  Files          22       22              
  Lines        4879     4895      +16     
==========================================
+ Hits         4523     4551      +28     
+ Misses        308      300       -8     
+ Partials       48       44       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
error_test.go (1)

Line range hint 1-143: Consider adding test cases for new error handling functionality

While the existing tests provide good coverage, consider adding specific test cases for:

  1. The new writeStatusError function to verify its behavior with different status codes
  2. Integration between WriteErr and writeStatusError to ensure they work together correctly
  3. Edge cases where both plain errors and status errors are handled in the same request

This would help ensure the robustness of the new error handling implementation.

huma.go (2)

466-488: LGTM! Well-structured response writing logic.

The function effectively centralizes response writing with proper content type negotiation and error handling. The fallback to JSON on negotiation failure is a good defensive programming practice.

Consider extracting the error response logic (lines 474-480) into a separate function to improve readability and reusability:

+func writeErrorResponse(api API, ctx Context, err error) error {
+    status := http.StatusInternalServerError
+    if se, ok := err.(StatusError); ok {
+        status = se.GetStatus()
+    }
+    return transformAndWrite(api, ctx, status, "application/json", err)
+}

 func writeResponse(api API, ctx Context, status int, ct string, body any) error {
     if ct == "" {
         var err error
         ct, err = getContentType(api, ctx, body)
         if err != nil {
-            status := http.StatusInternalServerError
-            if se, ok := err.(StatusError); ok {
-                status = se.GetStatus()
-            }
-            if err := transformAndWrite(api, ctx, status, "application/json", err); err != nil {
-                return err
-            }
+            return writeErrorResponse(api, ctx, err)
         }
     }
     // ... rest of the function
 }

490-494: LGTM! Consider adding documentation about panic behavior.

The function serves as a panic wrapper for writeResponse, which is useful in scenarios where error handling isn't feasible. However, it would benefit from clear documentation about its panic behavior.

Add a documentation comment explaining the panic behavior:

+// writeResponseWithPanic writes a response using writeResponse and panics if an error occurs.
+// This should only be used in scenarios where normal error handling isn't feasible,
+// such as in final error handlers or cleanup routines.
 func writeResponseWithPanic(api API, ctx Context, status int, ct string, body any) {
     if err := writeResponse(api, ctx, status, ct, body); err != nil {
         panic(err)
     }
 }
huma_test.go (1)

1368-1383: LGTM! The test case effectively verifies generic error handling.

The test case is well-structured and provides good coverage for generic error handling, ensuring that non-status errors are properly converted to 500 Internal Server Error responses.

Consider using errors.New for better performance.

Since the error message is a simple string without any formatting, using errors.New would be more efficient than fmt.Errorf.

Apply this diff to improve performance:

-					return nil, fmt.Errorf("whoops")
+					return nil, errors.New("whoops")
🧰 Tools
🪛 golangci-lint

1375-1375: fmt.Errorf can be replaced with errors.New

(perfsprint)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b66498a and 0cb7a89.

📒 Files selected for processing (4)
  • error.go (2 hunks)
  • error_test.go (2 hunks)
  • huma.go (6 hunks)
  • huma_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • error.go
🧰 Additional context used
🪛 golangci-lint
huma_test.go

1375-1375: fmt.Errorf can be replaced with errors.New

(perfsprint)

🔇 Additional comments (4)
error_test.go (2)

86-86: LGTM: Improved test context initialization

The change from nil to &huma.Operation{} makes the test more realistic by providing a concrete operation context, which better reflects real-world usage scenarios.


101-101: LGTM: Consistent context initialization

The change maintains consistency with other tests by using a proper Operation instance in the context. This helps ensure that error transformation is tested under realistic conditions.

huma.go (2)

496-507: LGTM! Well-designed content type negotiation.

The function effectively handles content type negotiation with proper error handling and extensibility through the ContentTypeFilter interface. The error message includes context which is helpful for debugging.


1449-1458: LGTM! Improved error handling with better context.

The changes effectively implement the PR objectives by:

  1. Using dedicated error handling for StatusError
  2. Adding request context to generic errors
  3. Maintaining consistent error response writing through writeResponseWithPanic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants