Files
galaxy-game/gamemaster/internal/domain/engineversion/semver.go
T
2026-05-03 07:59:03 +02:00

61 lines
1.9 KiB
Go

package engineversion
import (
"fmt"
"strings"
"golang.org/x/mod/semver"
)
// ParseSemver normalises version into the canonical "vMAJOR.MINOR.PATCH"
// form expected by `golang.org/x/mod/semver` and reports a wrapped
// ErrInvalidSemver when the resulting string is not a valid full semver.
//
// Whitespace is trimmed; a missing leading "v" is added before the
// validity check so callers may pass either "1.2.3" or "v1.2.3". The
// stripped base must carry exactly three dot-separated numeric
// components — `golang.org/x/mod/semver` accepts shortened forms such
// as "v1" or "v1.2", but the engine-version registry requires the full
// triple, so this function rejects anything narrower.
func ParseSemver(version string) (string, error) {
candidate := strings.TrimSpace(version)
if candidate == "" {
return "", fmt.Errorf("%w: empty", ErrInvalidSemver)
}
if !strings.HasPrefix(candidate, "v") {
candidate = "v" + candidate
}
if !semver.IsValid(candidate) {
return "", fmt.Errorf("%w: %q", ErrInvalidSemver, version)
}
base := candidate
if i := strings.IndexAny(base, "-+"); i >= 0 {
base = base[:i]
}
if strings.Count(base, ".") != 2 {
return "", fmt.Errorf(
"%w: %q (need vMAJOR.MINOR.PATCH)",
ErrInvalidSemver, version,
)
}
return candidate, nil
}
// IsPatchUpgrade reports whether next is a same-major.minor upgrade of
// current. Both inputs are parsed through ParseSemver so callers may
// pass either bare or `v`-prefixed forms. A wrapped ErrInvalidSemver is
// returned when either argument fails to parse; the boolean result is
// undefined in that case.
func IsPatchUpgrade(current, next string) (bool, error) {
curr, err := ParseSemver(current)
if err != nil {
return false, fmt.Errorf("current: %w", err)
}
nxt, err := ParseSemver(next)
if err != nil {
return false, fmt.Errorf("next: %w", err)
}
return semver.MajorMinor(curr) == semver.MajorMinor(nxt), nil
}