Files
ResolveSpec/pkg/resolvemcp/writecols.go
T
Hein 276c3814d8 feat(resolvemcp): read/write limits, preload validation, query timeout, stable client error codes
Config gains DefaultLimit/MaxLimit/MaxOffset/MaxBatch/MaxPreloadDepth/MaxWriteRows/QueryTimeout/
ConfirmTTL. Reads are capped and the total COUNT is optional. Errors reach clients as
{code,message}; everything else is logged with a reference. Panics (handler and hooks) are
recovered without returning the panic value.
2026-10-01 13:35:00 +02:00

56 lines
1.7 KiB
Go

package resolvemcp
import (
"reflect"
"sort"
"strings"
"github.com/bitechdev/ResolveSpec/pkg/reflection"
)
// maxKeyEcho caps how much of a rejected client key is echoed back in an error.
const maxKeyEcho = 64
// writeColumns validates the keys of a create/update payload against the model and returns the
// values keyed by database column name. A key may be the json name or the column name of a
// writable field (case-insensitive); relations, scan-only and unexported fields are not
// writable. Unknown keys are rejected rather than dropped, so a client learns that a write
// did not take effect, and no client-chosen identifier reaches SQL.
func writeColumns(model interface{}, data map[string]interface{}) (map[string]interface{}, error) {
modelType := reflect.TypeOf(model)
for modelType != nil && (modelType.Kind() == reflect.Pointer || modelType.Kind() == reflect.Slice) {
modelType = modelType.Elem()
}
if modelType == nil || modelType.Kind() != reflect.Struct {
return nil, errInternal
}
accepted := make(map[string]string)
for jsonKey, col := range reflection.BuildJSONToDBColumnMap(modelType) {
accepted[strings.ToLower(jsonKey)] = col
accepted[strings.ToLower(col)] = col
}
out := make(map[string]interface{}, len(data))
var unknown []string
for key, value := range data {
col, ok := accepted[strings.ToLower(key)]
if !ok {
if len(key) > maxKeyEcho {
key = key[:maxKeyEcho] + "..."
}
unknown = append(unknown, key)
continue
}
if _, dup := out[col]; dup {
return nil, invalidArg("column %q given more than once", col)
}
out[col] = value
}
if len(unknown) > 0 {
sort.Strings(unknown)
return nil, invalidArg("unknown or read-only fields: %s", strings.Join(unknown, ", "))
}
return out, nil
}