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

feat: make validation error messages customizable #520

Merged
merged 5 commits into from
Jul 26, 2024

Conversation

smacker
Copy link
Contributor

@smacker smacker commented Jul 24, 2024

Hello!

In the project I'm working on we show validation errors directly to the end user.
In this project we need to return them in specific format defined by the product.

But currently validation error messages are hard-coded in the source code so we would need to either replace validation completely or rely on existing text messages that might change over time.

This PR tries to improve it by allowing:

  1. Ability to compare errors safely using variables
  2. Customize error messages by re-defining variables during application start
  3. Customize formatting function to further customize error message

For example we want to replace existing error:

expected string to be RFC 5322 email: mail: missing '@' or angle-addr

with something simple like

incorrect email address

the following code let us do it

func init() {
	// override default error messages
	validation.MsgExpectedRFC5322Email = "incorrect email address"
	validation.ErrorFormatter = func(format string, a ...any) string {
		if format == validation.MsgExpectedRFC5322Email {
			return validation.MsgExpectedRFC5322Email
		}

		return fmt.Sprintf(format, a...)
	}
}

I'm not sure if we need unit tests for this and what exactly we need to test.

Please let me know if the idea resonates with you or you have something else in mind. Thank you!

Summary by CodeRabbit

  • New Features

    • Introduced a centralized set of validation error messages for improved consistency and clarity across various validation scenarios.
    • Added an ErrorFormatter function to enhance error message formatting dynamically.
  • Bug Fixes

    • Enhanced error message handling by replacing hardcoded strings with standardized constants, ensuring uniformity and reducing the risk of inconsistencies.
  • Tests

    • Added a new test for validating custom error formatting, improving test coverage for validation logic.

Copy link

coderabbitai bot commented Jul 24, 2024

Walkthrough

The recent changes enhance the validation framework by standardizing error message handling across multiple files. Hardcoded strings have been replaced with predefined constants, improving maintainability and clarity. A new file centralizes various validation error messages, ensuring consistent and informative feedback. Overall, these adjustments lead to a more organized and reliable approach to error reporting in schema validation.

Changes

Files Change Summary
schema.go, validate.go Refactored error handling to use standardized message constants, replacing hardcoded strings for validations.
validation/messages.go Introduced a new file containing a comprehensive set of validation error messages and an ErrorFormatter function.
error.go, validate_test.go Added a global error formatter and a new test function to validate custom error formatting.

Sequence Diagram(s)

sequenceDiagram
    participant Schema
    participant Validator
    participant ErrorFormatter

    Schema->>Validator: Validate Schema
    Validator->>ErrorFormatter: Format Error Messages
    ErrorFormatter->>Validator: Return Formatted Message
    Validator->>Schema: Validation Result
Loading

🐇 In the meadow, bright and merry,
Our schema sings, the changes carry.
With messages clear, like the morning dew,
Validation's joy is fresh and new!
Hop along, let errors be few! 🐇


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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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 as 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.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

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

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 9bf5661 and 9ff9231.

Files selected for processing (3)
  • schema.go (3 hunks)
  • validate.go (15 hunks)
  • validation/messages.go (1 hunks)
Additional comments not posted (9)
validation/messages.go (2)

5-47: LGTM! Well-defined error message constants.

The predefined error message constants improve maintainability and consistency across the validation framework.


49-50: LGTM! Customizable error formatter function.

The error formatter function enhances flexibility by allowing customizable error message formatting.

validate.go (6)

284-284: LGTM! Improved maintainability and consistency.

The use of predefined error message constants improves maintainability and consistency in the validateAnyOf function.


Line range hint 328-475:
LGTM! Improved maintainability and consistency.

The use of predefined error message constants improves maintainability and consistency in the validate function.


497-510: LGTM! Improved maintainability and consistency.

The use of predefined error message constants improves maintainability and consistency in the handleArray function.


597-597: LGTM! Improved maintainability and consistency.

The use of predefined error message constants improves maintainability and consistency in the handleMapString function.


697-697: LGTM! Improved maintainability and consistency.

The use of predefined error message constants improves maintainability and consistency in the handleMapAny function.


187-249: LGTM! Improved maintainability and consistency.

The use of predefined error message constants and the customizable error formatter improves maintainability and consistency in the validateFormat function.

However, ensure that all function calls to validateFormat match the new implementation.

Verification successful

Verification successful.

All instances of validateFormat in the codebase match the new implementation.

  • validate.go: The only call to validateFormat is within this file and aligns with the new implementation.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all function calls to `validateFormat` match the new implementation.

# Test: Search for the function usage. Expect: Only occurances of the new implementation.
rg --type go -A 5 $'validateFormat'

Length of output: 615


Script:

#!/bin/bash
# Description: Search for all instances of `validateFormat` across the entire codebase to ensure consistency with the new implementation.

# Test: Search for all function calls to `validateFormat`. Expect: Only occurrences of the new implementation.
rg 'validateFormat\('

Length of output: 168

schema.go (1)

Line range hint 207-270:
LGTM! Improved consistency and maintainability.

The use of the new validation.ErrorFormatter function for various validation checks improves consistency and maintainability in the PrecomputeMessages function.

Copy link

codecov bot commented Jul 25, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.82%. Comparing base (9bf5661) to head (9ff9231).
Report is 16 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #520      +/-   ##
==========================================
- Coverage   92.83%   92.82%   -0.02%     
==========================================
  Files          22       22              
  Lines        3838     3832       -6     
==========================================
- Hits         3563     3557       -6     
  Misses        230      230              
  Partials       45       45              

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

Copy link
Owner

@danielgtaylor danielgtaylor left a comment

Choose a reason for hiding this comment

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

Overall I think this looks great, thanks! One small comment and I want the tests/coverage checks to pass but otherwise this looks mostly ready.

validate.go Outdated Show resolved Hide resolved
)

// ErrorFormatter is a function that formats an error message
var ErrorFormatter func(format string, a ...any) string = fmt.Sprintf
Copy link
Owner

Choose a reason for hiding this comment

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

Random thought: what about making this aware of the request context to enable i18n/l10n scenarios? For example, you could then use a library to return a localized error message based on the request's Accept-Language header. As is with your PR you could provide a single langauge, what I'm suggesting here is providing many languages and choosing one based on the request's preferences.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually it is the next change I want to submit :) But decided to split the work into smaller pieces.

I noticed you try to avoid strings allocation during request validation. Using ctx would break it because we won't be able to do pre-computation.

The current approach I'm trying is to define 2 interfaces:

// ErrorFormatter is a function that formats an error message
var ErrorFormatter func(format string, a ...any) string = fmt.Sprintf

// ErrorFormatterContext is a function that will be used instead of ErrorFormatter
// to format an error message when defined
var ErrorFormatterContext func(ctx Context, format string, a ...any) string = nil

Usage of ErrorFormatterContext is opt-in. I didn't do benchmarking yet though. If you don't mind I'll continue with it in the following weeks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also moved it to the root of huma. It would make code easier to read as we introduce context.

Copy link
Owner

Choose a reason for hiding this comment

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

Good point about the pre-allocation. This probably requires a bit more thought, so I'm happy to get this improvement in first!

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, codebase verification and nitpick comments (1)
validation/messages.go (1)

1-45: Ensure consistent usage of validation error message constants.

The codebase uses the defined constants in validation/messages.go consistently. However, there are still some hardcoded validation error messages present in the test files. It is recommended to replace these hardcoded strings with the defined constants to maintain consistency.

  • Files with hardcoded strings:
    • validate_test.go
    • schema_test.go
    • queryparam_test.go
    • huma_test.go

Please update these test files to use the constants from validation/messages.go.

Analysis chain

LGTM! Ensure consistent usage of constants.

The code changes are approved.

However, verify that these constants are used consistently throughout the codebase to avoid hardcoded strings.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of validation error message constants.

# Test: Search for the usage of each constant. Expect: No hardcoded strings for validation error messages.
rg --type go -A 5 $'MsgUnexpectedProperty|MsgExpectedRFC3339DateTime|MsgExpectedRFC1123DateTime|MsgExpectedRFC3339Date|MsgExpectedRFC3339Time|MsgExpectedRFC5322Email|MsgExpectedRFC5890Hostname|MsgExpectedRFC2673IPv4|MsgExpectedRFC2373IPv6|MsgExpectedRFC3986URI|MsgExpectedRFC4122UUID|MsgExpectedRFC6570URITemplate|MsgExpectedRFC6901JSONPointer|MsgExpectedRFC6901RelativeJSONPointer|MsgExpectedRegexp|MsgExpectedMatchSchema|MsgExpectedNotMatchSchema|MsgExpectedBoolean|MsgExpectedNumber|MsgExpectedString|MsgExpectedBase64String|MsgExpectedArray|MsgExpectedObject|MsgExpectedArrayItemsUnique|MsgExpectedOneOf|MsgExpectedMinimumNumber|MsgExpectedExclusiveMinimumNumber|MsgExpectedMaximumNumber|MsgExpectedExclusiveMaximumNumber|MsgExpectedNumberBeMultipleOf|MsgExpectedMinLength|MsgExpectedMaxLength|MsgExpectedBePattern|MsgExpectedMatchPattern|MsgExpectedMinItems|MsgExpectedMaxItems|MsgExpectedMinProperties|MsgExpectedMaxProperties|MsgExpectedRequiredProperty|MsgExpectedDependentRequiredProperty'

Length of output: 13084


Script:

#!/bin/bash
# Description: Verify there are no hardcoded validation error messages in the codebase.

# Test: Search for hardcoded strings that match the patterns of the validation error messages.
rg --type go -F "unexpected property" -F "expected string to be RFC 3339 date-time" -F "expected string to be RFC 1123 date-time" -F "expected string to be RFC 3339 date" -F "expected string to be RFC 3339 time" -F "expected string to be RFC 5322 email" -F "expected string to be RFC 5890 hostname" -F "expected string to be RFC 2673 ipv4" -F "expected string to be RFC 2373 ipv6" -F "expected string to be RFC 3986 uri" -F "expected string to be RFC 4122 uuid" -F "expected string to be RFC 6570 uri-template" -F "expected string to be RFC 6901 json-pointer" -F "expected string to be RFC 6901 relative-json-pointer" -F "expected string to be regex" -F "expected value to match at least one schema but matched none" -F "expected value to not match schema" -F "expected boolean" -F "expected number" -F "expected string" -F "expected string to be base64 encoded" -F "expected array" -F "expected object" -F "expected array items to be unique" -F "expected value to be one of" -F "expected number >=" -F "expected number >" -F "expected number <=" -F "expected number <" -F "expected number to be a multiple of" -F "expected length >=" -F "expected length <=" -F "expected string to be" -F "expected string to match pattern" -F "expected array length >=" -F "expected array length <=" -F "expected object with at least" -F "expected object with at most" -F "expected required property" -F "expected property to be present when"

Length of output: 4182


Script:

#!/bin/bash
# Description: Verify there are no hardcoded validation error messages in the codebase.

# Test: Search for hardcoded strings that match the patterns of the validation error messages.
rg --type go -e "unexpected property" -e "expected string to be RFC 3339 date-time" -e "expected string to be RFC 1123 date-time" -e "expected string to be RFC 3339 date" -e "expected string to be RFC 3339 time" -e "expected string to be RFC 5322 email" -e "expected string to be RFC 5890 hostname" -e "expected string to be RFC 2673 ipv4" -e "expected string to be RFC 2373 ipv6" -e "expected string to be RFC 3986 uri" -e "expected string to be RFC 4122 uuid" -e "expected string to be RFC 6570 uri-template" -e "expected string to be RFC 6901 json-pointer" -e "expected string to be RFC 6901 relative-json-pointer" -e "expected string to be regex" -e "expected value to match at least one schema but matched none" -e "expected value to not match schema" -e "expected boolean" -e "expected number" -e "expected string" -e "expected string to be base64 encoded" -e "expected array" -e "expected object" -e "expected array items to be unique" -e "expected value to be one of" -e "expected number >=" -e "expected number >" -e "expected number <=" -e "expected number <" -e "expected number to be a multiple of" -e "expected length >=" -e "expected length <=" -e "expected string to be" -e "expected string to match pattern" -e "expected array length >=" -e "expected array length <=" -e "expected object with at least" -e "expected object with at most" -e "expected required property" -e "expected property to be present when"

Length of output: 10802

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 9ff9231 and 5ece0ad.

Files selected for processing (3)
  • schema.go (3 hunks)
  • validate.go (15 hunks)
  • validation/messages.go (1 hunks)
Files skipped from review as they are similar to previous changes (2)
  • schema.go
  • validate.go

@smacker smacker force-pushed the allow-override-errors branch from 5ece0ad to 71fac32 Compare July 26, 2024 03:36
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: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 5ece0ad and 71fac32.

Files selected for processing (5)
  • error.go (1 hunks)
  • schema.go (3 hunks)
  • validate.go (14 hunks)
  • validate_test.go (1 hunks)
  • validation/messages.go (1 hunks)
Files skipped from review as they are similar to previous changes (2)
  • schema.go
  • validate.go
Additional comments not posted (2)
error.go (1)

366-367: LGTM! Ensure proper usage of ErrorFormatter.

The new variable ErrorFormatter is correctly defined and initialized with fmt.Sprintf. This enhances the flexibility of error message formatting.

However, ensure that all usages of fmt.Sprintf for error messages are updated to use ErrorFormatter if applicable.

validate_test.go (1)

1303-1323: LGTM! Ensure comprehensive test coverage.

The new test function TestValidateCustomFormatter is correctly defined and effectively tests the custom error formatter by setting a custom formatter and validating an email format.

However, ensure that other validation scenarios are also covered by tests to comprehensively test the custom error formatter.

validation/messages.go Show resolved Hide resolved
@smacker
Copy link
Contributor Author

smacker commented Jul 26, 2024

I want the tests/coverage checks to pass but otherwise this looks mostly ready.

I added a test for the new code but I believe it won't improve the report from codecov.
The few lines that are currently not covered don't change in the PR but because the total number of lines has changed it reports the drop in coverage (I guess?).

I would really appreciate if you could help me to understand how to resolve this issue.
Or I can just add a test to unrelated code, it would bump the coverage a little and probably make codecov happy.

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