219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package scriptorium
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
)
|
|
|
|
const maxSafeJSONInteger = 1<<53 - 1
|
|
|
|
type jsonVisit struct {
|
|
typ reflect.Type
|
|
ptr uintptr
|
|
}
|
|
|
|
func copyPublicJSONMap(src map[string]any) (map[string]any, error) {
|
|
if src == nil {
|
|
return nil, nil
|
|
}
|
|
copied, err := copyPublicJSONValue(reflect.ValueOf(src), "extra_params", make(map[jsonVisit]struct{}))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out, ok := copied.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("extra_params: expected object")
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func copyPublicJSONValue(value reflect.Value, path string, seen map[jsonVisit]struct{}) (any, error) {
|
|
if !value.IsValid() {
|
|
return nil, nil
|
|
}
|
|
if value.Kind() == reflect.Interface {
|
|
if value.IsNil() {
|
|
return nil, nil
|
|
}
|
|
return copyPublicJSONValue(value.Elem(), path, seen)
|
|
}
|
|
if !value.CanInterface() {
|
|
return nil, fmt.Errorf("%s: value cannot be copied", path)
|
|
}
|
|
if number, ok := value.Interface().(json.Number); ok {
|
|
f, err := strconv.ParseFloat(number.String(), 64)
|
|
if err != nil || math.IsNaN(f) || math.IsInf(f, 0) {
|
|
return nil, fmt.Errorf("%s: invalid JSON number", path)
|
|
}
|
|
return number, nil
|
|
}
|
|
|
|
switch value.Kind() {
|
|
case reflect.Bool, reflect.String:
|
|
return value.Interface(), nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if value.Int() < -maxSafeJSONInteger || value.Int() > maxSafeJSONInteger {
|
|
return nil, fmt.Errorf("%s: integer is outside the JSON-safe range", path)
|
|
}
|
|
return value.Interface(), nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
if value.Uint() > maxSafeJSONInteger {
|
|
return nil, fmt.Errorf("%s: integer is outside the JSON-safe range", path)
|
|
}
|
|
return value.Interface(), nil
|
|
case reflect.Float32, reflect.Float64:
|
|
f := value.Convert(reflect.TypeOf(float64(0))).Float()
|
|
if math.IsNaN(f) || math.IsInf(f, 0) {
|
|
return nil, fmt.Errorf("%s: floating-point value must be finite", path)
|
|
}
|
|
return value.Interface(), nil
|
|
case reflect.Pointer:
|
|
if value.IsNil() {
|
|
return nil, nil
|
|
}
|
|
visit := jsonVisit{typ: value.Type(), ptr: value.Pointer()}
|
|
if _, ok := seen[visit]; ok {
|
|
return nil, fmt.Errorf("%s: cyclic value is not supported", path)
|
|
}
|
|
seen[visit] = struct{}{}
|
|
defer delete(seen, visit)
|
|
return copyPublicJSONValue(value.Elem(), path, seen)
|
|
case reflect.Map:
|
|
return copyPublicJSONMapValue(value, path, seen)
|
|
case reflect.Slice:
|
|
if value.IsNil() {
|
|
return nil, nil
|
|
}
|
|
return copyPublicJSONSequenceValue(value, path, seen)
|
|
case reflect.Array:
|
|
return copyPublicJSONSequenceValue(value, path, seen)
|
|
default:
|
|
return nil, fmt.Errorf("%s: unsupported JSON value type %s", path, value.Type())
|
|
}
|
|
}
|
|
|
|
func copyPublicJSONMapValue(value reflect.Value, path string, seen map[jsonVisit]struct{}) (any, error) {
|
|
if value.IsNil() {
|
|
return nil, nil
|
|
}
|
|
if value.Type().Key().Kind() != reflect.String {
|
|
return nil, fmt.Errorf("%s: map key type %s is not supported", path, value.Type().Key())
|
|
}
|
|
|
|
visit := jsonVisit{typ: value.Type(), ptr: value.Pointer()}
|
|
if _, ok := seen[visit]; ok {
|
|
return nil, fmt.Errorf("%s: cyclic value is not supported", path)
|
|
}
|
|
seen[visit] = struct{}{}
|
|
defer delete(seen, visit)
|
|
|
|
type entry struct {
|
|
key reflect.Value
|
|
name string
|
|
value any
|
|
}
|
|
entries := make([]entry, 0, value.Len())
|
|
preserveType := true
|
|
elemType := value.Type().Elem()
|
|
iter := value.MapRange()
|
|
for iter.Next() {
|
|
key := iter.Key()
|
|
name := key.String()
|
|
copied, err := copyPublicJSONValue(iter.Value(), path+"."+name, seen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries = append(entries, entry{key: key, name: name, value: copied})
|
|
if copied == nil {
|
|
if !canAssignNil(elemType) {
|
|
preserveType = false
|
|
}
|
|
continue
|
|
}
|
|
if !reflect.TypeOf(copied).AssignableTo(elemType) {
|
|
preserveType = false
|
|
}
|
|
}
|
|
|
|
if preserveType {
|
|
out := reflect.MakeMapWithSize(value.Type(), len(entries))
|
|
for _, entry := range entries {
|
|
if entry.value == nil {
|
|
out.SetMapIndex(entry.key, reflect.Zero(elemType))
|
|
continue
|
|
}
|
|
out.SetMapIndex(entry.key, reflect.ValueOf(entry.value))
|
|
}
|
|
return out.Interface(), nil
|
|
}
|
|
|
|
out := make(map[string]any, len(entries))
|
|
for _, entry := range entries {
|
|
out[entry.name] = entry.value
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func copyPublicJSONSequenceValue(value reflect.Value, path string, seen map[jsonVisit]struct{}) (any, error) {
|
|
var visit jsonVisit
|
|
if value.Kind() == reflect.Slice {
|
|
visit = jsonVisit{typ: value.Type(), ptr: value.Pointer()}
|
|
if _, ok := seen[visit]; ok {
|
|
return nil, fmt.Errorf("%s: cyclic value is not supported", path)
|
|
}
|
|
seen[visit] = struct{}{}
|
|
defer delete(seen, visit)
|
|
}
|
|
|
|
values := make([]any, value.Len())
|
|
preserveType := true
|
|
elemType := value.Type().Elem()
|
|
for i := 0; i < value.Len(); i++ {
|
|
copied, err := copyPublicJSONValue(value.Index(i), fmt.Sprintf("%s[%d]", path, i), seen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values[i] = copied
|
|
if copied == nil {
|
|
if !canAssignNil(elemType) {
|
|
preserveType = false
|
|
}
|
|
continue
|
|
}
|
|
if !reflect.TypeOf(copied).AssignableTo(elemType) {
|
|
preserveType = false
|
|
}
|
|
}
|
|
|
|
if preserveType {
|
|
out := reflect.New(value.Type()).Elem()
|
|
if value.Kind() == reflect.Slice {
|
|
out = reflect.MakeSlice(value.Type(), value.Len(), value.Len())
|
|
}
|
|
for i, copied := range values {
|
|
if copied == nil {
|
|
out.Index(i).Set(reflect.Zero(elemType))
|
|
continue
|
|
}
|
|
out.Index(i).Set(reflect.ValueOf(copied))
|
|
}
|
|
return out.Interface(), nil
|
|
}
|
|
|
|
out := make([]any, len(values))
|
|
copy(out, values)
|
|
return out, nil
|
|
}
|
|
|
|
func canAssignNil(typ reflect.Type) bool {
|
|
switch typ.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|