2
0
Files
gitcaddy-server/modules/errors/codes.go
logikonline 6bc3693cef feat(blog): add series support and v2 API endpoints
Adds blog series field to group related posts together. Implements v2 API endpoints for listing, creating, updating, and deleting blog posts with proper error codes. Adds series filtering to explore page and sitemap support with pagination. Includes BlogPostV2 structs with author/repo references, HTML URLs, and content rendering. Updates editor UI with series input field.
2026-02-02 13:04:30 -05:00

323 lines
12 KiB
Go

// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package errors
import "net/http"
// ErrorCode represents a machine-readable error code
type ErrorCode string
// Authentication errors (AUTH_)
const (
AuthTokenMissing ErrorCode = "AUTH_TOKEN_MISSING"
AuthTokenInvalid ErrorCode = "AUTH_TOKEN_INVALID"
AuthTokenExpired ErrorCode = "AUTH_TOKEN_EXPIRED"
AuthScopeInsufficient ErrorCode = "AUTH_SCOPE_INSUFFICIENT"
Auth2FARequired ErrorCode = "AUTH_2FA_REQUIRED"
AuthInvalidCredentials ErrorCode = "AUTH_INVALID_CREDENTIALS"
)
// Permission errors (PERM_)
const (
PermRepoReadDenied ErrorCode = "PERM_REPO_READ_DENIED"
PermRepoWriteDenied ErrorCode = "PERM_REPO_WRITE_DENIED"
PermRepoAdminRequired ErrorCode = "PERM_REPO_ADMIN_REQUIRED"
PermOrgMemberRequired ErrorCode = "PERM_ORG_MEMBER_REQUIRED"
PermOrgAdminRequired ErrorCode = "PERM_ORG_ADMIN_REQUIRED"
PermActionDenied ErrorCode = "PERM_ACTION_DENIED"
)
// Repository errors (REPO_)
const (
RepoNotFound ErrorCode = "REPO_NOT_FOUND"
RepoArchived ErrorCode = "REPO_ARCHIVED"
RepoDisabled ErrorCode = "REPO_DISABLED"
RepoTransferPending ErrorCode = "REPO_TRANSFER_PENDING"
RepoEmpty ErrorCode = "REPO_EMPTY"
RepoAlreadyExists ErrorCode = "REPO_ALREADY_EXISTS"
)
// File errors (FILE_)
const (
FileNotFound ErrorCode = "FILE_NOT_FOUND"
FileTooLarge ErrorCode = "FILE_TOO_LARGE"
FileConflict ErrorCode = "FILE_CONFLICT"
FileBinary ErrorCode = "FILE_BINARY"
FileTypeError ErrorCode = "FILE_TYPE_NOT_ALLOWED"
)
// Git errors (GIT_)
const (
GitRefNotFound ErrorCode = "GIT_REF_NOT_FOUND"
GitMergeConflict ErrorCode = "GIT_MERGE_CONFLICT"
GitBranchNotFound ErrorCode = "GIT_BRANCH_NOT_FOUND"
GitTagNotFound ErrorCode = "GIT_TAG_NOT_FOUND"
GitCommitNotFound ErrorCode = "GIT_COMMIT_NOT_FOUND"
GitPushRejected ErrorCode = "GIT_PUSH_REJECTED"
)
// Rate limiting errors (RATE_)
const (
RateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED"
RateQuotaExhausted ErrorCode = "RATE_QUOTA_EXHAUSTED"
)
// Validation errors (VAL_)
const (
ValInvalidInput ErrorCode = "VAL_INVALID_INPUT"
ValMissingField ErrorCode = "VAL_MISSING_FIELD"
ValInvalidName ErrorCode = "VAL_INVALID_NAME"
ValNameTooLong ErrorCode = "VAL_NAME_TOO_LONG"
ValInvalidEmail ErrorCode = "VAL_INVALID_EMAIL"
ValDuplicateName ErrorCode = "VAL_DUPLICATE_NAME"
ValInvalidFormat ErrorCode = "VAL_INVALID_FORMAT"
ValidationFailed ErrorCode = "VALIDATION_FAILED"
)
// General errors
const (
InternalError ErrorCode = "INTERNAL_ERROR"
PermAccessDenied ErrorCode = "ACCESS_DENIED"
RefNotFound ErrorCode = "REF_NOT_FOUND"
)
// Upload errors (UPLOAD_)
const (
UploadSessionNotFound ErrorCode = "UPLOAD_SESSION_NOT_FOUND"
UploadSessionExpired ErrorCode = "UPLOAD_SESSION_EXPIRED"
UploadChunkInvalid ErrorCode = "UPLOAD_CHUNK_INVALID"
UploadChunkSizeMismatch ErrorCode = "UPLOAD_CHUNK_SIZE_MISMATCH"
UploadChecksumMismatch ErrorCode = "UPLOAD_CHECKSUM_MISMATCH"
UploadIncomplete ErrorCode = "UPLOAD_INCOMPLETE"
UploadFileTooLarge ErrorCode = "UPLOAD_FILE_TOO_LARGE"
)
// Resource errors (RESOURCE_)
const (
ResourceNotFound ErrorCode = "RESOURCE_NOT_FOUND"
ResourceConflict ErrorCode = "RESOURCE_CONFLICT"
ResourceGone ErrorCode = "RESOURCE_GONE"
)
// Server errors (SERVER_)
const (
ServerInternal ErrorCode = "SERVER_INTERNAL_ERROR"
ServerUnavailable ErrorCode = "SERVER_UNAVAILABLE"
ServerTimeout ErrorCode = "SERVER_TIMEOUT"
)
// User errors (USER_)
const (
UserNotFound ErrorCode = "USER_NOT_FOUND"
UserAlreadyExists ErrorCode = "USER_ALREADY_EXISTS"
UserInactive ErrorCode = "USER_INACTIVE"
UserProhibitLogin ErrorCode = "USER_PROHIBIT_LOGIN"
)
// Organization errors (ORG_)
const (
OrgNotFound ErrorCode = "ORG_NOT_FOUND"
OrgAlreadyExists ErrorCode = "ORG_ALREADY_EXISTS"
)
// Issue errors (ISSUE_)
const (
IssueNotFound ErrorCode = "ISSUE_NOT_FOUND"
IssueClosed ErrorCode = "ISSUE_CLOSED"
IssueLocked ErrorCode = "ISSUE_LOCKED"
)
// Pull Request errors (PR_)
const (
PRNotFound ErrorCode = "PR_NOT_FOUND"
PRAlreadyMerged ErrorCode = "PR_ALREADY_MERGED"
PRNotMergeable ErrorCode = "PR_NOT_MERGEABLE"
PRWorkInProgress ErrorCode = "PR_WORK_IN_PROGRESS"
)
// Release errors (RELEASE_)
const (
ReleaseNotFound ErrorCode = "RELEASE_NOT_FOUND"
ReleaseTagExists ErrorCode = "RELEASE_TAG_EXISTS"
ReleaseIsDraft ErrorCode = "RELEASE_IS_DRAFT"
)
// Webhook errors (WEBHOOK_)
const (
WebhookNotFound ErrorCode = "WEBHOOK_NOT_FOUND"
WebhookDeliveryFail ErrorCode = "WEBHOOK_DELIVERY_FAILED"
)
// Wiki errors (WIKI_)
const (
WikiPageNotFound ErrorCode = "WIKI_PAGE_NOT_FOUND"
WikiPageAlreadyExists ErrorCode = "WIKI_PAGE_ALREADY_EXISTS"
WikiReservedName ErrorCode = "WIKI_RESERVED_NAME"
WikiDisabled ErrorCode = "WIKI_DISABLED"
)
// Blog errors (BLOG_)
const (
BlogPostNotFound ErrorCode = "BLOG_POST_NOT_FOUND"
BlogDisabled ErrorCode = "BLOG_DISABLED"
)
// errorInfo contains metadata about an error code
type errorInfo struct {
Message string
HTTPStatus int
}
// errorCatalog maps error codes to their metadata
var errorCatalog = map[ErrorCode]errorInfo{
// Auth errors
AuthTokenMissing: {"No authentication token provided", http.StatusUnauthorized},
AuthTokenInvalid: {"Token is malformed or invalid", http.StatusUnauthorized},
AuthTokenExpired: {"Token has expired", http.StatusUnauthorized},
AuthScopeInsufficient: {"Token lacks required scope", http.StatusForbidden},
Auth2FARequired: {"Two-factor authentication required", http.StatusUnauthorized},
AuthInvalidCredentials: {"Invalid username or password", http.StatusUnauthorized},
// Permission errors
PermRepoReadDenied: {"Cannot read repository", http.StatusForbidden},
PermRepoWriteDenied: {"Cannot write to repository", http.StatusForbidden},
PermRepoAdminRequired: {"Repository admin access required", http.StatusForbidden},
PermOrgMemberRequired: {"Must be organization member", http.StatusForbidden},
PermOrgAdminRequired: {"Organization admin access required", http.StatusForbidden},
PermActionDenied: {"Permission denied for this action", http.StatusForbidden},
// Repository errors
RepoNotFound: {"Repository does not exist", http.StatusNotFound},
RepoArchived: {"Repository is archived", http.StatusForbidden},
RepoDisabled: {"Repository is disabled", http.StatusForbidden},
RepoTransferPending: {"Repository has pending transfer", http.StatusConflict},
RepoEmpty: {"Repository is empty", http.StatusUnprocessableEntity},
RepoAlreadyExists: {"Repository already exists", http.StatusConflict},
// File errors
FileNotFound: {"File does not exist", http.StatusNotFound},
FileTooLarge: {"File exceeds size limit", http.StatusRequestEntityTooLarge},
FileConflict: {"File was modified (SHA mismatch)", http.StatusConflict},
FileBinary: {"Cannot perform text operation on binary file", http.StatusBadRequest},
FileTypeError: {"File type not allowed", http.StatusBadRequest},
// Git errors
GitRefNotFound: {"Git reference not found", http.StatusNotFound},
GitMergeConflict: {"Merge conflict detected", http.StatusConflict},
GitBranchNotFound: {"Branch not found", http.StatusNotFound},
GitTagNotFound: {"Tag not found", http.StatusNotFound},
GitCommitNotFound: {"Commit not found", http.StatusNotFound},
GitPushRejected: {"Push rejected", http.StatusForbidden},
// Rate limiting errors
RateLimitExceeded: {"API rate limit exceeded", http.StatusTooManyRequests},
RateQuotaExhausted: {"Rate quota exhausted", http.StatusTooManyRequests},
// Validation errors
ValInvalidInput: {"Invalid input provided", http.StatusBadRequest},
ValMissingField: {"Required field is missing", http.StatusBadRequest},
ValInvalidName: {"Name contains invalid characters", http.StatusBadRequest},
ValNameTooLong: {"Name exceeds maximum length", http.StatusBadRequest},
ValInvalidEmail: {"Invalid email address", http.StatusBadRequest},
ValDuplicateName: {"Name already exists", http.StatusConflict},
ValInvalidFormat: {"Invalid format", http.StatusBadRequest},
ValidationFailed: {"Validation failed", http.StatusBadRequest},
// General errors
InternalError: {"Internal server error", http.StatusInternalServerError},
PermAccessDenied: {"Access denied", http.StatusForbidden},
RefNotFound: {"Reference not found", http.StatusNotFound},
// Upload errors
UploadSessionNotFound: {"Upload session does not exist", http.StatusNotFound},
UploadSessionExpired: {"Upload session has expired", http.StatusGone},
UploadChunkInvalid: {"Chunk number out of range", http.StatusBadRequest},
UploadChunkSizeMismatch: {"Chunk size doesn't match expected", http.StatusBadRequest},
UploadChecksumMismatch: {"File checksum verification failed", http.StatusBadRequest},
UploadIncomplete: {"Not all chunks have been uploaded", http.StatusBadRequest},
UploadFileTooLarge: {"File exceeds maximum upload size", http.StatusRequestEntityTooLarge},
// Resource errors
ResourceNotFound: {"Resource not found", http.StatusNotFound},
ResourceConflict: {"Resource conflict", http.StatusConflict},
ResourceGone: {"Resource no longer available", http.StatusGone},
// Server errors
ServerInternal: {"Internal server error", http.StatusInternalServerError},
ServerUnavailable: {"Service temporarily unavailable", http.StatusServiceUnavailable},
ServerTimeout: {"Request timeout", http.StatusGatewayTimeout},
// User errors
UserNotFound: {"User not found", http.StatusNotFound},
UserAlreadyExists: {"User already exists", http.StatusConflict},
UserInactive: {"User account is inactive", http.StatusForbidden},
UserProhibitLogin: {"User is not allowed to login", http.StatusForbidden},
// Organization errors
OrgNotFound: {"Organization not found", http.StatusNotFound},
OrgAlreadyExists: {"Organization already exists", http.StatusConflict},
// Issue errors
IssueNotFound: {"Issue not found", http.StatusNotFound},
IssueClosed: {"Issue is closed", http.StatusUnprocessableEntity},
IssueLocked: {"Issue is locked", http.StatusForbidden},
// Pull Request errors
PRNotFound: {"Pull request not found", http.StatusNotFound},
PRAlreadyMerged: {"Pull request already merged", http.StatusConflict},
PRNotMergeable: {"Pull request is not mergeable", http.StatusConflict},
PRWorkInProgress: {"Pull request is marked as work in progress", http.StatusUnprocessableEntity},
// Release errors
ReleaseNotFound: {"Release not found", http.StatusNotFound},
ReleaseTagExists: {"Release tag already exists", http.StatusConflict},
ReleaseIsDraft: {"Release is a draft", http.StatusUnprocessableEntity},
// Webhook errors
WebhookNotFound: {"Webhook not found", http.StatusNotFound},
WebhookDeliveryFail: {"Webhook delivery failed", http.StatusBadGateway},
// Wiki errors
WikiPageNotFound: {"Wiki page not found", http.StatusNotFound},
WikiPageAlreadyExists: {"Wiki page already exists", http.StatusConflict},
WikiReservedName: {"Wiki page name is reserved", http.StatusBadRequest},
WikiDisabled: {"Wiki is disabled for this repository", http.StatusForbidden},
// Blog errors
BlogPostNotFound: {"Blog post not found", http.StatusNotFound},
BlogDisabled: {"Blogs are disabled", http.StatusForbidden},
}
// Message returns the human-readable message for an error code
func (e ErrorCode) Message() string {
if info, ok := errorCatalog[e]; ok {
return info.Message
}
return string(e)
}
// HTTPStatus returns the HTTP status code for an error code
func (e ErrorCode) HTTPStatus() int {
if info, ok := errorCatalog[e]; ok {
return info.HTTPStatus
}
return http.StatusInternalServerError
}
// String returns the error code as a string
func (e ErrorCode) String() string {
return string(e)
}
// Error implements the error interface
func (e ErrorCode) Error() string {
return e.Message()
}
// IsValid returns true if the error code is registered in the catalog
func (e ErrorCode) IsValid() bool {
_, ok := errorCatalog[e]
return ok
}