Skip to content

feat: add TopFieldError interface to expose parent struct reflection #1421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

VDHewei
Copy link

@VDHewei VDHewei commented Apr 24, 2025

feat: add TopFieldError interface exposing parent struct reflection

Description:
This PR introduces a new TopFieldError interface extending FieldError with:

  • Top() reflect.Value method to access parent struct reflection
  • Enables future tag inspection on ancestor structs
  • Backward-compatible via interface assertion
  • Lays foundation for advanced validation scenarios

Key Changes:

  1. New interface in errors.go
  2. Parent ref storage in fieldError
  3. Validation context propagation
  4. Tests for hierarchical field access

Use Case:

if topErr, ok := err.(validator.TopFieldError); ok {
    parentTags := topErr.Top().Type().Field(0).Tag // Access parent metadata
}

Rationale: Maintains validator flexibility while enabling deeper struct inspection capabilities.
(Let me know if you'd like to emphasize any specific technical aspect further.)

@go-playground/validator-maintainers

@VDHewei VDHewei requested a review from a team as a code owner April 24, 2025 14:20
@VDHewei
Copy link
Author

VDHewei commented Apr 24, 2025

example:

MyStruct Struct {
  Data1 string `json:"data1" validate:"required" msg:"{jsonTag} must be required"`
}

func WarpValidatorError(err error) error {
	if err != nil {
		var (
			errMsg           []string
			validationErrors validator.ValidationErrors
		)
		if errors.As(err, &validationErrors) {
			for _, fieldErr := range validationErrors {
				if customMsg := getCustomTagMsg(fieldErr); customMsg != "" {
					errMsg = append(errMsg, customMsg)
				} else {
					errMsg = append(errMsg, fieldErr.Error())
				}
			}
		}
		if len(errMsg) > 0 {
			return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Parameter verification failed,%s", strings.Join(errMsg, ";")))
		}
		return err
	}
	return nil
}

func getCustomTagMsg(e error) string {
	var x validator.TopFieldError
	if !errors.As(e, &x) {
		return e.Error()
	} else {
		var (
			msg string
			t   = x.Top().Type()
		)
		// pointer type
		if t.Kind() == reflect.Ptr {
			t = t.Elem()
		}
		// slice type
		if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
			t = t.Elem()
		}
		name := x.Field()
		field, found := t.FieldByName(name)
		if !found {
			fmt.Printf("Field `%s` not found, param=%s,ns=%s\n", name, x.Param(), x.StructNamespace())
			return ""
		}
		if msg = field.Tag.Get("msg"); msg == "" {
			return msg
		}
		msg = strings.ReplaceAll(msg, "{fieldTag}", x.Field())
		msg = strings.ReplaceAll(msg, "{jsonTag}", splitAndGetFirst(field.Tag.Get("json"), ","))
		return msg
	}
}

func splitAndGetFirst(value, sep string) string {
	if value == "" {
		return ""
	}
	values := strings.Split(value, sep)
	if len(values) >= 1 {
		return values[0]
	}
	return ""
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant