-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: RFC 9457 compatible #10
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
90bc8c2
feat: RFC 9457 compatible
rluders 922048f
fix: test content-type (resolve: discussion_r1936938314)
rluders da0b909
fix: validation as package var (resolve: discussion_r1936947547)
rluders 0c031ff
fix: handle response encode error (resolve discussion_r1936947554)
rluders 03f137f
feat: expanding problem details
rluders 6301ca8
fix: adjust typo and handle mutex
rluders d9b8f7a
fix: use default title based on status code
rluders a36da0d
fix: mu.RLock for getters
rluders File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| package httpsuite | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "sync" | ||
| ) | ||
|
|
||
| const BlankUrl = "about:blank" | ||
|
|
||
| var ( | ||
| mu sync.RWMutex | ||
| problemBaseURL = BlankUrl | ||
| errorTypePaths = map[string]string{ | ||
| "validation_error": "/errors/validation-error", | ||
| "not_found_error": "/errors/not-found", | ||
| "server_error": "/errors/server-error", | ||
| "bad_request_error": "/errors/bad-request", | ||
| } | ||
| ) | ||
|
|
||
| // ProblemDetails conforms to RFC 9457, providing a standard format for describing errors in HTTP APIs. | ||
| type ProblemDetails struct { | ||
| Type string `json:"type"` // A URI reference identifying the problem type. | ||
| Title string `json:"title"` // A short, human-readable summary of the problem. | ||
| Status int `json:"status"` // The HTTP status code. | ||
| Detail string `json:"detail,omitempty"` // Detailed explanation of the problem. | ||
| Instance string `json:"instance,omitempty"` // A URI reference identifying the specific instance of the problem. | ||
| Extensions map[string]interface{} `json:"extensions,omitempty"` // Custom fields for additional details. | ||
| } | ||
|
|
||
| // NewProblemDetails creates a ProblemDetails instance with standard fields. | ||
| func NewProblemDetails(status int, problemType, title, detail string) *ProblemDetails { | ||
| if problemType == "" { | ||
| problemType = BlankUrl | ||
| } | ||
| if title == "" { | ||
| title = http.StatusText(status) | ||
| if title == "" { | ||
| title = "Unknown error" | ||
| } | ||
| } | ||
| return &ProblemDetails{ | ||
| Type: problemType, | ||
| Title: title, | ||
| Status: status, | ||
| Detail: detail, | ||
| } | ||
| } | ||
|
|
||
| // SetProblemBaseURL configures the base URL used in the "type" field for ProblemDetails. | ||
| // | ||
| // This function allows applications using httpsuite to provide a custom domain and structure | ||
| // for error documentation URLs. By setting this base URL, the library can generate meaningful | ||
| // and discoverable problem types. | ||
| // | ||
| // Parameters: | ||
| // - baseURL: The base URL where error documentation is hosted (e.g., "https://api.mycompany.com"). | ||
| // | ||
| // Example usage: | ||
| // | ||
| // httpsuite.SetProblemBaseURL("https://api.mycompany.com") | ||
| // | ||
| // Once configured, generated ProblemDetails will include a "type" such as: | ||
| // | ||
| // "https://api.mycompany.com/errors/validation-error" | ||
| // | ||
| // If the base URL is not set, the default value for the "type" field will be "about:blank". | ||
| func SetProblemBaseURL(baseURL string) { | ||
| mu.Lock() | ||
| defer mu.Unlock() | ||
| problemBaseURL = baseURL | ||
| } | ||
|
|
||
| // SetProblemErrorTypePath sets or updates the path for a specific error type. | ||
| // | ||
| // This allows applications to define custom paths for error documentation. | ||
| // | ||
| // Parameters: | ||
| // - errorType: The unique key identifying the error type (e.g., "validation_error"). | ||
| // - path: The path under the base URL where the error documentation is located. | ||
| // | ||
| // Example usage: | ||
| // | ||
| // httpsuite.SetProblemErrorTypePath("validation_error", "/errors/validation-error") | ||
| // | ||
| // After setting this path, the generated problem type for "validation_error" will be: | ||
| // | ||
| // "https://api.mycompany.com/errors/validation-error" | ||
| func SetProblemErrorTypePath(errorType, path string) { | ||
| mu.Lock() | ||
| defer mu.Unlock() | ||
| errorTypePaths[errorType] = path | ||
| } | ||
|
|
||
| // SetProblemErrorTypePaths sets or updates multiple paths for different error types. | ||
| // | ||
| // This allows applications to define multiple custom paths at once. | ||
| // | ||
| // Parameters: | ||
| // - paths: A map of error types to paths (e.g., {"validation_error": "/errors/validation-error"}). | ||
| // | ||
| // Example usage: | ||
| // | ||
| // paths := map[string]string{ | ||
| // "validation_error": "/errors/validation-error", | ||
| // "not_found_error": "/errors/not-found", | ||
| // } | ||
| // httpsuite.SetProblemErrorTypePaths(paths) | ||
| // | ||
| // This method overwrites any existing paths with the same keys. | ||
| func SetProblemErrorTypePaths(paths map[string]string) { | ||
| mu.Lock() | ||
| defer mu.Unlock() | ||
| for errorType, path := range paths { | ||
| errorTypePaths[errorType] = path | ||
| } | ||
| } | ||
|
|
||
| // GetProblemTypeURL get the full problem type URL based on the error type. | ||
| // | ||
| // If the error type is not found in the predefined paths, it returns a default unknown error path. | ||
| // | ||
| // Parameters: | ||
| // - errorType: The unique key identifying the error type (e.g., "validation_error"). | ||
| // | ||
| // Example usage: | ||
| // | ||
| // problemTypeURL := GetProblemTypeURL("validation_error") | ||
| func GetProblemTypeURL(errorType string) string { | ||
| mu.RLock() | ||
| defer mu.RUnlock() | ||
| if path, exists := errorTypePaths[errorType]; exists { | ||
| return getProblemBaseURL() + path | ||
| } | ||
|
|
||
| return BlankUrl | ||
| } | ||
|
|
||
| // getProblemBaseURL just return the baseURL if it isn't "about:blank" | ||
| func getProblemBaseURL() string { | ||
| mu.RLock() | ||
| defer mu.RUnlock() | ||
| if problemBaseURL == BlankUrl { | ||
| return "" | ||
| } | ||
| return problemBaseURL | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| package httpsuite | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func Test_SetProblemBaseURL(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| input string | ||
| expected string | ||
| }{ | ||
| { | ||
| name: "Set valid base URL", | ||
| input: "https://api.example.com", | ||
| expected: "https://api.example.com", | ||
| }, | ||
| { | ||
| name: "Set base URL to blank", | ||
| input: BlankUrl, | ||
| expected: BlankUrl, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| SetProblemBaseURL(tt.input) | ||
| assert.Equal(t, tt.expected, problemBaseURL) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func Test_SetProblemErrorTypePath(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| errorKey string | ||
| path string | ||
| expected string | ||
| }{ | ||
| { | ||
| name: "Set custom error path", | ||
| errorKey: "custom_error", | ||
| path: "/errors/custom-error", | ||
| expected: "/errors/custom-error", | ||
| }, | ||
| { | ||
| name: "Override existing path", | ||
| errorKey: "validation_error", | ||
| path: "/errors/new-validation-error", | ||
| expected: "/errors/new-validation-error", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| SetProblemErrorTypePath(tt.errorKey, tt.path) | ||
| assert.Equal(t, tt.expected, errorTypePaths[tt.errorKey]) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func Test_GetProblemTypeURL(t *testing.T) { | ||
| // Setup initial state | ||
| SetProblemBaseURL("https://api.example.com") | ||
| SetProblemErrorTypePath("validation_error", "/errors/validation-error") | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| errorType string | ||
| expectedURL string | ||
| }{ | ||
| { | ||
| name: "Valid error type", | ||
| errorType: "validation_error", | ||
| expectedURL: "https://api.example.com/errors/validation-error", | ||
| }, | ||
| { | ||
| name: "Unknown error type", | ||
| errorType: "unknown_error", | ||
| expectedURL: BlankUrl, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| result := GetProblemTypeURL(tt.errorType) | ||
| assert.Equal(t, tt.expectedURL, result) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func Test_getProblemBaseURL(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| baseURL string | ||
| expectedResult string | ||
| }{ | ||
| { | ||
| name: "Base URL is set", | ||
| baseURL: "https://api.example.com", | ||
| expectedResult: "https://api.example.com", | ||
| }, | ||
| { | ||
| name: "Base URL is about:blank", | ||
| baseURL: BlankUrl, | ||
| expectedResult: "", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| problemBaseURL = tt.baseURL | ||
| assert.Equal(t, tt.expectedResult, getProblemBaseURL()) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func Test_NewProblemDetails(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| status int | ||
| problemType string | ||
| title string | ||
| detail string | ||
| expectedType string | ||
| }{ | ||
| { | ||
| name: "All fields provided", | ||
| status: 400, | ||
| problemType: "https://api.example.com/errors/validation-error", | ||
| title: "Validation Error", | ||
| detail: "Invalid input", | ||
| expectedType: "https://api.example.com/errors/validation-error", | ||
| }, | ||
| { | ||
| name: "Empty problem type", | ||
| status: 404, | ||
| problemType: "", | ||
| title: "Not Found", | ||
| detail: "The requested resource was not found", | ||
| expectedType: BlankUrl, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| details := NewProblemDetails(tt.status, tt.problemType, tt.title, tt.detail) | ||
| assert.Equal(t, tt.status, details.Status) | ||
| assert.Equal(t, tt.title, details.Title) | ||
| assert.Equal(t, tt.detail, details.Detail) | ||
| assert.Equal(t, tt.expectedType, details.Type) | ||
| }) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.