This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

appview/xrpc: sentence-case error messages and fix unclaimed-domain 500

The getDomainClaim handler returned 500 for the common no-claim case because
scanClaim yields sql.ErrNoRows; treat that as an empty claim. Also sentence-case
the user-facing error messages across the temp handlers (upper case, full stops).

author
Anirudh Oppiliappan
committer
dawn
date (Jul 31, 2026, 10:57 PM +0300) commit f078649e parent 910bb8bd change-id onqzpmsx
+45 -44
+5 -5
appview/xrpc/account.go
··· 61 61 existing, err := db.GetEmail(x.DB, did, addr) 62 62 if err != nil { 63 63 if errors.Is(err, sql.ErrNoRows) { 64 - writeError(w, xrpcErrorTag("EmailNotFound", "the email address is not associated with this account"), http.StatusNotFound) 64 + writeError(w, xrpcErrorTag("EmailNotFound", "The email address is not associated with this account."), http.StatusNotFound) 65 65 return 66 66 } 67 67 l.Error("failed to get email", "err", err) ··· 69 69 return 70 70 } 71 71 if existing.Primary { 72 - writeError(w, xrpcErrorTag("CannotDeletePrimary", "the primary email address cannot be deleted; set another address as primary first"), http.StatusBadRequest) 72 + writeError(w, xrpcErrorTag("CannotDeletePrimary", "The primary email address cannot be deleted; set another address as primary first."), http.StatusBadRequest) 73 73 return 74 74 } 75 75 ··· 101 101 existing, err := db.GetEmail(x.DB, did, addr) 102 102 if err != nil { 103 103 if errors.Is(err, sql.ErrNoRows) { 104 - writeError(w, xrpcErrorTag("EmailNotFound", "the email address is not associated with this account"), http.StatusNotFound) 104 + writeError(w, xrpcErrorTag("EmailNotFound", "The email address is not associated with this account."), http.StatusNotFound) 105 105 return 106 106 } 107 107 l.Error("failed to get email", "err", err) ··· 109 109 return 110 110 } 111 111 if !existing.Verified { 112 - writeError(w, xrpcErrorTag("EmailNotVerified", "the email address must be verified before it can be made primary"), http.StatusBadRequest) 112 + writeError(w, xrpcErrorTag("EmailNotVerified", "The email address must be verified before it can be made primary."), http.StatusBadRequest) 113 113 return 114 114 } 115 115 ··· 133 133 134 134 primary, err := db.GetPrimaryEmail(x.DB, did) 135 135 if err != nil || primary.Address == "" { 136 - writeError(w, xrpcErrorTag("NoVerifiedEmail", "a primary email address is required to subscribe"), http.StatusBadRequest) 136 + writeError(w, xrpcErrorTag("NoVerifiedEmail", "A primary email address is required to subscribe."), http.StatusBadRequest) 137 137 return 138 138 } 139 139
+4 -4
appview/xrpc/search.go
··· 18 18 l := x.Logger.With("handler", "SearchSearchCode") 19 19 20 20 if x.CodeSearch == nil { 21 - writeError(w, notImplementedError("code search is not configured"), http.StatusNotImplemented) 21 + writeError(w, notImplementedError("Code search is not configured."), http.StatusNotImplemented) 22 22 return 23 23 } 24 24 25 25 q := r.URL.Query() 26 26 rawQuery := strings.TrimSpace(q.Get("q")) 27 27 if rawQuery == "" { 28 - writeError(w, badRequestError("missing required parameter: q"), http.StatusBadRequest) 28 + writeError(w, badRequestError("Missing required parameter: q."), http.StatusBadRequest) 29 29 return 30 30 } 31 31 ··· 35 35 if raw := strings.TrimSpace(q.Get("repoDid")); raw != "" { 36 36 repoDid, err := syntax.ParseDID(raw) 37 37 if err != nil { 38 - writeError(w, badRequestError("invalid repoDid"), http.StatusBadRequest) 38 + writeError(w, badRequestError("Invalid repoDid."), http.StatusBadRequest) 39 39 return 40 40 } 41 41 repoFilter = repoDid ··· 62 62 if err != nil { 63 63 var repoErr *codesearch.RepoOnlyError 64 64 if errors.As(err, &repoErr) { 65 - writeError(w, badRequestError("query only filters by repo name; use repo search instead"), http.StatusBadRequest) 65 + writeError(w, badRequestError("Query only filters by repo name; use repo search instead."), http.StatusBadRequest) 66 66 return 67 67 } 68 68 l.Error("code search failed", "err", err, "query", queryStr)
+8 -8
appview/xrpc/signup.go
··· 22 22 23 23 // signup is gated on cloudflare being configured, mirroring appview/signup 24 24 if x.Cloudflare == nil { 25 - writeError(w, xrpcErrorTag("SignupDisabled", "signup is not currently enabled"), http.StatusFailedDependency) 25 + writeError(w, xrpcErrorTag("SignupDisabled", "Signup is not currently enabled."), http.StatusFailedDependency) 26 26 return 27 27 } 28 28 ··· 34 34 35 35 if err := x.validateTurnstile(input.TurnstileToken, r); err != nil { 36 36 l.Warn("turnstile validation failed", "err", err, "email", input.Email) 37 - writeError(w, xrpcErrorTag("InvalidTurnstileToken", "captcha validation failed"), http.StatusForbidden) 37 + writeError(w, xrpcErrorTag("InvalidTurnstileToken", "Captcha validation failed."), http.StatusForbidden) 38 38 return 39 39 } 40 40 41 41 if !email.IsValidEmail(input.Email) { 42 - writeError(w, xrpcErrorTag("InvalidEmail", "invalid email address"), http.StatusBadRequest) 42 + writeError(w, xrpcErrorTag("InvalidEmail", "Invalid email address."), http.StatusBadRequest) 43 43 return 44 44 } 45 45 ··· 50 50 return 51 51 } 52 52 if exists { 53 - writeError(w, xrpcErrorTag("EmailAlreadyRegistered", "an account already exists for this email"), http.StatusConflict) 53 + writeError(w, xrpcErrorTag("EmailAlreadyRegistered", "An account already exists for this email."), http.StatusConflict) 54 54 return 55 55 } 56 56 ··· 89 89 l := x.Logger.With("handler", "AccountCompleteSignup") 90 90 91 91 if x.Cloudflare == nil { 92 - writeError(w, xrpcErrorTag("SignupDisabled", "signup is not currently enabled"), http.StatusFailedDependency) 92 + writeError(w, xrpcErrorTag("SignupDisabled", "Signup is not currently enabled."), http.StatusFailedDependency) 93 93 return 94 94 } 95 95 ··· 100 100 } 101 101 102 102 if !userutil.IsValidSubdomain(input.Username) { 103 - writeError(w, xrpcErrorTag("InvalidUsername", "invalid username"), http.StatusBadRequest) 103 + writeError(w, xrpcErrorTag("InvalidUsername", "Invalid username."), http.StatusBadRequest) 104 104 return 105 105 } 106 106 if x.DisallowedNicknames[strings.ToLower(input.Username)] { 107 - writeError(w, xrpcErrorTag("UsernameUnavailable", "this username is not available"), http.StatusConflict) 107 + writeError(w, xrpcErrorTag("UsernameUnavailable", "This username is not available."), http.StatusConflict) 108 108 return 109 109 } 110 110 111 111 emailAddr, err := db.GetEmailForCode(x.DB, input.Code) 112 112 if err != nil { 113 113 l.Error("failed to get email for code", "err", err) 114 - writeError(w, xrpcErrorTag("InvalidCode", "invalid or expired verification code"), http.StatusBadRequest) 114 + writeError(w, xrpcErrorTag("InvalidCode", "Invalid or expired verification code."), http.StatusBadRequest) 115 115 return 116 116 } 117 117
+16 -15
appview/xrpc/sites.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "database/sql" 5 6 "encoding/json" 6 7 "errors" 7 8 "net/http" ··· 27 28 } 28 29 29 30 claim, err := db.GetActiveDomainClaimForDid(x.DB, did) 30 - if err != nil { 31 + if err != nil && !errors.Is(err, sql.ErrNoRows) { 31 32 l.Error("failed to get domain claim", "err", err) 32 33 writeError(w, errInternal, http.StatusInternalServerError) 33 34 return ··· 57 58 58 59 subdomain := strings.TrimSpace(input.Subdomain) 59 60 if len(subdomain) < 4 { 60 - writeError(w, xrpcErrorTag("InvalidSubdomain", "subdomain must be at least 4 characters long"), http.StatusBadRequest) 61 + writeError(w, xrpcErrorTag("InvalidSubdomain", "Subdomain must be at least 4 characters long."), http.StatusBadRequest) 61 62 return 62 63 } 63 64 if !userutil.IsValidSubdomain(subdomain) { 64 - writeError(w, xrpcErrorTag("InvalidSubdomain", "use only lowercase letters, digits, and hyphens; cannot start or end with a hyphen"), http.StatusBadRequest) 65 + writeError(w, xrpcErrorTag("InvalidSubdomain", "Use only lowercase letters, digits, and hyphens; cannot start or end with a hyphen."), http.StatusBadRequest) 65 66 return 66 67 } 67 68 if userutil.HasSlur(subdomain) { 68 - writeError(w, xrpcErrorTag("InvalidSubdomain", "that subdomain is not allowed"), http.StatusBadRequest) 69 + writeError(w, xrpcErrorTag("InvalidSubdomain", "That subdomain is not allowed."), http.StatusBadRequest) 69 70 return 70 71 } 71 72 72 73 sitesDomain := x.Config.Sites.Domain 73 74 if subdomain == sitesDomain { 74 - writeError(w, xrpcErrorTag("InvalidSubdomain", "cannot claim the root domain"), http.StatusBadRequest) 75 + writeError(w, xrpcErrorTag("InvalidSubdomain", "Cannot claim the root domain."), http.StatusBadRequest) 75 76 return 76 77 } 77 78 fullDomain := subdomain + "." + sitesDomain ··· 79 80 if err := db.ClaimDomain(x.DB, did, fullDomain); err != nil { 80 81 switch { 81 82 case errors.Is(err, db.ErrDomainTaken): 82 - writeError(w, xrpcErrorTag("DomainTaken", err.Error()), http.StatusConflict) 83 + writeError(w, xrpcErrorTag("DomainTaken", "That subdomain is already claimed by another user."), http.StatusConflict) 83 84 case errors.Is(err, db.ErrDomainCooldown): 84 - writeError(w, xrpcErrorTag("DomainCooldown", err.Error()), http.StatusConflict) 85 + writeError(w, xrpcErrorTag("DomainCooldown", "That subdomain was recently released and is in a cooldown period. Please try again later."), http.StatusConflict) 85 86 case errors.Is(err, db.ErrAlreadyClaimed): 86 - writeError(w, xrpcErrorTag("AlreadyClaimed", err.Error()), http.StatusConflict) 87 + writeError(w, xrpcErrorTag("AlreadyClaimed", "You already have a domain claimed. Release it before claiming a new one."), http.StatusConflict) 87 88 default: 88 89 l.Error("claiming domain", "err", err) 89 90 writeError(w, errInternal, http.StatusInternalServerError) ··· 111 112 112 113 domain := strings.TrimSpace(input.Domain) 113 114 if domain == "" { 114 - writeError(w, badRequestError("domain cannot be empty"), http.StatusBadRequest) 115 + writeError(w, badRequestError("Domain cannot be empty."), http.StatusBadRequest) 115 116 return 116 117 } 117 118 ··· 121 122 writeError(w, errInternal, http.StatusInternalServerError) 122 123 return 123 124 } else if isTngl { 124 - writeError(w, xrpcErrorTag("HandleBoundDomain", "your tngl.sh domain is tied to your handle and cannot be released"), http.StatusBadRequest) 125 + writeError(w, xrpcErrorTag("HandleBoundDomain", "Your tngl.sh domain is tied to your handle and cannot be released."), http.StatusBadRequest) 125 126 return 126 127 } 127 128 128 129 if err := db.ReleaseDomain(x.DB, did, domain); err != nil { 129 130 l.Error("releasing domain", "err", err) 130 - writeError(w, xrpcErrorTag("DomainNotFound", "unable to release domain; ensure it belongs to your account"), http.StatusNotFound) 131 + writeError(w, xrpcErrorTag("DomainNotFound", "Unable to release domain; ensure it belongs to your account."), http.StatusNotFound) 131 132 return 132 133 } 133 134 ··· 201 202 202 203 branch := strings.TrimSpace(input.Branch) 203 204 if branch == "" { 204 - writeError(w, badRequestError("branch cannot be empty"), http.StatusBadRequest) 205 + writeError(w, badRequestError("Branch cannot be empty."), http.StatusBadRequest) 205 206 return 206 207 } 207 208 ··· 211 212 } 212 213 dir = path.Clean("/" + dir) 213 214 if dir != "/" && strings.Contains(dir, "..") { 214 - writeError(w, badRequestError("invalid directory path"), http.StatusBadRequest) 215 + writeError(w, badRequestError("Invalid directory path."), http.StatusBadRequest) 215 216 return 216 217 } 217 218 ··· 220 221 // check the claim before persisting, so a failed call leaves no state 221 222 ownerClaim, _ := db.GetActiveDomainClaimForDid(x.DB, repo.Did) 222 223 if ownerClaim == nil { 223 - writeError(w, xrpcErrorTag("NoDomainClaim", "the account does not have an active domain claim"), http.StatusBadRequest) 224 + writeError(w, xrpcErrorTag("NoDomainClaim", "The account does not have an active domain claim."), http.StatusBadRequest) 224 225 return 225 226 } 226 227 ··· 256 257 257 258 existingConfig, _ := db.GetRepoSiteConfig(x.DB, repo.RepoDid) 258 259 if existingConfig == nil { 259 - writeError(w, xrpcErrorTag("SiteNotFound", "no site configuration exists for this repository"), http.StatusNotFound) 260 + writeError(w, xrpcErrorTag("SiteNotFound", "No site configuration exists for this repository."), http.StatusNotFound) 260 261 return 261 262 } 262 263
+9 -9
appview/xrpc/webhooks.go
··· 25 25 26 26 repo, err := db.GetRepoByDid(x.DB, repoDid) 27 27 if err != nil { 28 - e := notFoundError("repo not found") 28 + e := notFoundError("Repo not found.") 29 29 return nil, &e, http.StatusNotFound 30 30 } 31 31 ··· 91 91 return 92 92 } 93 93 if len(input.Events) == 0 { 94 - writeError(w, xrpcErrorTag("NoEventsSelected", "at least one event must be specified"), http.StatusBadRequest) 94 + writeError(w, xrpcErrorTag("NoEventsSelected", "At least one event must be specified."), http.StatusBadRequest) 95 95 return 96 96 } 97 97 ··· 151 151 152 152 webhook, err := db.GetWebhook(x.DB, input.Id) 153 153 if err != nil || string(webhook.RepoDid) != repo.RepoDid { 154 - writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) 154 + writeError(w, xrpcErrorTag("WebhookNotFound", "Webhook not found."), http.StatusNotFound) 155 155 return 156 156 } 157 157 ··· 214 214 215 215 webhook, err := db.GetWebhook(x.DB, input.Id) 216 216 if err != nil || string(webhook.RepoDid) != repo.RepoDid { 217 - writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) 217 + writeError(w, xrpcErrorTag("WebhookNotFound", "Webhook not found."), http.StatusNotFound) 218 218 return 219 219 } 220 220 ··· 257 257 258 258 webhook, err := db.GetWebhook(x.DB, input.Id) 259 259 if err != nil || string(webhook.RepoDid) != repo.RepoDid { 260 - writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) 260 + writeError(w, xrpcErrorTag("WebhookNotFound", "Webhook not found."), http.StatusNotFound) 261 261 return 262 262 } 263 263 ··· 297 297 298 298 id, err := strconv.ParseInt(q.Get("id"), 10, 64) 299 299 if err != nil { 300 - writeError(w, badRequestError("invalid webhook id"), http.StatusBadRequest) 300 + writeError(w, badRequestError("Invalid webhook ID."), http.StatusBadRequest) 301 301 return 302 302 } 303 303 304 304 webhook, err := db.GetWebhook(x.DB, id) 305 305 if err != nil || string(webhook.RepoDid) != repo.RepoDid { 306 - writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) 306 + writeError(w, xrpcErrorTag("WebhookNotFound", "Webhook not found."), http.StatusNotFound) 307 307 return 308 308 } 309 309 ··· 365 365 366 366 webhook, err := db.GetWebhook(x.DB, input.WebhookId) 367 367 if err != nil || string(webhook.RepoDid) != repo.RepoDid { 368 - writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) 368 + writeError(w, xrpcErrorTag("WebhookNotFound", "Webhook not found."), http.StatusNotFound) 369 369 return 370 370 } 371 371 372 372 delivery, err := db.GetWebhookDelivery(x.DB, input.DeliveryId) 373 373 if err != nil || delivery.WebhookId != webhook.Id { 374 - writeError(w, xrpcErrorTag("DeliveryNotFound", "delivery not found"), http.StatusNotFound) 374 + writeError(w, xrpcErrorTag("DeliveryNotFound", "Delivery not found."), http.StatusNotFound) 375 375 return 376 376 } 377 377
+3 -3
appview/xrpc/xrpc.go
··· 162 162 163 163 // stable client-facing errors; handlers log the real cause and return these 164 164 var ( 165 - errInternal = xrpcErrorTag("InternalError", "internal server error") 166 - errBadRequestBody = xrpcErrorTag("InvalidRequest", "invalid request body") 167 - errUpstream = xrpcErrorTag("UpstreamError", "an upstream service failed") 165 + errInternal = xrpcErrorTag("InternalError", "Internal server error.") 166 + errBadRequestBody = xrpcErrorTag("InvalidRequest", "Invalid request body.") 167 + errUpstream = xrpcErrorTag("UpstreamError", "An upstream service failed.") 168 168 ) 169 169 170 170 func xrpcErrorTag(tag, message string) xrpcerr.XrpcError {