package contract import ( "context" "errors" "io" "strings" "unicode/utf8" "git.yuntuo.hk/k2safe/go-omni/internal/configdiag" ) // RequestPolicy describes protocol strictness, independently of business // fields. Empty values inherit App defaults, then the historical binder policy. type RequestPolicy struct { UnknownQuery string `json:"unknownQuery"` // ignore or reject UTF8 string `json:"utf8"` // replace or reject Charset string `json:"charset"` // any or utf8 Body string `json:"body"` // auto, required, optional, none, emptyObject } func (p RequestPolicy) Validate() error { for _, item := range []struct { name, value string allowed []string }{ {"unknownQuery", p.UnknownQuery, []string{"ignore", "reject"}}, {"utf8", p.UTF8, []string{"replace", "reject"}}, {"charset", p.Charset, []string{"any", "utf8"}}, {"body", p.Body, []string{"auto", "required", "optional", "none", "emptyObject"}}, } { if item.value == "" { continue } ok := false for _, value := range item.allowed { ok = ok || value == item.value } if !ok { return configdiag.New(item.name, "must be one of "+strings.Join(item.allowed, ", ")) } } return nil } func MergePolicy(base, override RequestPolicy) RequestPolicy { if override.UnknownQuery != "" { base.UnknownQuery = override.UnknownQuery } if override.UTF8 != "" { base.UTF8 = override.UTF8 } if override.Charset != "" { base.Charset = override.Charset } if override.Body != "" { base.Body = override.Body } return base } type policyKey struct{} func WithRequestPolicy(ctx context.Context, p RequestPolicy) context.Context { if ctx == nil { ctx = context.Background() } return context.WithValue(ctx, policyKey{}, p) } func RequestPolicyFromContext(ctx context.Context) RequestPolicy { if ctx == nil { return RequestPolicy{} } p, _ := ctx.Value(policyKey{}).(RequestPolicy) return p } // utf8Reader checks original bytes in one pass, including runes crossing Read // boundaries. It never replaces invalid bytes or buffers the complete body. type utf8Reader struct { r io.Reader pending [4]byte n int } var errUTF8 = errors.New("invalid UTF-8 in JSON body") func (r *utf8Reader) Read(p []byte) (int, error) { n, err := r.r.Read(p) for _, b := range p[:n] { if r.n == 0 && b < utf8.RuneSelf { continue } r.pending[r.n] = b r.n++ if utf8.FullRune(r.pending[:r.n]) { ch, size := utf8.DecodeRune(r.pending[:r.n]) if ch == utf8.RuneError && size == 1 { return n, errUTF8 } r.n = 0 } } if err == io.EOF && r.n != 0 { return n, errUTF8 } return n, err }