85 lines
2.6 KiB
Go
85 lines
2.6 KiB
Go
// Package api holds the wire format shared by pages-server and the pages CLI.
|
|
//
|
|
// It must depend on the standard library only. cmd/pages imports this package,
|
|
// and cmd/pages/deps_test.go fails the build if any server-side dependency
|
|
// (SQLite driver, database/sql, internal/store, ...) reaches the CLI through it.
|
|
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// Code is a machine-readable error code. Clients switch on these; the
|
|
// accompanying message is for humans and may change.
|
|
type Code string
|
|
|
|
const (
|
|
CodeBadRequest Code = "bad_request"
|
|
CodeUnauthorized Code = "unauthorized"
|
|
CodeForbidden Code = "forbidden"
|
|
CodeNotFound Code = "not_found"
|
|
CodeMethodNotAllowed Code = "method_not_allowed"
|
|
CodeConflict Code = "conflict"
|
|
CodePayloadTooLarge Code = "payload_too_large"
|
|
CodeRateLimited Code = "rate_limited"
|
|
CodeInternal Code = "internal"
|
|
CodeUnavailable Code = "unavailable"
|
|
|
|
// Domain-specific codes.
|
|
CodeProjectExists Code = "project_exists"
|
|
CodeInvalidProjectName Code = "invalid_project_name"
|
|
CodeInvalidPath Code = "invalid_path"
|
|
CodeDigestMismatch Code = "digest_mismatch"
|
|
CodeSizeMismatch Code = "size_mismatch"
|
|
CodeDeploymentNotReady Code = "deployment_not_ready"
|
|
CodeDeploymentActive Code = "deployment_active"
|
|
CodeBlobsMissing Code = "blobs_missing"
|
|
CodeLimitExceeded Code = "limit_exceeded"
|
|
)
|
|
|
|
// Error is the body of every non-2xx management API response:
|
|
//
|
|
// {"error":{"code":"deployment_not_ready","message":"...","details":{...}}}
|
|
type Error struct {
|
|
Code Code `json:"code"`
|
|
Message string `json:"message"`
|
|
Details map[string]any `json:"details,omitempty"`
|
|
}
|
|
|
|
// ErrorEnvelope wraps Error so the JSON has a single top-level "error" key.
|
|
type ErrorEnvelope struct {
|
|
Error Error `json:"error"`
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
if e.Message == "" {
|
|
return string(e.Code)
|
|
}
|
|
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
|
}
|
|
|
|
// Errorf builds an *Error with a formatted message.
|
|
func Errorf(code Code, format string, args ...any) *Error {
|
|
return &Error{Code: code, Message: fmt.Sprintf(format, args...)}
|
|
}
|
|
|
|
// WithDetail attaches a detail key and returns the receiver for chaining.
|
|
func (e *Error) WithDetail(key string, val any) *Error {
|
|
if e.Details == nil {
|
|
e.Details = make(map[string]any, 1)
|
|
}
|
|
e.Details[key] = val
|
|
return e
|
|
}
|
|
|
|
// CodeOf reports the Code carried by err, or CodeInternal when err is not an
|
|
// *Error. It unwraps, so a wrapped *Error is still recognised.
|
|
func CodeOf(err error) Code {
|
|
var apiErr *Error
|
|
if errors.As(err, &apiErr) {
|
|
return apiErr.Code
|
|
}
|
|
return CodeInternal
|
|
}
|