mirror of
https://github.com/stashapp/stash.git
synced 2026-02-02 05:22:44 +01:00
199 lines
4.7 KiB
Go
199 lines
4.7 KiB
Go
package validator
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"fmt"
|
|
|
|
"github.com/vektah/gqlparser/ast"
|
|
"github.com/vektah/gqlparser/gqlerror"
|
|
)
|
|
|
|
var UnexpectedType = fmt.Errorf("Unexpected Type")
|
|
|
|
// VariableValues coerces and validates variable values
|
|
func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables map[string]interface{}) (map[string]interface{}, *gqlerror.Error) {
|
|
coercedVars := map[string]interface{}{}
|
|
|
|
validator := varValidator{
|
|
path: []interface{}{"variable"},
|
|
schema: schema,
|
|
}
|
|
|
|
for _, v := range op.VariableDefinitions {
|
|
validator.path = append(validator.path, v.Variable)
|
|
|
|
if !v.Definition.IsInputType() {
|
|
return nil, gqlerror.ErrorPathf(validator.path, "must an input type")
|
|
}
|
|
|
|
val, hasValue := variables[v.Variable]
|
|
if !hasValue {
|
|
if v.DefaultValue != nil {
|
|
var err error
|
|
val, err = v.DefaultValue.Value(nil)
|
|
if err != nil {
|
|
return nil, gqlerror.WrapPath(validator.path, err)
|
|
}
|
|
hasValue = true
|
|
} else if v.Type.NonNull {
|
|
return nil, gqlerror.ErrorPathf(validator.path, "must be defined")
|
|
}
|
|
}
|
|
|
|
if hasValue {
|
|
if val == nil {
|
|
if v.Type.NonNull {
|
|
return nil, gqlerror.ErrorPathf(validator.path, "cannot be null")
|
|
}
|
|
coercedVars[v.Variable] = nil
|
|
} else {
|
|
rv := reflect.ValueOf(val)
|
|
if rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
|
|
rv = rv.Elem()
|
|
}
|
|
|
|
if err := validator.validateVarType(v.Type, rv); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coercedVars[v.Variable] = val
|
|
}
|
|
}
|
|
|
|
validator.path = validator.path[0 : len(validator.path)-1]
|
|
}
|
|
|
|
return coercedVars, nil
|
|
}
|
|
|
|
type varValidator struct {
|
|
path []interface{}
|
|
schema *ast.Schema
|
|
}
|
|
|
|
func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerror.Error {
|
|
currentPath := v.path
|
|
resetPath := func() {
|
|
v.path = currentPath
|
|
}
|
|
defer resetPath()
|
|
|
|
if typ.Elem != nil {
|
|
if val.Kind() != reflect.Slice {
|
|
return gqlerror.ErrorPathf(v.path, "must be an array")
|
|
}
|
|
|
|
for i := 0; i < val.Len(); i++ {
|
|
resetPath()
|
|
v.path = append(v.path, i)
|
|
field := val.Index(i)
|
|
|
|
if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
|
|
if typ.Elem.NonNull && field.IsNil() {
|
|
return gqlerror.ErrorPathf(v.path, "cannot be null")
|
|
}
|
|
field = field.Elem()
|
|
}
|
|
|
|
if err := v.validateVarType(typ.Elem, field); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
def := v.schema.Types[typ.NamedType]
|
|
if def == nil {
|
|
panic(fmt.Errorf("missing def for %s", typ.NamedType))
|
|
}
|
|
|
|
switch def.Kind {
|
|
case ast.Enum:
|
|
kind := val.Type().Kind()
|
|
if kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || kind == reflect.String {
|
|
return nil
|
|
}
|
|
return gqlerror.ErrorPathf(v.path, "enums must be ints or strings")
|
|
case ast.Scalar:
|
|
kind := val.Type().Kind()
|
|
switch typ.NamedType {
|
|
case "Int":
|
|
if kind == reflect.String || kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 {
|
|
return nil
|
|
}
|
|
case "Float":
|
|
if kind == reflect.String || kind == reflect.Float32 || kind == reflect.Float64 || kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 {
|
|
return nil
|
|
}
|
|
case "String":
|
|
if kind == reflect.String {
|
|
return nil
|
|
}
|
|
|
|
case "Boolean":
|
|
if kind == reflect.Bool {
|
|
return nil
|
|
}
|
|
|
|
case "ID":
|
|
if kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || kind == reflect.String {
|
|
return nil
|
|
}
|
|
default:
|
|
// assume custom scalars are ok
|
|
return nil
|
|
}
|
|
return gqlerror.ErrorPathf(v.path, "cannot use %s as %s", kind.String(), typ.NamedType)
|
|
case ast.InputObject:
|
|
if val.Kind() != reflect.Map {
|
|
return gqlerror.ErrorPathf(v.path, "must be a %s", def.Name)
|
|
}
|
|
|
|
// check for unknown fields
|
|
for _, name := range val.MapKeys() {
|
|
val.MapIndex(name)
|
|
fieldDef := def.Fields.ForName(name.String())
|
|
resetPath()
|
|
v.path = append(v.path, name.String())
|
|
|
|
if fieldDef == nil {
|
|
return gqlerror.ErrorPathf(v.path, "unknown field")
|
|
}
|
|
}
|
|
|
|
for _, fieldDef := range def.Fields {
|
|
resetPath()
|
|
v.path = append(v.path, fieldDef.Name)
|
|
|
|
field := val.MapIndex(reflect.ValueOf(fieldDef.Name))
|
|
if !field.IsValid() {
|
|
if fieldDef.Type.NonNull {
|
|
return gqlerror.ErrorPathf(v.path, "must be defined")
|
|
}
|
|
continue
|
|
}
|
|
|
|
if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
|
|
if fieldDef.Type.NonNull && field.IsNil() {
|
|
return gqlerror.ErrorPathf(v.path, "cannot be null")
|
|
}
|
|
//allow null object field and skip it
|
|
if !fieldDef.Type.NonNull && field.IsNil() {
|
|
continue
|
|
}
|
|
field = field.Elem()
|
|
}
|
|
|
|
err := v.validateVarType(fieldDef.Type, field)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("unsupported type %s", def.Kind))
|
|
}
|
|
|
|
return nil
|
|
}
|