~bigbes/core-go

e4d7b5ff581da26b225a567adbe6109cc8f23211 — Drew DeVault 4 years ago 86e1e93
valid: new module for input validation
1 files changed, 95 insertions(+), 0 deletions(-)

A valid/valid.go
A valid/valid.go => valid/valid.go +95 -0
@@ 0,0 1,95 @@
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 {} 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)
}