Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
157
cloud/maplefile-backend/pkg/maplefile/client/errors.go
Normal file
157
cloud/maplefile-backend/pkg/maplefile/client/errors.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// Package client provides a Go SDK for interacting with the MapleFile API.
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProblemDetail represents an RFC 9457 problem detail response from the API.
|
||||
type ProblemDetail struct {
|
||||
Type string `json:"type"`
|
||||
Status int `json:"status"`
|
||||
Title string `json:"title"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
Instance string `json:"instance,omitempty"`
|
||||
Errors map[string]string `json:"errors,omitempty"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TraceID string `json:"trace_id,omitempty"`
|
||||
}
|
||||
|
||||
// APIError wraps ProblemDetail for the error interface.
|
||||
type APIError struct {
|
||||
ProblemDetail
|
||||
}
|
||||
|
||||
// Error returns a formatted error message from the ProblemDetail.
|
||||
func (e *APIError) Error() string {
|
||||
var errMsg strings.Builder
|
||||
|
||||
if e.Detail != "" {
|
||||
errMsg.WriteString(e.Detail)
|
||||
} else {
|
||||
errMsg.WriteString(e.Title)
|
||||
}
|
||||
|
||||
if len(e.Errors) > 0 {
|
||||
errMsg.WriteString("\n\nValidation errors:")
|
||||
for field, message := range e.Errors {
|
||||
errMsg.WriteString(fmt.Sprintf("\n - %s: %s", field, message))
|
||||
}
|
||||
}
|
||||
|
||||
return errMsg.String()
|
||||
}
|
||||
|
||||
// StatusCode returns the HTTP status code from the error.
|
||||
func (e *APIError) StatusCode() int {
|
||||
return e.Status
|
||||
}
|
||||
|
||||
// GetValidationErrors returns the validation errors map.
|
||||
func (e *APIError) GetValidationErrors() map[string]string {
|
||||
return e.Errors
|
||||
}
|
||||
|
||||
// GetFieldError returns the error message for a specific field, or empty string if not found.
|
||||
func (e *APIError) GetFieldError(field string) string {
|
||||
if e.Errors == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Errors[field]
|
||||
}
|
||||
|
||||
// HasFieldError checks if a specific field has a validation error.
|
||||
func (e *APIError) HasFieldError(field string) bool {
|
||||
if e.Errors == nil {
|
||||
return false
|
||||
}
|
||||
_, exists := e.Errors[field]
|
||||
return exists
|
||||
}
|
||||
|
||||
// IsNotFound checks if the error is a 404 Not Found error.
|
||||
func IsNotFound(err error) bool {
|
||||
if apiErr, ok := err.(*APIError); ok {
|
||||
return apiErr.Status == 404
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnauthorized checks if the error is a 401 Unauthorized error.
|
||||
func IsUnauthorized(err error) bool {
|
||||
if apiErr, ok := err.(*APIError); ok {
|
||||
return apiErr.Status == 401
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsForbidden checks if the error is a 403 Forbidden error.
|
||||
func IsForbidden(err error) bool {
|
||||
if apiErr, ok := err.(*APIError); ok {
|
||||
return apiErr.Status == 403
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsValidationError checks if the error has validation errors.
|
||||
func IsValidationError(err error) bool {
|
||||
if apiErr, ok := err.(*APIError); ok {
|
||||
return len(apiErr.Errors) > 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsConflict checks if the error is a 409 Conflict error.
|
||||
func IsConflict(err error) bool {
|
||||
if apiErr, ok := err.(*APIError); ok {
|
||||
return apiErr.Status == 409
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTooManyRequests checks if the error is a 429 Too Many Requests error.
|
||||
func IsTooManyRequests(err error) bool {
|
||||
if apiErr, ok := err.(*APIError); ok {
|
||||
return apiErr.Status == 429
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseErrorResponse attempts to parse an error response body into an APIError.
|
||||
// It tries RFC 9457 format first, then falls back to legacy format.
|
||||
//
|
||||
// Note: RFC 9457 specifies that error responses should use Content-Type: application/problem+json,
|
||||
// but we parse based on the response structure rather than Content-Type for maximum compatibility.
|
||||
func parseErrorResponse(body []byte, statusCode int) error {
|
||||
// Try to parse as RFC 9457 ProblemDetail
|
||||
// The presence of the "type" field distinguishes RFC 9457 from legacy responses
|
||||
var problem ProblemDetail
|
||||
if err := json.Unmarshal(body, &problem); err == nil && problem.Type != "" {
|
||||
return &APIError{ProblemDetail: problem}
|
||||
}
|
||||
|
||||
// Fallback for non-RFC 9457 errors
|
||||
var errorResponse map[string]interface{}
|
||||
if err := json.Unmarshal(body, &errorResponse); err == nil {
|
||||
if errMsg, ok := errorResponse["message"].(string); ok {
|
||||
return &APIError{
|
||||
ProblemDetail: ProblemDetail{
|
||||
Status: statusCode,
|
||||
Title: errMsg,
|
||||
Detail: errMsg,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: return raw body as error
|
||||
return &APIError{
|
||||
ProblemDetail: ProblemDetail{
|
||||
Status: statusCode,
|
||||
Title: fmt.Sprintf("HTTP %d", statusCode),
|
||||
Detail: string(body),
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue