feat: release configurable API response contracts
Define reusable response bodies in .api with typed code mappings, per-route overrides and matching OpenAPI schemas. Add application and callback encoders, early response policy selection, and isolated remote-consumer release verification. Release v0.8.0-preview after full race, vet, build and local MySQL workflow acceptance.
This commit is contained in:
@@ -30,6 +30,12 @@ type Router interface {
|
||||
Group(path string) RouterGroup
|
||||
}
|
||||
|
||||
// FallbackRouter optionally routes 404/405 through the application's response
|
||||
// policy and middleware. Custom adapters can implement it without changing Router.
|
||||
type FallbackRouter interface {
|
||||
SetFallbacks(notFound, methodNotAllowed http.Handler)
|
||||
}
|
||||
|
||||
// RouterGroup is the route-registration portion of Router.
|
||||
type RouterGroup interface {
|
||||
Handle(method, path string, handler http.Handler)
|
||||
@@ -87,6 +93,7 @@ type Context struct {
|
||||
values map[string]any
|
||||
responded bool
|
||||
status int
|
||||
encoder response.Encoder
|
||||
}
|
||||
|
||||
// NewContext creates a transport-neutral GoOmni context.
|
||||
@@ -121,6 +128,7 @@ func (c *Context) Raw() http.ResponseWriter { return c.writer }
|
||||
// Stored values themselves are shared; middleware must not mutate them concurrently.
|
||||
func (c *Context) Clone(ctx context.Context, writer http.ResponseWriter) *Context {
|
||||
clone := NewContext(c.request.Clone(ctx), writer, c.params)
|
||||
clone.encoder = c.encoder
|
||||
for key, value := range c.values {
|
||||
clone.values[key] = value
|
||||
}
|
||||
@@ -242,7 +250,8 @@ func (c *Context) NoContent() error {
|
||||
}
|
||||
|
||||
func (c *Context) respond(status int, code, message string, data any) error {
|
||||
return c.JSON(status, response.Response{
|
||||
return c.writeResult(response.Result{
|
||||
Status: status,
|
||||
Code: code,
|
||||
Message: message,
|
||||
Data: data,
|
||||
@@ -265,13 +274,18 @@ func (c *Context) Responded() bool { return c.responded }
|
||||
// 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 {
|
||||
if c.responded || err == nil {
|
||||
return
|
||||
}
|
||||
apiErr := apierrors.From(err)
|
||||
_ = c.JSON(apiErr.Status, response.Response{
|
||||
if writeErr := c.writeResult(response.Result{
|
||||
Status: apiErr.Status,
|
||||
Code: apiErr.Code,
|
||||
Message: apiErr.Message,
|
||||
RequestID: c.RequestID(),
|
||||
})
|
||||
Err: err,
|
||||
}); writeErr != nil && !c.responded {
|
||||
// An invalid custom encoder must not recurse or leak a different envelope.
|
||||
_ = c.WriteResponse(http.StatusInternalServerError, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user