Files
2026-08-15 07:13:00 +00:00

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
}