From e4d7b5ff581da26b225a567adbe6109cc8f23211 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 20 Aug 2021 09:39:59 +0200 Subject: [PATCH] valid: new module for input validation --- valid/valid.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 valid/valid.go diff --git a/valid/valid.go b/valid/valid.go new file mode 100644 index 0000000000000000000000000000000000000000..e01ac07b393fa0b38a68d7cec590f46451be4eda --- /dev/null +++ b/valid/valid.go @@ -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) +}