108 lines
2.6 KiB
Go
108 lines
2.6 KiB
Go
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
|
|
}
|