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, ¬Enabled): 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}, } 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 }