feat: standardize transport contracts across Gin and nethttp

This commit is contained in:
2026-09-21 00:10:44 +08:00
parent affd02d5f7
commit 5561795d80
22 changed files with 1137 additions and 205 deletions

View File

@@ -20,7 +20,9 @@ type Middleware func(Handler) Handler
// Router is the small contract the application needs from a network layer.
// The default implementation is backed by Gin, but another router or a
// future GoOmni transport can implement this interface without changing
// business handlers.
// business handlers. Register routes before serving requests. Implementations
// should pass transport/transporttest.Run; see transport/README.md for the
// portable path, method, fallback and context-propagation rules.
type Router interface {
http.Handler
Handle(method, path string, handler http.Handler)
@@ -162,16 +164,25 @@ func (c *Context) BindURI(value any) error { return validate.URI(c.params, value
// JSON is an escape hatch for responses that do not use the standard envelope.
func (c *Context) JSON(status int, value any) error {
if c.responded {
return nil
}
if c.writer == nil {
return apierrors.Internal(nil)
}
c.writer.Header().Set("Content-Type", "application/json; charset=utf-8")
c.writer.WriteHeader(status)
if err := json.NewEncoder(c.writer).Encode(value); err != nil {
// Encode before committing headers so an invalid value can still produce
// the standard 500 response on every adapter.
data, err := json.Marshal(value)
if err != nil {
return apierrors.Internal(err)
}
c.responded = true
c.status = status
c.writer.Header().Set("Content-Type", "application/json; charset=utf-8")
c.writer.WriteHeader(status)
if _, err := c.writer.Write(append(data, '\n')); err != nil {
return apierrors.Internal(err)
}
return nil
}
@@ -186,9 +197,15 @@ func (c *Context) Accepted(data any) error {
}
func (c *Context) NoContent() error {
c.writer.WriteHeader(http.StatusNoContent)
if c.responded {
return nil
}
if c.writer == nil {
return apierrors.Internal(nil)
}
c.responded = true
c.status = http.StatusNoContent
c.writer.WriteHeader(http.StatusNoContent)
return nil
}
@@ -213,7 +230,8 @@ func (c *Context) Status() int {
func (c *Context) Responded() bool { return c.responded }
// WriteError writes a stable public error response. Internal causes are not
// serialized and a partially written response is preserved.
// serialized. Responses committed through Context methods are preserved;
// direct Raw writes are outside this response-tracking contract.
func (c *Context) WriteError(err error) {
if c.responded {
return