From da66a125a9f796247cba9e13f6cf72a5de34f0ef Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Wed, 2 Sep 2026 13:16:23 +0100 Subject: [PATCH] Use header constants across the handler package goconst tripped on main after #259 landed on top of #280: five composite-literal occurrences each of "Content-Length" and "Content-Type" across container.go, container_manifest.go, container_tags.go, handler.go, and swift.go crossed the min-occurrences: 5 threshold. Neither PR hit it alone. Add headerContentType and headerContentLength beside headerAcceptEncoding and use them throughout the package rather than only at the flagged sites, so the next handler that adds one does not re-trip the check. --- internal/handler/cargo.go | 4 ++-- internal/handler/composer.go | 6 +++--- internal/handler/conda.go | 4 ++-- internal/handler/container.go | 12 ++++++------ internal/handler/container_manifest.go | 8 ++++---- internal/handler/container_tags.go | 8 ++++---- internal/handler/debian.go | 2 +- internal/handler/gem.go | 2 +- internal/handler/gradle.go | 6 +++--- internal/handler/handler.go | 20 ++++++++++++-------- internal/handler/helm.go | 2 +- internal/handler/hex.go | 6 +++--- internal/handler/npm.go | 4 ++-- internal/handler/nuget.go | 8 ++++---- internal/handler/pub.go | 4 ++-- internal/handler/pypi.go | 6 +++--- internal/handler/rpm.go | 2 +- internal/handler/swift.go | 18 +++++++++--------- 18 files changed, 63 insertions(+), 59 deletions(-) diff --git a/internal/handler/cargo.go b/internal/handler/cargo.go index 79fb750..4cf8591 100644 --- a/internal/handler/cargo.go +++ b/internal/handler/cargo.go @@ -80,7 +80,7 @@ func (h *CargoHandler) handleConfig(w http.ResponseWriter, r *http.Request) { DL: h.proxyURL + "/cargo/crates/{crate}/{version}/download", } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _ = json.NewEncoder(w).Encode(config) } @@ -112,7 +112,7 @@ func (h *CargoHandler) handleIndex(w http.ResponseWriter, r *http.Request) { contentType = "text/plain; charset=utf-8" } - w.Header().Set("Content-Type", contentType) + w.Header().Set(headerContentType, contentType) w.WriteHeader(http.StatusOK) h.applyCooldownFiltering(w, body) } diff --git a/internal/handler/composer.go b/internal/handler/composer.go index 9b2de80..fbbd7a4 100644 --- a/internal/handler/composer.go +++ b/internal/handler/composer.go @@ -77,7 +77,7 @@ func (h *ComposerHandler) handleServiceIndex(w http.ResponseWriter, r *http.Requ "providers-lazy-url": h.proxyURL + "/composer/p2/%package%.json", } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _ = json.NewEncoder(w).Encode(index) } @@ -113,12 +113,12 @@ func (h *ComposerHandler) handlePackageMetadata(w http.ResponseWriter, r *http.R rewritten, err := h.rewriteMetadata(body) if err != nil { h.proxy.Logger.Warn("failed to rewrite metadata, proxying original", "error", err) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(rewritten) } diff --git a/internal/handler/conda.go b/internal/handler/conda.go index bee5694..cef814b 100644 --- a/internal/handler/conda.go +++ b/internal/handler/conda.go @@ -174,12 +174,12 @@ func (h *CondaHandler) handleRepodata(w http.ResponseWriter, r *http.Request) { filtered, err := h.applyCooldownFiltering(body) if err != nil { h.proxy.Logger.Warn("failed to filter repodata, proxying original", "error", err) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(filtered) } diff --git a/internal/handler/container.go b/internal/handler/container.go index 7a0a0e9..722e8f8 100644 --- a/internal/handler/container.go +++ b/internal/handler/container.go @@ -126,9 +126,9 @@ func (h *ContainerHandler) handleBlobDownload(w http.ResponseWriter, r *http.Req if cached != nil { w.Header().Set("Docker-Content-Digest", digest) if cached.ContentType != "" { - w.Header().Set("Content-Type", cached.ContentType) + w.Header().Set(headerContentType, cached.ContentType) } else { - w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set(headerContentType, "application/octet-stream") } serveArtifact(w, r.Method, cached) return @@ -162,9 +162,9 @@ func (h *ContainerHandler) handleBlobDownload(w http.ResponseWriter, r *http.Req w.Header().Set("Docker-Content-Digest", digest) if result.ContentType != "" { - w.Header().Set("Content-Type", result.ContentType) + w.Header().Set(headerContentType, result.ContentType) } else { - w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set(headerContentType, "application/octet-stream") } ServeArtifact(w, result) } @@ -232,7 +232,7 @@ func (h *ContainerHandler) proxyBlobHead(w http.ResponseWriter, r *http.Request, } defer func() { _ = resp.Body.Close() }() - for _, header := range []string{"Content-Type", "Content-Length", "Docker-Content-Digest"} { + for _, header := range []string{headerContentType, headerContentLength, "Docker-Content-Digest"} { if v := resp.Header.Get(header); v != "" { w.Header().Set(header, v) } @@ -261,7 +261,7 @@ func (h *ContainerHandler) registryForName(name string) (registryURL, upstreamNa // containerError writes an OCI-compliant error response. func (h *ContainerHandler) containerError(w http.ResponseWriter, status int, code, message string) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]any{ "errors": []map[string]string{ diff --git a/internal/handler/container_manifest.go b/internal/handler/container_manifest.go index b7cdeb5..a471330 100644 --- a/internal/handler/container_manifest.go +++ b/internal/handler/container_manifest.go @@ -95,7 +95,7 @@ func (h *ContainerHandler) serveManifest(w http.ResponseWriter, r *http.Request, } manifest := &cachedContainerManifest{ body: body, - contentType: resp.Header.Get("Content-Type"), + contentType: resp.Header.Get(headerContentType), contentDigest: resp.Header.Get("Docker-Content-Digest"), etag: resp.Header.Get("ETag"), size: int64(len(body)), @@ -228,9 +228,9 @@ func (h *ContainerHandler) storeContainerManifest(ctx context.Context, cacheKey func writeContainerManifest(w http.ResponseWriter, method string, manifest *cachedContainerManifest, stale bool) { if manifest.contentType != "" { - w.Header().Set("Content-Type", manifest.contentType) + w.Header().Set(headerContentType, manifest.contentType) } - w.Header().Set("Content-Length", strconv.FormatInt(manifest.size, 10)) + w.Header().Set(headerContentLength, strconv.FormatInt(manifest.size, 10)) if manifest.contentDigest != "" { w.Header().Set("Docker-Content-Digest", manifest.contentDigest) } @@ -383,7 +383,7 @@ func containerAcceptQuality(params map[string]string) float64 { } func copyContainerManifestHeaders(destination, source http.Header) { - for _, header := range []string{"Content-Type", "Content-Length", "Docker-Content-Digest", "ETag", "WWW-Authenticate"} { + for _, header := range []string{headerContentType, headerContentLength, "Docker-Content-Digest", "ETag", "WWW-Authenticate"} { if value := source.Get(header); value != "" { destination.Set(header, value) } diff --git a/internal/handler/container_tags.go b/internal/handler/container_tags.go index 95b0b4c..0e9e3eb 100644 --- a/internal/handler/container_tags.go +++ b/internal/handler/container_tags.go @@ -86,7 +86,7 @@ func (h *ContainerHandler) serveTagsList(w http.ResponseWriter, r *http.Request, } tags := &cachedContainerTags{ body: body, - contentType: resp.Header.Get("Content-Type"), + contentType: resp.Header.Get(headerContentType), etag: resp.Header.Get("ETag"), link: h.rewriteContainerTagsLink(strings.Join(resp.Header.Values("Link"), ", "), registryURL, r.URL.Path), size: int64(len(body)), @@ -169,8 +169,8 @@ func (h *ContainerHandler) storeContainerTags(ctx context.Context, cacheKey stri } func writeContainerTags(w http.ResponseWriter, tags *cachedContainerTags, stale bool) { - w.Header().Set("Content-Type", tags.contentType) - w.Header().Set("Content-Length", strconv.FormatInt(tags.size, 10)) + w.Header().Set(headerContentType, tags.contentType) + w.Header().Set(headerContentLength, strconv.FormatInt(tags.size, 10)) if tags.etag != "" { w.Header().Set("ETag", tags.etag) } @@ -185,7 +185,7 @@ func writeContainerTags(w http.ResponseWriter, tags *cachedContainerTags, stale } func copyContainerTagsHeaders(destination, source http.Header) { - for _, header := range []string{"Content-Type", "Content-Length", "ETag", "Link", "WWW-Authenticate"} { + for _, header := range []string{headerContentType, headerContentLength, "ETag", "Link", "WWW-Authenticate"} { if value := source.Get(header); value != "" { destination.Set(header, value) } diff --git a/internal/handler/debian.go b/internal/handler/debian.go index b48f4fc..42d26e7 100644 --- a/internal/handler/debian.go +++ b/internal/handler/debian.go @@ -88,7 +88,7 @@ func (h *DebianHandler) handlePackageDownload(w http.ResponseWriter, r *http.Req return } - w.Header().Set("Content-Type", "application/vnd.debian.binary-package") + w.Header().Set(headerContentType, "application/vnd.debian.binary-package") ServeArtifact(w, result) } diff --git a/internal/handler/gem.go b/internal/handler/gem.go index 50bba0c..8fc5039 100644 --- a/internal/handler/gem.go +++ b/internal/handler/gem.go @@ -182,7 +182,7 @@ func (h *GemHandler) fetchCompactIndex(r *http.Request, name string) (*http.Resp // writeFilteredIndex writes the compact index response with cooldown-filtered versions removed. func (h *GemHandler) writeFilteredIndex(w http.ResponseWriter, resp *http.Response, name string, filtered map[string]bool) { for k, vv := range resp.Header { - if strings.EqualFold(k, "Content-Length") { + if strings.EqualFold(k, headerContentLength) { continue // length will change after filtering } for _, v := range vv { diff --git a/internal/handler/gradle.go b/internal/handler/gradle.go index 3b703be..cc7e33f 100644 --- a/internal/handler/gradle.go +++ b/internal/handler/gradle.go @@ -93,7 +93,7 @@ func (h *GradleBuildCacheHandler) cacheStoragePath(key string) string { func (h *GradleBuildCacheHandler) handleGetOrHead(w http.ResponseWriter, r *http.Request, key string) { storagePath := h.cacheStoragePath(key) - w.Header().Set("Content-Type", gradleBuildCacheContentType) + w.Header().Set(headerContentType, gradleBuildCacheContentType) if r.Method == http.MethodHead { existsStart := time.Now() @@ -118,7 +118,7 @@ func (h *GradleBuildCacheHandler) handleGetOrHead(w http.ResponseWriter, r *http if err != nil { metrics.RecordStorageError("read") } else if size >= 0 { - w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + w.Header().Set(headerContentLength, strconv.FormatInt(size, 10)) } w.WriteHeader(http.StatusOK) @@ -171,7 +171,7 @@ func (h *GradleBuildCacheHandler) handlePut(w http.ResponseWriter, r *http.Reque return } - w.Header().Set("Content-Length", "0") + w.Header().Set(headerContentLength, "0") w.Header().Set("ETag", `"`+hash+`"`) w.WriteHeader(http.StatusCreated) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 05a58a9..6f4ceaf 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -91,7 +91,11 @@ func packagePURLStrings(ecosystem, name, version string) (string, string, error) const contentTypeJSON = "application/json" -const headerAcceptEncoding = "Accept-Encoding" +const ( + headerAcceptEncoding = "Accept-Encoding" + headerContentType = "Content-Type" + headerContentLength = "Content-Length" +) // defaultMetadataMaxSize is used when Proxy.MetadataMaxSize is unset. const defaultMetadataMaxSize = 100 << 20 @@ -453,10 +457,10 @@ func serveArtifact(w http.ResponseWriter, method string, result *CacheResult) { } if result.ContentType != "" { - w.Header().Set("Content-Type", result.ContentType) + w.Header().Set(headerContentType, result.ContentType) } if result.Size > 0 || (method == http.MethodHead && result.Size == 0) { - w.Header().Set("Content-Length", strconv.FormatInt(result.Size, 10)) + w.Header().Set(headerContentLength, strconv.FormatInt(result.Size, 10)) } if result.Hash != "" { w.Header().Set("ETag", `"`+result.Hash+`"`) @@ -537,7 +541,7 @@ func (p *Proxy) ProxyFile(w http.ResponseWriter, r *http.Request, upstreamURL st // JSONError writes a JSON error response. func JSONError(w http.ResponseWriter, status int, message string) { - w.Header().Set("Content-Type", contentTypeJSON) + w.Header().Set(headerContentType, contentTypeJSON) w.WriteHeader(status) _, _ = fmt.Fprintf(w, `{"error":%q}`, message) } @@ -711,7 +715,7 @@ func (p *Proxy) fetchUpstreamMetadata(ctx context.Context, upstreamURL string, e return nil, "", "", zeroTime, fmt.Errorf("reading response: %w", err) } - contentType := resp.Header.Get("Content-Type") + contentType := resp.Header.Get(headerContentType) if contentType == "" { contentType = contentTypeJSON } @@ -826,8 +830,8 @@ func (p *Proxy) writeMetadataCachedResponse(w http.ResponseWriter, r *http.Reque } } - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", strconv.Itoa(len(body))) + w.Header().Set(headerContentType, contentType) + w.Header().Set(headerContentLength, strconv.Itoa(len(body))) if cm.etag != "" { w.Header().Set("ETag", cm.etag) } @@ -870,7 +874,7 @@ func (p *Proxy) proxyMetadataStream(w http.ResponseWriter, r *http.Request, upst } defer func() { _ = resp.Body.Close() }() - for _, header := range []string{"Content-Type", "Content-Length", "Last-Modified", "ETag"} { + for _, header := range []string{headerContentType, headerContentLength, "Last-Modified", "ETag"} { if v := resp.Header.Get(header); v != "" { w.Header().Set(header, v) } diff --git a/internal/handler/helm.go b/internal/handler/helm.go index f91ba58..994c259 100644 --- a/internal/handler/helm.go +++ b/internal/handler/helm.go @@ -136,7 +136,7 @@ func (h *HelmHandler) serveChart(w http.ResponseWriter, r *http.Request, reposit } if result.ContentType == "" { - w.Header().Set("Content-Type", "application/gzip") + w.Header().Set(headerContentType, "application/gzip") } ServeArtifact(w, result) } diff --git a/internal/handler/hex.go b/internal/handler/hex.go index f3bcb93..15d2891 100644 --- a/internal/handler/hex.go +++ b/internal/handler/hex.go @@ -135,7 +135,7 @@ func (h *HexHandler) handlePackages(w http.ResponseWriter, r *http.Request) { if len(filteredVersions) == 0 { // No versions to filter or couldn't get timestamps, pass through - w.Header().Set("Content-Type", protoResp.Header.Get("Content-Type")) + w.Header().Set(headerContentType, protoResp.Header.Get(headerContentType)) w.Header().Set("Content-Encoding", "gzip") _, _ = w.Write(body) return @@ -144,13 +144,13 @@ func (h *HexHandler) handlePackages(w http.ResponseWriter, r *http.Request) { filtered, err := h.filterSignedPackage(body, filteredVersions) if err != nil { h.proxy.Logger.Warn("failed to filter hex package, proxying original", "error", err) - w.Header().Set("Content-Type", protoResp.Header.Get("Content-Type")) + w.Header().Set(headerContentType, protoResp.Header.Get(headerContentType)) w.Header().Set("Content-Encoding", "gzip") _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set(headerContentType, "application/octet-stream") w.Header().Set("Content-Encoding", "gzip") _, _ = w.Write(filtered) } diff --git a/internal/handler/npm.go b/internal/handler/npm.go index 265603f..166b944 100644 --- a/internal/handler/npm.go +++ b/internal/handler/npm.go @@ -98,13 +98,13 @@ func (h *NPMHandler) handlePackageMetadata(w http.ResponseWriter, r *http.Reques if err != nil { // If rewriting fails, just proxy the original h.proxy.Logger.Warn("failed to rewrite metadata, proxying original", "error", err) - w.Header().Set("Content-Type", contentTypeJSON) + w.Header().Set(headerContentType, contentTypeJSON) w.WriteHeader(http.StatusOK) _, _ = w.Write(body) return } - w.Header().Set("Content-Type", contentTypeJSON) + w.Header().Set(headerContentType, contentTypeJSON) w.WriteHeader(http.StatusOK) _, _ = w.Write(rewritten) } diff --git a/internal/handler/nuget.go b/internal/handler/nuget.go index 0fb139b..7df6240 100644 --- a/internal/handler/nuget.go +++ b/internal/handler/nuget.go @@ -85,12 +85,12 @@ func (h *NuGetHandler) handleServiceIndex(w http.ResponseWriter, r *http.Request rewritten, err := h.rewriteServiceIndex(body) if err != nil { h.proxy.Logger.Warn("failed to rewrite service index, proxying original", "error", err) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(rewritten) } @@ -188,12 +188,12 @@ func (h *NuGetHandler) handleRegistration(w http.ResponseWriter, r *http.Request filtered, err := h.applyCooldownFiltering(body) if err != nil { h.proxy.Logger.Warn("failed to filter registration, proxying original", "error", err) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(filtered) } diff --git a/internal/handler/pub.go b/internal/handler/pub.go index 303a9e6..9d449ba 100644 --- a/internal/handler/pub.go +++ b/internal/handler/pub.go @@ -110,13 +110,13 @@ func (h *PubHandler) handlePackageMetadata(w http.ResponseWriter, r *http.Reques rewritten, err := h.rewriteMetadata(name, body) if err != nil { h.proxy.Logger.Warn("failed to rewrite metadata, proxying original", "error", err) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write(rewritten) } diff --git a/internal/handler/pypi.go b/internal/handler/pypi.go index b1d7e72..03d178f 100644 --- a/internal/handler/pypi.go +++ b/internal/handler/pypi.go @@ -130,7 +130,7 @@ func (h *PyPIHandler) handleSimplePackage(w http.ResponseWriter, r *http.Request rewritten = h.rewriteSimpleHTML(body, filteredVersions) } - w.Header().Set("Content-Type", contentType) + w.Header().Set(headerContentType, contentType) ensureVaryAccept(w.Header()) w.WriteHeader(http.StatusOK) _, _ = w.Write(rewritten) @@ -401,12 +401,12 @@ func (h *PyPIHandler) proxyAndRewriteJSON(w http.ResponseWriter, r *http.Request rewritten, err := h.rewriteJSONMetadata(body) if err != nil { h.proxy.Logger.Warn("failed to rewrite metadata, proxying original", "error", err) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(body) return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(headerContentType, "application/json") _, _ = w.Write(rewritten) } diff --git a/internal/handler/rpm.go b/internal/handler/rpm.go index f4cf9e7..e06a3cc 100644 --- a/internal/handler/rpm.go +++ b/internal/handler/rpm.go @@ -94,7 +94,7 @@ func (h *RPMHandler) handlePackageDownload(w http.ResponseWriter, r *http.Reques return } - w.Header().Set("Content-Type", "application/x-rpm") + w.Header().Set(headerContentType, "application/x-rpm") ServeArtifact(w, result) } diff --git a/internal/handler/swift.go b/internal/handler/swift.go index 545407e..9acc881 100644 --- a/internal/handler/swift.go +++ b/internal/handler/swift.go @@ -219,9 +219,9 @@ func (h *SwiftHandler) handleSourceArchiveHead( return } setSwiftArchiveHeaders(w.Header(), name, version, "", archiveInfo) - w.Header().Set("Content-Type", "application/zip") + w.Header().Set(headerContentType, "application/zip") if size >= 0 { - w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + w.Header().Set(headerContentLength, strconv.FormatInt(size, 10)) } w.WriteHeader(http.StatusOK) } @@ -260,7 +260,7 @@ func (h *SwiftHandler) probeSourceArchive(ctx context.Context, upstreamURL, acce } size := int64(-1) - if contentLength := resp.Header.Get("Content-Length"); contentLength != "" { + if contentLength := resp.Header.Get(headerContentLength); contentLength != "" { if parsed, parseErr := strconv.ParseInt(contentLength, 10, 64); parseErr == nil { size = parsed } @@ -296,7 +296,7 @@ func (h *SwiftHandler) fetchMetadataWithHeaders( if err != nil { return nil, "", nil, fmt.Errorf("reading upstream metadata: %w", err) } - contentType := resp.Header.Get("Content-Type") + contentType := resp.Header.Get(headerContentType) if contentType == "" { contentType = contentTypeJSON } @@ -430,8 +430,8 @@ func (h *SwiftHandler) proxySwiftResource(w http.ResponseWriter, r *http.Request func copySwiftResponseHeaders(dst, src http.Header) { for _, name := range []string{ - "Cache-Control", "Content-Disposition", "Content-Language", "Content-Length", - "Content-Type", "Content-Version", "Digest", "ETag", "Last-Modified", + "Cache-Control", "Content-Disposition", "Content-Language", headerContentLength, + headerContentType, "Content-Version", "Digest", "ETag", "Last-Modified", "Retry-After", "Vary", "Warning", "X-Swift-Package-Signature", "X-Swift-Package-Signature-Format", } { @@ -564,14 +564,14 @@ func writeSwiftMetadata(w http.ResponseWriter, r *http.Request, body []byte, con } digest := sha256.Sum256(body) etag := fmt.Sprintf(`"%x"`, digest) - w.Header().Set("Content-Type", contentType) + w.Header().Set(headerContentType, contentType) w.Header().Set("Content-Version", swiftContentVersion) w.Header().Set("ETag", etag) if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return } - w.Header().Set("Content-Length", strconv.Itoa(len(body))) + w.Header().Set(headerContentLength, strconv.Itoa(len(body))) w.WriteHeader(http.StatusOK) if r.Method != http.MethodHead { _, _ = w.Write(body) @@ -597,7 +597,7 @@ func (h *SwiftHandler) writeArtifactError(w http.ResponseWriter, err error) { } func writeSwiftProblem(w http.ResponseWriter, status int, detail string) { - w.Header().Set("Content-Type", "application/problem+json") + w.Header().Set(headerContentType, "application/problem+json") w.Header().Set("Content-Version", swiftContentVersion) w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]string{"detail": detail})