Files
RenewCMS/api/pkg/articleerr/articleErr.go
T

101 lines
4.1 KiB
Go

package articleerr
import (
"RenewCMS/api/pkg/apperr"
domain "RenewCMS/internal/domain/article"
"RenewCMS/internal/domain/author"
"RenewCMS/internal/domain/image"
"RenewCMS/internal/domain/language"
"RenewCMS/internal/domain/localization"
"RenewCMS/internal/domain/site"
"RenewCMS/internal/domain/transaction"
"errors"
"net/http"
"slices"
"strconv"
"strings"
)
func Write(w http.ResponseWriter, err error) bool {
var conflict *domain.RevisionConflictError
var existing *domain.LanguageExistsError
var notEnabled *domain.LanguageNotEnabledError
var addresses *domain.SlugConflictError
var taken *domain.SlugTakenError
switch {
case errors.As(err, &conflict):
w.Header().Set(apperr.CurrentRevisionHeader, strconv.FormatUint(uint64(conflict.Current), 10))
http.Error(w, apperr.RevisionConflictErrorMessage, http.StatusConflict)
case errors.As(err, &existing):
w.Header().Set(apperr.ExistingArticleHeader, strconv.FormatUint(uint64(existing.ArticleID), 10))
http.Error(w, apperr.LanguageExistsErrorMessage, http.StatusConflict)
case errors.As(err, &notEnabled):
if len(notEnabled.Missing) > 0 {
w.Header().Set(apperr.MissingLanguagesHeader, strings.Join(notEnabled.Missing, ","))
}
http.Error(w, apperr.LanguageNotEnabledErrorMessage, http.StatusUnprocessableEntity)
case errors.As(err, &addresses):
if len(addresses.ArticleIDs) > 0 {
w.Header().Set(apperr.ConflictingArticlesHeader, joinIDs(addresses.ArticleIDs))
}
http.Error(w, apperr.SlugConflictErrorMessage, http.StatusConflict)
case errors.Is(err, domain.ErrSlugTaken):
if errors.As(err, &taken) && taken.Suggestion != "" {
w.Header().Set(apperr.SlugSuggestionHeader, taken.Suggestion)
}
http.Error(w, apperr.ArticleSlugTakenErrorMessage, http.StatusConflict)
default:
return writeSentinel(w, err)
}
return true
}
func joinIDs(ids []uint32) string {
parts := make([]string, 0, len(ids))
for _, id := range ids {
parts = append(parts, strconv.FormatUint(uint64(id), 10))
}
return strings.Join(parts, ",")
}
type sentinelResponse struct {
cause error
message string
status int
}
var articleSentinels = []sentinelResponse{
{domain.ErrInvalidTitle, apperr.ArticleTitleErrorMessage, http.StatusBadRequest},
{domain.ErrArticleNotFound, apperr.ArticleNotFoundErrorMessage, http.StatusNotFound},
{site.ErrSiteNotFound, apperr.SiteNotFoundErrorMessage, http.StatusNotFound},
{author.ErrAuthorNotFound, apperr.AuthorNotFoundErrorMessage, http.StatusNotFound},
{domain.ErrInvalidSlug, apperr.InvalidSlugErrorMessage, http.StatusBadRequest},
{domain.ErrInvalidCanonicalURL, apperr.InvalidURLErrorMessage, http.StatusUnprocessableEntity},
{image.ErrImageNotFound, apperr.ArticleImageNotFoundErrorMessage, http.StatusBadRequest},
}
var localizationSentinels = []sentinelResponse{
{domain.ErrLanguageUnavailable, apperr.LanguageUnavailableErrorMessage, http.StatusConflict},
{domain.ErrLanguageTaken, apperr.LanguageUnavailableErrorMessage, http.StatusConflict},
{domain.ErrLanguageDisabled, apperr.LanguageDisabledErrorMessage, http.StatusUnprocessableEntity},
{domain.ErrSourceUnavailable, apperr.SourceUnavailableErrorMessage, http.StatusNotFound},
{domain.ErrSourceOtherStory, apperr.SourceOtherStoryErrorMessage, http.StatusUnprocessableEntity},
{domain.ErrInvalidTranslation, apperr.BodyErrorMessage, http.StatusBadRequest},
{domain.ErrStoryMoveRequired, apperr.StoryMoveRequiredErrorMessage, http.StatusConflict},
{localization.ErrSetupRequired, apperr.SetupRequiredErrorMessage, http.StatusConflict},
{language.ErrInvalidLanguage, apperr.InvalidLanguageErrorMessage, http.StatusUnprocessableEntity},
{language.ErrUnsupportedLanguage, apperr.UnsupportedLanguageErrorMessage, http.StatusUnprocessableEntity},
{transaction.ErrLockContention, apperr.StoryBusyErrorMessage, http.StatusConflict},
{domain.ErrTargetSiteDeleted, apperr.TargetSiteDeletedErrorMessage, http.StatusConflict},
}
func writeSentinel(w http.ResponseWriter, err error) bool {
for _, candidate := range slices.Concat(articleSentinels, localizationSentinels) {
if errors.Is(err, candidate.cause) {
http.Error(w, candidate.message, candidate.status)
return true
}
}
return false
}