// 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 }