resdes

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 27, 2025 License: MIT Imports: 9 Imported by: 0

README

Response Designer

Build Go Reference Version

Removes boilerplate from serving protobuf-based requests.

Install

go get github.com/signal426/resdes

Field Validation

The default message validator utilizes a fluent API to compose a set of policies and conditions under which a field must exist. Any exceptions to this policy are added to the ValidatorErrors object. To handle fields in a custom way, use the Validator function through the CustomValidation API. There can only be one custom function per-instance. To add field-level errors, simply add to the error object passed in and return nil. For any errors that occur outside of the field-level (i.e. io, etc...), return the error.

Response Arrangement

Auth

The Auth stage is where the first function is called. Any errors returned from this stage will return an error immediately.

Validation

The Validation stage is a plug-able stage. It can be executed independent of an arrangement, and can also be passed into compose an arrangement. Any errors returned from this stage will return an error immediately.

Serve

The Serve stage is the last function to be executed and only if any previously declared stages have executed successfully.

Error Types

Calling .Exec(ctx, request) on an arragement returns 2 values:

  • the response object
  • an error object

All errors implement the error interface, so simply calling .Error() will give you the error message that you can wrap however you'd like for downstream handling.

Examples

Field validation only
err := resdes.ForMessage[*v1.UpdateUserRequest](req.GetUpdateMask().GetPaths()...).
	AssertNonZero("user.id", req.GetUser().GetId()).
	AssertNotEqualToWhenInMask("user.first_name", req.GetUser().GetFirstName(), "bob").
	AssertNonZeroWhenInMask("user.last_name", req.GetUser().GetLastName()).
	AssertNonZeroWhenInMask("user.primary_address.line1", req.GetUser().GetPrimaryAddress().GetLine1()).
	AssertNotEqualToWhenInMask("user.primary_address.line2", req.GetUser().GetPrimaryAddress().GetLine2(), "b").
	CustomValidation(func(ctx context.Context, uur *v1.UpdateUserRequest, ve *ValidationErrors) error {
		if uur.GetUser().GetId() == "abc123" {
			ve.AddFieldErr("user.id", errors.New("user id cannot be abc123"))
		}
		// return nil if only adding field-level errors
		return nil
	}).Exec(ctx, req)
Full request handling
resp, err := resdes.Arrange[*v1.UpdateUserRequest, *v1.UpdateUserResponse]().
	WithAuth(func(ctx context.Context, _ *v1.UpdateUserRequest) error {
		return authUpdate(ctx)
	}).
	WithValidate(resdes.ForMessage[*v1.UpdateUserRequest](req.GetUpdateMask().GetPaths()...).
		AssertNonZero("user", req.GetUser()).
		AssertNonZero("user.id", req.GetUser().GetId()),
	).
	WithServe(func(ctx context.Context, uur *v1.UpdateUserRequest) (*v1.UpdateUserResponse, error) {
		return someBusinessLogic(ctx, uur)
	}).Exec(context.Background(), req)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrFieldComparisonFailedNotComparable returned when an equality policy is applied (e.g. AssertNotEqualTo) to an incompatible type
	ErrFieldComparisonFailedNotComparable = errors.New("equality check failed, types not comparable")
	// ErrFieldMustEqualFailed returned when the supplied value does not match the target value
	ErrFieldMustEqualFailed = errors.New("expected values to be equal")
	// ErrFieldMustEqualFailed returned when the supplied value matches a forbidden value
	ErrFieldMustNotEqualFailed = errors.New("field set to forbidden value")
	// ErrFieldMustNotBeZeroFailed returned when the supplied value matches its type's zero-value
	ErrFieldMustNotBeZeroFailed = errors.New("field set to zero value")
)

Functions

func GetPathsFromMask

func GetPathsFromMask(fieldMask ...string) map[string]struct{}

func IsPathInMask

func IsPathInMask(path string, paths map[string]struct{}) bool

func NormalizePath

func NormalizePath(path string) string

Types

type AddFieldValidationErrOption

type AddFieldValidationErrOption func(*FieldError)

func WithExpectedValue

func WithExpectedValue(value any) AddFieldValidationErrOption

func WithValue

func WithValue(value any) AddFieldValidationErrOption

type Arrangement

type Arrangement[T proto.Message, U any] struct {
	// action to run before running field validations
	Auth Auther[T]

	// validator to validate the incoming message
	Validate MessageValidator[T]

	// logic to run if all validations completed successfully --
	// typically some business logic
	Serve Server[T, U]
}

Arrangement represents different actions to take during the execution of serving some request

func Arrange

func Arrange[T proto.Message, U any]() *Arrangement[T, U]

Instantiate a new Arrangement to build

func (*Arrangement[T, U]) Exec

func (s *Arrangement[T, U]) Exec(ctx context.Context, message T) (U, *Error)

Exec runs in the following order: 1. Auth 2. Validate 3. Serve The function exits if any error is encountered at any stage

func (*Arrangement[T, U]) WithAuth

func (r *Arrangement[T, U]) WithAuth(act Auther[T]) *Arrangement[T, U]

Add an Auth behavior

func (*Arrangement[T, U]) WithServe

func (r *Arrangement[T, U]) WithServe(act Server[T, U]) *Arrangement[T, U]

Add a Serve behavior

func (*Arrangement[T, U]) WithValidate

func (r *Arrangement[T, U]) WithValidate(fv MessageValidator[T]) *Arrangement[T, U]

Add a Validate behavior

type AuthError

type AuthError struct {
	Err error
}

AuthError wraps when an error occurs in the auth stage

func NewAuthError

func NewAuthError(err error) *AuthError

func (*AuthError) Error

func (a *AuthError) Error() string

func (*AuthError) Unwrap

func (a *AuthError) Unwrap() error

type Auther

type Auther[T proto.Message] func(context.Context, T) error

Auther a function to run before validation or request serve

type Condition

type Condition uint32
const (
	Always Condition = iota
	InMask
)

type DefaultMessageValidator

type DefaultMessageValidator[T proto.Message] struct {
	// contains filtered or unexported fields
}

func ForMessage

func ForMessage[T proto.Message](fieldMask ...string) *DefaultMessageValidator[T]

ForMessage creates a new DefaultMessageValidator Accepts paths from a field mask if available

func (*DefaultMessageValidator[T]) AssertEqualTo

func (s *DefaultMessageValidator[T]) AssertEqualTo(path string, value any, equalTo any) *DefaultMessageValidator[T]

AssertEqualTo assert that the value for the supplied fialed path is equal to the supplied target value

func (*DefaultMessageValidator[T]) AssertEqualToWhenInMask

func (s *DefaultMessageValidator[T]) AssertEqualToWhenInMask(path string, value any, equalTo any) *DefaultMessageValidator[T]

AssertEqualToWhenInMask same as AssertEqualTo, but only executes if the supplied path is in the field mask

func (*DefaultMessageValidator[T]) AssertNonZero

func (s *DefaultMessageValidator[T]) AssertNonZero(path string, value any) *DefaultMessageValidator[T]

AssertNonZero assert that the value for the supplied field path is not a zero-value

func (*DefaultMessageValidator[T]) AssertNonZeroWhenInMask

func (s *DefaultMessageValidator[T]) AssertNonZeroWhenInMask(path string, value any) *DefaultMessageValidator[T]

AssertNonZeroWhenInMask same as AssertNonZero, but only executes if the supplied path is in the field mask

func (*DefaultMessageValidator[T]) AssertNotEqualTo

func (s *DefaultMessageValidator[T]) AssertNotEqualTo(path string, value any, notEqualTo any) *DefaultMessageValidator[T]

AssertNotEqualTo assert that the value for the supplied field path is not equal to the supplied target value

func (*DefaultMessageValidator[T]) AssertNotEqualToWhenInMask

func (s *DefaultMessageValidator[T]) AssertNotEqualToWhenInMask(path string, value any, notEqualTo any) *DefaultMessageValidator[T]

AssertNotEqualToWhenInMask same as AssertNotEqualTo, but only executes if the supplied path is in the field mask

func (*DefaultMessageValidator[T]) CustomValidation

func (s *DefaultMessageValidator[T]) CustomValidation(act Validator[T]) *DefaultMessageValidator[T]

CustomValidation is a custom validation function. There can only be one per-validator instance. To add field-level errors to the existing list of field validation errors (in the case regular Assertxxx functions are used), add the errors to the ValidationErrors object and return nil.

In the case that a non-field level error occurs, return the err

func (*DefaultMessageValidator[T]) Exec

func (s *DefaultMessageValidator[T]) Exec(ctx context.Context, message T) *ValidationErrors

Exec executes in the following order: 1. Custom validation function if it exists 2. Field-level assertion functions

type Error

type Error struct {
	AuthError      *AuthError
	ValidationErrs *ValidationErrors
	ServeError     *ServeErr
}

Error holds errors for each stage of a request

func NewError

func NewError(errs ...error) *Error

func (*Error) Error

func (e *Error) Error() string

func (*Error) GetAuthError

func (e *Error) GetAuthError() *AuthError

func (*Error) GetServeError

func (e *Error) GetServeError() *ServeErr

func (*Error) GetValidationErrors

func (e *Error) GetValidationErrors() *ValidationErrors

func (*Error) SetAuthError

func (e *Error) SetAuthError(err error)

func (*Error) SetServeError

func (e *Error) SetServeError(err error)

func (*Error) SetValidationErrors

func (e *Error) SetValidationErrors(err error)

func (*Error) ToGrpcStatus

func (e *Error) ToGrpcStatus() *status.Status

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Field

type Field struct {
	// contains filtered or unexported fields
}

func NewField

func NewField(path string, value any, policy Policy, condition Condition, cmpTo any, paths map[string]struct{}) *Field

func (Field) CompareTo

func (f Field) CompareTo() any

func (Field) Condition

func (f Field) Condition() Condition

func (Field) ID

func (f Field) ID() string

func (Field) InMask

func (f Field) InMask() bool

func (Field) Path

func (f Field) Path(normalized bool) string

func (Field) Policy

func (f Field) Policy() Policy

func (Field) Validate

func (f Field) Validate() error

func (Field) Value

func (f Field) Value() any

func (Field) Zero

func (f Field) Zero() bool

type FieldError

type FieldError struct {
	Path     string
	Policy   Policy
	Value    any
	Expected any
	Err      error
}

func FieldErrorFromField

func FieldErrorFromField(f *Field, err error) *FieldError

func (*FieldError) Error

func (f *FieldError) Error() string

func (*FieldError) Unwrap

func (f *FieldError) Unwrap() error

type MessageValidator

type MessageValidator[T proto.Message] interface {
	Exec(context.Context, T) *ValidationErrors
}

MessageValidator responsible for validating supplied fields and returning the results in a ValidationErrors object

type Policy

type Policy uint32
const (
	NonZero Policy = iota
	NotEqualTo
	MustEqual
	Custom
)

func (Policy) String

func (p Policy) String() string

type Request

type Request[T proto.Message] struct {
	Msg     T
	Headers map[string]string
	Claims  map[string]any
	Extras  map[string]any
}

TODO(signal426)

func NewRequest

func NewRequest[T proto.Message](ctx context.Context, msg T) *Request[T]

type Response

type Response[U any] struct {
	Data  U
	Meta  map[string]any
	Error *Error
}

func NewResponse

func NewResponse[U any](data U, err *Error, meta map[string]any) *Response[U]

type ServeErr

type ServeErr struct {
	Err error
}

ServeErr wraps when an error occurs during the handle stage

func NewServeError

func NewServeError(err error) *ServeErr

func (*ServeErr) Error

func (h *ServeErr) Error() string

func (*ServeErr) Unwrap

func (h *ServeErr) Unwrap() error

type Server

type Server[T proto.Message, U any] func(context.Context, T) (U, error)

Server a function to run if all other stages of a request arrangement have passed

type ValidationErrors

type ValidationErrors struct {
	FieldErrors           []*FieldError
	CustomValidationError error
	// contains filtered or unexported fields
}

ValidationErrors holds faults for each field evaluated

func NewValidationErrors

func NewValidationErrors() *ValidationErrors

func ValidationErrorsFromErr

func ValidationErrorsFromErr(err error) *ValidationErrors

func (*ValidationErrors) AddFieldErr

func (v *ValidationErrors) AddFieldErr(path string, err error, options ...AddFieldValidationErrOption)

AddFieldErr adds an error for a field from a custom eval function

func (*ValidationErrors) AsMap

func (v *ValidationErrors) AsMap() map[string]*FieldError

func (*ValidationErrors) Error

func (v *ValidationErrors) Error() string

func (ValidationErrors) HasErrors

func (v ValidationErrors) HasErrors() bool

func (ValidationErrors) Paths

func (v ValidationErrors) Paths() []string

func (*ValidationErrors) SetCustomValidationErr

func (v *ValidationErrors) SetCustomValidationErr(err error)

func (*ValidationErrors) Unwrap

func (v *ValidationErrors) Unwrap() error

type Validator

type Validator[T proto.Message] func(context.Context, T, *ValidationErrors) error

Validator function accepts a context, some message, and pointer to current list of ValidationErrors

Directories

Path Synopsis
test_protos

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL