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.
115 lines
3.1 KiB
Go
115 lines
3.1 KiB
Go
package openapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"git.yuntuo.hk/k2safe/go-omni/response"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// EnvelopeWith describes the same configurable envelope used at runtime. data
|
|
// describes the business value; it is never embedded under a fixed field name.
|
|
// A nil data schema describes an error response and selects ErrorBody if set.
|
|
func EnvelopeWith(data *Schema, cfg response.Config) (*Schema, error) {
|
|
f, err := response.New(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c := f.Config()
|
|
body := c.Body
|
|
if data == nil && c.ErrorBody != nil {
|
|
body = c.ErrorBody
|
|
}
|
|
if body != nil {
|
|
return templateSchema(body, data, c), nil
|
|
}
|
|
s := &Schema{Type: "object", Properties: map[string]*Schema{}}
|
|
if c.CodeField != "-" {
|
|
s.Properties[c.CodeField] = codeSchema(c.CodeType)
|
|
s.Required = append(s.Required, c.CodeField)
|
|
}
|
|
if c.MessageField != "-" {
|
|
s.Properties[c.MessageField] = &Schema{Type: "string"}
|
|
s.Required = append(s.Required, c.MessageField)
|
|
}
|
|
if c.RequestIDField != "-" {
|
|
s.Properties[c.RequestIDField] = &Schema{Type: "string"}
|
|
}
|
|
if c.DataField != "-" && (data != nil || c.DataMode == "always") {
|
|
if data == nil {
|
|
data = &Schema{Type: "object", Nullable: true, Enum: []any{nil}}
|
|
}
|
|
s.Properties[c.DataField] = data
|
|
s.Required = append(s.Required, c.DataField)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func templateSchema(value any, data *Schema, cfg response.Config) *Schema {
|
|
switch response.Slot(value) {
|
|
case "code":
|
|
return codeSchema(cfg.CodeType)
|
|
case "message", "requestId":
|
|
return &Schema{Type: "string"}
|
|
case "ok":
|
|
return &Schema{Type: "boolean"}
|
|
case "status":
|
|
return &Schema{Type: "integer"}
|
|
case "data":
|
|
if data != nil {
|
|
return data
|
|
}
|
|
return &Schema{Type: "object", Nullable: true, Enum: []any{nil}}
|
|
}
|
|
switch v := value.(type) {
|
|
case map[string]any:
|
|
s := &Schema{Type: "object", Properties: map[string]*Schema{}}
|
|
keys := make([]string, 0, len(v))
|
|
for key := range v {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
item := v[key]
|
|
if response.Slot(item) == "data" && data == nil && cfg.DataMode != "always" {
|
|
continue
|
|
}
|
|
s.Properties[key] = templateSchema(item, data, cfg)
|
|
if response.Slot(item) != "requestId" {
|
|
s.Required = append(s.Required, key)
|
|
}
|
|
}
|
|
return s
|
|
case []any:
|
|
items := &Schema{}
|
|
for _, item := range v {
|
|
items.AnyOf = append(items.AnyOf, templateSchema(item, data, cfg))
|
|
}
|
|
size := len(v)
|
|
return &Schema{Type: "array", Items: items, MinItems: &size, MaxItems: &size}
|
|
case string:
|
|
if strings.HasPrefix(v, "$${") {
|
|
v = v[1:]
|
|
}
|
|
return &Schema{Type: "string", Enum: []any{v}}
|
|
case bool:
|
|
return &Schema{Type: "boolean", Enum: []any{v}}
|
|
case nil:
|
|
return &Schema{Type: "object", Nullable: true, Enum: []any{nil}}
|
|
default:
|
|
typeName := "number"
|
|
encoded, _ := json.Marshal(v)
|
|
if _, err := json.Number(string(encoded)).Int64(); err == nil {
|
|
typeName = "integer"
|
|
}
|
|
return &Schema{Type: typeName, Enum: []any{v}}
|
|
}
|
|
}
|
|
|
|
func codeSchema(kind string) *Schema {
|
|
if kind == "int32" || kind == "int64" {
|
|
return &Schema{Type: "integer", Format: kind}
|
|
}
|
|
return &Schema{Type: kind}
|
|
}
|