package main

import (
	"errors"
	"fmt"
	"strconv"
)

// ErrUmurNegatif adalah sentinel penyebab untuk umur di bawah nol.
var ErrUmurNegatif = errors.New("umur tidak boleh negatif")

// ErrorValidasi membawa konteks field dan pesan, bukan sekadar teks.
type ErrorValidasi struct {
	Field string
	Pesan string
	Sebab error
}

func (e *ErrorValidasi) Error() string {
	if e.Sebab != nil {
		return fmt.Sprintf("field %s: %s: %v", e.Field, e.Pesan, e.Sebab)
	}
	return fmt.Sprintf("field %s: %s", e.Field, e.Pesan)
}

func (e *ErrorValidasi) Unwrap() error {
	return e.Sebab
}

func validasiUmur(teks string) error {
	umur, err := strconv.Atoi(teks)
	if err != nil {
		return &ErrorValidasi{Field: "umur", Pesan: "bukan angka", Sebab: err}
	}
	if umur < 0 {
		return &ErrorValidasi{Field: "umur", Pesan: "negatif", Sebab: ErrUmurNegatif}
	}
	return nil
}

func main() {
	for _, masukan := range []string{"17", "-4", "dua puluh"} {
		err := validasiUmur(masukan)
		fmt.Printf("masukan=%q err=%v\n", masukan, err)
		if err == nil {
			continue
		}
		var ev *ErrorValidasi
		if errors.As(err, &ev) {
			fmt.Printf("  field=%q pesan=%q\n", ev.Field, ev.Pesan)
		}
		fmt.Println("  errors.Is ErrUmurNegatif:", errors.Is(err, ErrUmurNegatif))
	}
}
