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:
60
response/encoder.go
Normal file
60
response/encoder.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Result is the transport-independent input to an Encoder. Err is available for
|
||||
// application error mapping; default encoders never expose it or its causes.
|
||||
type Result struct {
|
||||
Status int
|
||||
Code, Message, RequestID string
|
||||
Data any
|
||||
Err error
|
||||
}
|
||||
|
||||
// Payload is a complete response. Encoders must be safe for concurrent use.
|
||||
// Status and headers are intentionally part of the contract: callback protocols
|
||||
// may acknowledge an error using a different HTTP status or media type.
|
||||
type Payload struct {
|
||||
Status int
|
||||
Header http.Header
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type Encoder interface{ Encode(Result) (Payload, error) }
|
||||
type EncoderFunc func(Result) (Payload, error)
|
||||
|
||||
func (f EncoderFunc) Encode(r Result) (Payload, error) { return f(r) }
|
||||
|
||||
func JSON(status int, value any) (Payload, error) {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return Payload{}, err
|
||||
}
|
||||
return Payload{Status: status, Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, Body: append(data, '\n')}, nil
|
||||
}
|
||||
|
||||
func Text(status int, value string) Payload {
|
||||
return Payload{Status: status, Header: http.Header{"Content-Type": {"text/plain; charset=utf-8"}}, Body: []byte(value)}
|
||||
}
|
||||
|
||||
// Default is immutable; applications can use different encoders in one process.
|
||||
var defaultEncoder = MustNew(Config{})
|
||||
|
||||
func Default() Encoder { return defaultEncoder }
|
||||
|
||||
// RawJSON leaves successful data unwrapped. Errors use the selected error
|
||||
// policy; callback protocols with custom acknowledgments should use EncoderFunc.
|
||||
func RawJSON(errors Encoder) Encoder {
|
||||
if errors == nil {
|
||||
errors = Default()
|
||||
}
|
||||
return EncoderFunc(func(r Result) (Payload, error) {
|
||||
if r.Err != nil || r.Status >= 400 {
|
||||
return errors.Encode(r)
|
||||
}
|
||||
return JSON(r.Status, r.Data)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user