package httpx import ( "encoding/xml" "fmt" "net/http" apierrors "git.yuntuo.hk/k2safe/go-omni/errors" "git.yuntuo.hk/k2safe/go-omni/response" ) // SetResponseEncoder overrides this request's success AND error encoding. A // cloned context inherits it, including buffered/idempotent requests. func (c *Context) SetResponseEncoder(encoder response.Encoder) { c.encoder = encoder } // WithResponse applies a policy to one route/group. It deliberately stays set // until the outer error boundary finishes handling this request. func WithResponse(encoder response.Encoder) Middleware { return func(next Handler) Handler { return func(c *Context) error { c.SetResponseEncoder(encoder); return next(c) } } } // ResponseRegistrar installs the response policy before application middleware, // so early auth/protection failures and buffered clones use the route contract. type ResponseRegistrar interface { HandleWithResponse(string, string, response.Encoder, Handler, ...Middleware) } // HandleWithResponse uses the registrar's early policy hook when available. // Custom registrars without this hook get an ordinary leading route middleware. func HandleWithResponse(router interface { Handle(string, string, Handler, ...Middleware) }, method, path string, encoder response.Encoder, handler Handler, middleware ...Middleware) { if r, ok := router.(ResponseRegistrar); ok { r.HandleWithResponse(method, path, encoder, handler, middleware...) return } all := append([]Middleware{WithResponse(encoder)}, middleware...) router.Handle(method, path, handler, all...) } func (c *Context) writeResult(result response.Result) (err error) { if c.responded { return nil } defer func() { if recovered := recover(); recovered != nil { err = apierrors.Internal(fmt.Errorf("response encoder panic: %v", recovered)) } }() encoder := c.encoder if encoder == nil { encoder = response.Default() } payload, err := encoder.Encode(result) if err != nil { return apierrors.Internal(err) } if result.PreserveStatus { payload.Status = result.Status } if payload.Status < 200 || payload.Status > 599 { return apierrors.Internal(fmt.Errorf("response encoder returned invalid status %d", payload.Status)) } if (payload.Status == 204 || payload.Status == 304) && len(payload.Body) > 0 { return apierrors.Internal(fmt.Errorf("response encoder returned a body for status %d", payload.Status)) } return c.WriteResponse(payload.Status, payload.Header, payload.Body) } // RespondStatus uses the current response format and error catalog, but keeps // the explicit HTTP status even if a catalog/custom encoder selects another. // This is useful for protocol endpoints such as health probes. Ordinary // business handlers should use OK/Created or return their error as usual. func (c *Context) RespondStatus(status int, data any, err error) error { if c.Responded() { return nil } if status < 200 || status > 599 || status == 204 || status == 205 || status == 304 { return apierrors.Internal(fmt.Errorf("response requires a body-bearing HTTP status")) } result := response.Result{Status: status, Code: "OK", Message: "success", Data: data, RequestID: c.RequestID(), PreserveStatus: true} if err != nil { failure := c.ResolveError(err) failure.Status = status c.failure = failure result.Code, result.Message, result.BusinessCode = failure.Code, failure.Message, failure.BusinessCode result.Err, result.Failed = err, true } writeErr := c.writeResult(result) if writeErr != nil && !c.Responded() { // A broken encoder must not fall through to a business error policy // that acknowledges failures with HTTP 200. Commit a bodyless failure. failureStatus := status if failureStatus < 400 { failureStatus = http.StatusInternalServerError } c.failure = c.ResolveError(writeErr) c.failure.Status = failureStatus _ = c.WriteResponse(failureStatus, nil, nil) } return writeErr } // Bytes, Text and XML are explicit wire responses; they bypass any envelope. func (c *Context) Bytes(status int, contentType string, body []byte) error { return c.WriteResponse(status, http.Header{"Content-Type": {contentType}}, body) } func (c *Context) Text(status int, body string) error { return c.Bytes(status, "text/plain; charset=utf-8", []byte(body)) } func (c *Context) XML(status int, value any) error { if c.responded { return nil } body, err := xml.Marshal(value) if err != nil { return apierrors.Internal(err) } return c.Bytes(status, "application/xml; charset=utf-8", body) }