package valid
import (
"context"
"fmt"
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser/v2/gqlerror"
)
type Validation struct {
ctx context.Context
input map[string]interface{}
}
type ValidationError struct {
valid *Validation
err *gqlerror.Error
}
// Creates a new validation context.
func New(ctx context.Context) *Validation {
return &Validation{
ctx: ctx,
}
}
// Adds an input map to a validation context.
func (valid *Validation) WithInput(input map[string]interface{}) *Validation {
valid.input = input
return valid
}
// Returns true if no errors were found.
func (valid *Validation) Ok() bool {
return len(graphql.GetErrors(valid.ctx)) == 0
}
// Fetches a string from the validation context, which must have an input
// registered. If the field is not present, the callback is not run. If
// present, but not a string, an error is recorded. Otherwise, the function is
// called with the string for the user to conduct further validation with.
func (valid *Validation) OptionalString(name string, fn func(s string)) {
if valid.input == nil {
panic("Attempted to validate fields without input")
}
if o, ok := valid.input[name]; ok {
s, ok := o.(string)
valid.
Expect(ok, fmt.Sprintf("Expected %s to be a string", name)).
WithField(name)
if ok {
fn(s)
}
}
}
// Asserts that a condition is true, recording a GraphQL error with the given
// message if not.
func (valid *Validation) Expect(cond bool, msg string) *ValidationError {
if cond {
return &ValidationError{valid: valid}
}
err := &gqlerror.Error{
Path: graphql.GetPath(valid.ctx),
Message: msg,
}
graphql.AddError(valid.ctx, err)
return &ValidationError{
valid: valid,
err: err,
}
}
// Associates a field name with an error.
func (err *ValidationError) WithField(field string) *ValidationError {
if err.err == nil {
return err
}
if err.err.Extensions == nil {
err.err.Extensions = make(map[string]interface{})
}
err.err.Extensions["field"] = field
return err
}
// Composes another assertion onto the same validation context which initially
// created an error. Short-circuiting is used, such that if the earlier
// condition failed, the new condition is not considered.
func (err *ValidationError) And(cond bool, msg string) *ValidationError {
if err.err != nil {
return err
}
return err.valid.Expect(cond, msg)
}