diff --git a/internal/gperr/builder.go b/internal/gperr/builder.go index 68922f0..21eca3a 100644 --- a/internal/gperr/builder.go +++ b/internal/gperr/builder.go @@ -59,6 +59,9 @@ func (b *Builder) Error() Error { if len(b.errs) == 0 { return nil } + if len(b.errs) == 1 && b.about == "" { + return wrap(b.errs[0]) + } return &nestedError{Err: New(b.about), Extras: b.errs} } diff --git a/internal/gperr/nested_error.go b/internal/gperr/nested_error.go index 7b620c3..888f350 100644 --- a/internal/gperr/nested_error.go +++ b/internal/gperr/nested_error.go @@ -11,9 +11,11 @@ type nestedError struct { Extras []error `json:"extras"` } +var emptyError = errStr("") + func (err nestedError) Subject(subject string) Error { if err.Err == nil { - err.Err = PrependSubject(subject, errStr("")) + err.Err = PrependSubject(subject, emptyError) } else { err.Err = PrependSubject(subject, err.Err) } diff --git a/internal/gperr/subject.go b/internal/gperr/subject.go index bed2b72..3a336f0 100644 --- a/internal/gperr/subject.go +++ b/internal/gperr/subject.go @@ -45,6 +45,11 @@ func PrependSubject(subject string, err error) error { switch err := err.(type) { case *withSubject: return err.Prepend(subject) + case *wrappedError: + return &wrappedError{ + Err: PrependSubject(subject, err.Err), + Message: err.Message, + } case Error: return err.Subject(subject) } @@ -95,20 +100,24 @@ func (err *withSubject) Markdown() []byte { func (err *withSubject) fmtError(highlight highlightFunc) []byte { // subject is in reversed order - n := len(err.Subjects) size := 0 errStr := err.Err.Error() + subjects := err.Subjects + if err.pendingSubject != "" { + subjects = append(subjects, err.pendingSubject) + } var buf bytes.Buffer - for _, s := range err.Subjects { + for _, s := range subjects { size += len(s) } + n := len(subjects) buf.Grow(size + 2 + n*len(subjectSep) + len(errStr) + len(highlight(""))) for i := n - 1; i > 0; i-- { - buf.WriteString(err.Subjects[i]) + buf.WriteString(subjects[i]) buf.WriteString(subjectSep) } - buf.WriteString(highlight(err.Subjects[0])) + buf.WriteString(highlight(subjects[0])) if errStr != "" { buf.WriteString(": ") buf.WriteString(errStr) diff --git a/internal/utils/serialization.go b/internal/utils/serialization.go index cecb193..b2930d5 100644 --- a/internal/utils/serialization.go +++ b/internal/utils/serialization.go @@ -256,7 +256,7 @@ func mapUnmarshalValidate(src SerializedObject, dst any, checkValidateTag bool) if field, ok := mapping[strutils.ToLowerNoSnake(k)]; ok { err := Convert(reflect.ValueOf(v), field, !hasValidateTag) if err != nil { - errs.Add(err) + errs.Add(err.Subject(k)) } } else { errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMean(NearestField(k, mapping)))) @@ -330,10 +330,6 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. srcT = src.Type() } - if !dst.CanSet() { - return ErrUnsettable.Subject(dstT.String()) - } - if dst.Kind() == reflect.Pointer { if dst.IsNil() { dst.Set(New(dstT.Elem())) @@ -346,16 +342,25 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. switch { case srcT.AssignableTo(dstT): + if !dst.CanSet() { + return ErrUnsettable.Subject(dstT.String()) + } dst.Set(src) return nil // case srcT.ConvertibleTo(dstT): // dst.Set(src.Convert(dstT)) // return nil case srcKind == reflect.String: + if !dst.CanSet() { + return ErrUnsettable.Subject(dstT.String()) + } if convertible, err := ConvertString(src.String(), dst); convertible { return err } case isIntFloat(srcKind): + if !dst.CanSet() { + return ErrUnsettable.Subject(dstT.String()) + } var strV string switch { case src.CanInt(): @@ -386,7 +391,7 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. if dstT.Kind() != reflect.Slice { return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String()) } - sliceErrs := gperr.NewBuilder("slice conversion errors") + sliceErrs := gperr.NewBuilder() newSlice := reflect.MakeSlice(dstT, src.Len(), src.Len()) i := 0 for j, v := range src.Seq2() { @@ -469,7 +474,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe if !isMultiline && src[0] != '-' { values := strutils.CommaSeperatedList(src) dst.Set(reflect.MakeSlice(dst.Type(), len(values), len(values))) - errs := gperr.NewBuilder("invalid slice values") + errs := gperr.NewBuilder() for i, v := range values { err := Convert(reflect.ValueOf(v), dst.Index(i), true) if err != nil {