157 lines
4.2 KiB
Go
157 lines
4.2 KiB
Go
// 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),
|
|
},
|
|
}
|
|
}
|