Update module github.com/Masterminds/semver/v3 to v3.4.0

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
renovate[bot] 2025-06-30 15:53:16 +00:00 committed by GitHub
parent a4ab531365
commit ef2375bc80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 268 additions and 76 deletions

2
go.mod
View File

@ -6,7 +6,7 @@ go 1.23.3
// Warning: Ensure the "go" and "toolchain" versions match exactly to prevent unwanted auto-updates // Warning: Ensure the "go" and "toolchain" versions match exactly to prevent unwanted auto-updates
require ( require (
github.com/Masterminds/semver/v3 v3.3.1 github.com/Masterminds/semver/v3 v3.4.0
github.com/containers/common v0.63.1 github.com/containers/common v0.63.1
github.com/containers/image/v5 v5.35.0 github.com/containers/image/v5 v5.35.0
github.com/containers/ocicrypt v1.2.1 github.com/containers/ocicrypt v1.2.1

4
go.sum
View File

@ -8,8 +8,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=

View File

@ -1,5 +1,31 @@
# Changelog # Changelog
## 3.4.0 (2025-06-27)
### Added
- #268: Added property to Constraints to include prereleases for Check and Validate
### Changed
- #263: Updated Go testing for 1.24, 1.23, and 1.22
- #269: Updated the error message handling for message case and wrapping errors
- #266: Restore the ability to have leading 0's when parsing with NewVersion.
Opt-out of this by setting CoerceNewVersion to false.
### Fixed
- #257: Fixed the CodeQL link (thanks @dmitris)
- #262: Restored detailed errors when failed to parse with NewVersion. Opt-out
of this by setting DetailedNewVersionErrors to false for faster performance.
- #267: Handle pre-releases for an "and" group if one constraint includes them
## 3.3.1 (2024-11-19)
### Fixed
- #253: Fix for allowing some version that were invalid
## 3.3.0 (2024-08-27) ## 3.3.0 (2024-08-27)
### Added ### Added

View File

@ -50,6 +50,18 @@ other versions, convert the version back into a string, and get the original
string. Getting the original string is useful if the semantic version was coerced string. Getting the original string is useful if the semantic version was coerced
into a valid form. into a valid form.
There are package level variables that affect how `NewVersion` handles parsing.
- `CoerceNewVersion` is `true` by default. When set to `true` it coerces non-compliant
versions into SemVer. For example, allowing a leading 0 in a major, minor, or patch
part. This enables the use of CalVer in versions even when not compliant with SemVer.
When set to `false` less coercion work is done.
- `DetailedNewVersionErrors` provides more detailed errors. It only has an affect when
`CoerceNewVersion` is set to `false`. When `DetailedNewVersionErrors` is set to `true`
it can provide some more insight into why a version is invalid. Setting
`DetailedNewVersionErrors` to `false` is faster on performance but provides less
detailed error messages if a version fails to parse.
## Sorting Semantic Versions ## Sorting Semantic Versions
A set of versions can be sorted using the `sort` package from the standard library. A set of versions can be sorted using the `sort` package from the standard library.
@ -160,6 +172,10 @@ means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case
sensitivity doesn't apply here. This is due to ASCII sort ordering which is what sensitivity doesn't apply here. This is due to ASCII sort ordering which is what
the spec specifies. the spec specifies.
The `Constraints` instance returned from `semver.NewConstraint()` has a property
`IncludePrerelease` that, when set to true, will return prerelease versions when calls
to `Check()` and `Validate()` are made.
### Hyphen Range Comparisons ### Hyphen Range Comparisons
There are multiple methods to handle ranges and the first is hyphens ranges. There are multiple methods to handle ranges and the first is hyphens ranges.
@ -250,7 +266,7 @@ or [create a pull request](https://github.com/Masterminds/semver/pulls).
Security is an important consideration for this project. The project currently Security is an important consideration for this project. The project currently
uses the following tools to help discover security issues: uses the following tools to help discover security issues:
* [CodeQL](https://github.com/Masterminds/semver) * [CodeQL](https://codeql.github.com)
* [gosec](https://github.com/securego/gosec) * [gosec](https://github.com/securego/gosec)
* Daily Fuzz testing * Daily Fuzz testing

View File

@ -12,6 +12,13 @@ import (
// checked against. // checked against.
type Constraints struct { type Constraints struct {
constraints [][]*constraint constraints [][]*constraint
containsPre []bool
// IncludePrerelease specifies if pre-releases should be included in
// the results. Note, if a constraint range has a prerelease than
// prereleases will be included for that AND group even if this is
// set to false.
IncludePrerelease bool
} }
// NewConstraint returns a Constraints instance that a Version instance can // NewConstraint returns a Constraints instance that a Version instance can
@ -22,11 +29,10 @@ func NewConstraint(c string) (*Constraints, error) {
c = rewriteRange(c) c = rewriteRange(c)
ors := strings.Split(c, "||") ors := strings.Split(c, "||")
or := make([][]*constraint, len(ors)) lenors := len(ors)
or := make([][]*constraint, lenors)
hasPre := make([]bool, lenors)
for k, v := range ors { for k, v := range ors {
// TODO: Find a way to validate and fetch all the constraints in a simpler form
// Validate the segment // Validate the segment
if !validConstraintRegex.MatchString(v) { if !validConstraintRegex.MatchString(v) {
return nil, fmt.Errorf("improper constraint: %s", v) return nil, fmt.Errorf("improper constraint: %s", v)
@ -43,12 +49,22 @@ func NewConstraint(c string) (*Constraints, error) {
return nil, err return nil, err
} }
// If one of the constraints has a prerelease record this.
// This information is used when checking all in an "and"
// group to ensure they all check for prereleases.
if pc.con.pre != "" {
hasPre[k] = true
}
result[i] = pc result[i] = pc
} }
or[k] = result or[k] = result
} }
o := &Constraints{constraints: or} o := &Constraints{
constraints: or,
containsPre: hasPre,
}
return o, nil return o, nil
} }
@ -57,10 +73,10 @@ func (cs Constraints) Check(v *Version) bool {
// TODO(mattfarina): For v4 of this library consolidate the Check and Validate // TODO(mattfarina): For v4 of this library consolidate the Check and Validate
// functions as the underlying functions make that possible now. // functions as the underlying functions make that possible now.
// loop over the ORs and check the inner ANDs // loop over the ORs and check the inner ANDs
for _, o := range cs.constraints { for i, o := range cs.constraints {
joy := true joy := true
for _, c := range o { for _, c := range o {
if check, _ := c.check(v); !check { if check, _ := c.check(v, (cs.IncludePrerelease || cs.containsPre[i])); !check {
joy = false joy = false
break break
} }
@ -83,12 +99,12 @@ func (cs Constraints) Validate(v *Version) (bool, []error) {
// Capture the prerelease message only once. When it happens the first time // Capture the prerelease message only once. When it happens the first time
// this var is marked // this var is marked
var prerelesase bool var prerelesase bool
for _, o := range cs.constraints { for i, o := range cs.constraints {
joy := true joy := true
for _, c := range o { for _, c := range o {
// Before running the check handle the case there the version is // Before running the check handle the case there the version is
// a prerelease and the check is not searching for prereleases. // a prerelease and the check is not searching for prereleases.
if c.con.pre == "" && v.pre != "" { if !(cs.IncludePrerelease || cs.containsPre[i]) && v.pre != "" {
if !prerelesase { if !prerelesase {
em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
e = append(e, em) e = append(e, em)
@ -98,7 +114,7 @@ func (cs Constraints) Validate(v *Version) (bool, []error) {
} else { } else {
if _, err := c.check(v); err != nil { if _, err := c.check(v, (cs.IncludePrerelease || cs.containsPre[i])); err != nil {
e = append(e, err) e = append(e, err)
joy = false joy = false
} }
@ -227,8 +243,8 @@ type constraint struct {
} }
// Check if a version meets the constraint // Check if a version meets the constraint
func (c *constraint) check(v *Version) (bool, error) { func (c *constraint) check(v *Version, includePre bool) (bool, error) {
return constraintOps[c.origfunc](v, c) return constraintOps[c.origfunc](v, c, includePre)
} }
// String prints an individual constraint into a string // String prints an individual constraint into a string
@ -236,7 +252,7 @@ func (c *constraint) string() string {
return c.origfunc + c.orig return c.origfunc + c.orig
} }
type cfunc func(v *Version, c *constraint) (bool, error) type cfunc func(v *Version, c *constraint, includePre bool) (bool, error)
func parseConstraint(c string) (*constraint, error) { func parseConstraint(c string) (*constraint, error) {
if len(c) > 0 { if len(c) > 0 {
@ -272,7 +288,7 @@ func parseConstraint(c string) (*constraint, error) {
// The constraintRegex should catch any regex parsing errors. So, // The constraintRegex should catch any regex parsing errors. So,
// we should never get here. // we should never get here.
return nil, errors.New("constraint Parser Error") return nil, errors.New("constraint parser error")
} }
cs.con = con cs.con = con
@ -290,7 +306,7 @@ func parseConstraint(c string) (*constraint, error) {
// The constraintRegex should catch any regex parsing errors. So, // The constraintRegex should catch any regex parsing errors. So,
// we should never get here. // we should never get here.
return nil, errors.New("constraint Parser Error") return nil, errors.New("constraint parser error")
} }
cs := &constraint{ cs := &constraint{
@ -305,16 +321,14 @@ func parseConstraint(c string) (*constraint, error) {
} }
// Constraint functions // Constraint functions
func constraintNotEqual(v *Version, c *constraint) (bool, error) { func constraintNotEqual(v *Version, c *constraint, includePre bool) (bool, error) {
// The existence of prereleases is checked at the group level and passed in.
// Exit early if the version has a prerelease but those are to be ignored.
if v.Prerelease() != "" && !includePre {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
}
if c.dirty { if c.dirty {
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
}
if c.con.Major() != v.Major() { if c.con.Major() != v.Major() {
return true, nil return true, nil
} }
@ -345,12 +359,11 @@ func constraintNotEqual(v *Version, c *constraint) (bool, error) {
return true, nil return true, nil
} }
func constraintGreaterThan(v *Version, c *constraint) (bool, error) { func constraintGreaterThan(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }
@ -391,11 +404,10 @@ func constraintGreaterThan(v *Version, c *constraint) (bool, error) {
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
} }
func constraintLessThan(v *Version, c *constraint) (bool, error) { func constraintLessThan(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }
@ -406,12 +418,11 @@ func constraintLessThan(v *Version, c *constraint) (bool, error) {
return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
} }
func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { func constraintGreaterThanEqual(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }
@ -422,11 +433,10 @@ func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) {
return false, fmt.Errorf("%s is less than %s", v, c.orig) return false, fmt.Errorf("%s is less than %s", v, c.orig)
} }
func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { func constraintLessThanEqual(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }
@ -455,11 +465,10 @@ func constraintLessThanEqual(v *Version, c *constraint) (bool, error) {
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 // ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 // ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
func constraintTilde(v *Version, c *constraint) (bool, error) { func constraintTilde(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }
@ -487,16 +496,15 @@ func constraintTilde(v *Version, c *constraint) (bool, error) {
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise // When there is a .x (dirty) status it automatically opts in to ~. Otherwise
// it's a straight = // it's a straight =
func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { func constraintTildeOrEqual(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }
if c.dirty { if c.dirty {
return constraintTilde(v, c) return constraintTilde(v, c, includePre)
} }
eq := v.Equal(c.con) eq := v.Equal(c.con)
@ -516,11 +524,10 @@ func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) {
// ^0.0.3 --> >=0.0.3 <0.0.4 // ^0.0.3 --> >=0.0.3 <0.0.4
// ^0.0 --> >=0.0.0 <0.1.0 // ^0.0 --> >=0.0.0 <0.1.0
// ^0 --> >=0.0.0 <1.0.0 // ^0 --> >=0.0.0 <1.0.0
func constraintCaret(v *Version, c *constraint) (bool, error) { func constraintCaret(v *Version, c *constraint, includePre bool) (bool, error) {
// If there is a pre-release on the version but the constraint isn't looking // The existence of prereleases is checked at the group level and passed in.
// for them assume that pre-releases are not compatible. See issue 21 for // Exit early if the version has a prerelease but those are to be ignored.
// more details. if v.Prerelease() != "" && !includePre {
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
} }

View File

@ -14,28 +14,40 @@ import (
// The compiled version of the regex created at init() is cached here so it // The compiled version of the regex created at init() is cached here so it
// only needs to be created once. // only needs to be created once.
var versionRegex *regexp.Regexp var versionRegex *regexp.Regexp
var looseVersionRegex *regexp.Regexp
// CoerceNewVersion sets if leading 0's are allowd in the version part. Leading 0's are
// not allowed in a valid semantic version. When set to true, NewVersion will coerce
// leading 0's into a valid version.
var CoerceNewVersion = true
// DetailedNewVersionErrors specifies if detailed errors are returned from the NewVersion
// function. This is used when CoerceNewVersion is set to false. If set to false
// ErrInvalidSemVer is returned for an invalid version. This does not apply to
// StrictNewVersion. Setting this function to false returns errors more quickly.
var DetailedNewVersionErrors = true
var ( var (
// ErrInvalidSemVer is returned a version is found to be invalid when // ErrInvalidSemVer is returned a version is found to be invalid when
// being parsed. // being parsed.
ErrInvalidSemVer = errors.New("Invalid Semantic Version") ErrInvalidSemVer = errors.New("invalid semantic version")
// ErrEmptyString is returned when an empty string is passed in for parsing. // ErrEmptyString is returned when an empty string is passed in for parsing.
ErrEmptyString = errors.New("Version string empty") ErrEmptyString = errors.New("version string empty")
// ErrInvalidCharacters is returned when invalid characters are found as // ErrInvalidCharacters is returned when invalid characters are found as
// part of a version // part of a version
ErrInvalidCharacters = errors.New("Invalid characters in version") ErrInvalidCharacters = errors.New("invalid characters in version")
// ErrSegmentStartsZero is returned when a version segment starts with 0. // ErrSegmentStartsZero is returned when a version segment starts with 0.
// This is invalid in SemVer. // This is invalid in SemVer.
ErrSegmentStartsZero = errors.New("Version segment starts with 0") ErrSegmentStartsZero = errors.New("version segment starts with 0")
// ErrInvalidMetadata is returned when the metadata is an invalid format // ErrInvalidMetadata is returned when the metadata is an invalid format
ErrInvalidMetadata = errors.New("Invalid Metadata string") ErrInvalidMetadata = errors.New("invalid metadata string")
// ErrInvalidPrerelease is returned when the pre-release is an invalid format // ErrInvalidPrerelease is returned when the pre-release is an invalid format
ErrInvalidPrerelease = errors.New("Invalid Prerelease string") ErrInvalidPrerelease = errors.New("invalid prerelease string")
) )
// semVerRegex is the regular expression used to parse a semantic version. // semVerRegex is the regular expression used to parse a semantic version.
@ -45,6 +57,12 @@ const semVerRegex string = `v?(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?
`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` +
`(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?` `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?`
// looseSemVerRegex is a regular expression that lets invalid semver expressions through
// with enough detail that certain errors can be checked for.
const looseSemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
// Version represents a single semantic version. // Version represents a single semantic version.
type Version struct { type Version struct {
major, minor, patch uint64 major, minor, patch uint64
@ -55,6 +73,7 @@ type Version struct {
func init() { func init() {
versionRegex = regexp.MustCompile("^" + semVerRegex + "$") versionRegex = regexp.MustCompile("^" + semVerRegex + "$")
looseVersionRegex = regexp.MustCompile("^" + looseSemVerRegex + "$")
} }
const ( const (
@ -142,8 +161,27 @@ func StrictNewVersion(v string) (*Version, error) {
// attempts to convert it to SemVer. If you want to validate it was a strict // attempts to convert it to SemVer. If you want to validate it was a strict
// semantic version at parse time see StrictNewVersion(). // semantic version at parse time see StrictNewVersion().
func NewVersion(v string) (*Version, error) { func NewVersion(v string) (*Version, error) {
if CoerceNewVersion {
return coerceNewVersion(v)
}
m := versionRegex.FindStringSubmatch(v) m := versionRegex.FindStringSubmatch(v)
if m == nil { if m == nil {
// Disabling detailed errors is first so that it is in the fast path.
if !DetailedNewVersionErrors {
return nil, ErrInvalidSemVer
}
// Check for specific errors with the semver string and return a more detailed
// error.
m = looseVersionRegex.FindStringSubmatch(v)
if m == nil {
return nil, ErrInvalidSemVer
}
err := validateVersion(m)
if err != nil {
return nil, err
}
return nil, ErrInvalidSemVer return nil, ErrInvalidSemVer
} }
@ -156,13 +194,13 @@ func NewVersion(v string) (*Version, error) {
var err error var err error
sv.major, err = strconv.ParseUint(m[1], 10, 64) sv.major, err = strconv.ParseUint(m[1], 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err) return nil, fmt.Errorf("error parsing version segment: %w", err)
} }
if m[2] != "" { if m[2] != "" {
sv.minor, err = strconv.ParseUint(m[2], 10, 64) sv.minor, err = strconv.ParseUint(m[2], 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err) return nil, fmt.Errorf("error parsing version segment: %w", err)
} }
} else { } else {
sv.minor = 0 sv.minor = 0
@ -171,7 +209,61 @@ func NewVersion(v string) (*Version, error) {
if m[3] != "" { if m[3] != "" {
sv.patch, err = strconv.ParseUint(m[3], 10, 64) sv.patch, err = strconv.ParseUint(m[3], 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err) return nil, fmt.Errorf("error parsing version segment: %w", err)
}
} else {
sv.patch = 0
}
// Perform some basic due diligence on the extra parts to ensure they are
// valid.
if sv.pre != "" {
if err = validatePrerelease(sv.pre); err != nil {
return nil, err
}
}
if sv.metadata != "" {
if err = validateMetadata(sv.metadata); err != nil {
return nil, err
}
}
return sv, nil
}
func coerceNewVersion(v string) (*Version, error) {
m := looseVersionRegex.FindStringSubmatch(v)
if m == nil {
return nil, ErrInvalidSemVer
}
sv := &Version{
metadata: m[8],
pre: m[5],
original: v,
}
var err error
sv.major, err = strconv.ParseUint(m[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing version segment: %w", err)
}
if m[2] != "" {
sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing version segment: %w", err)
}
} else {
sv.minor = 0
}
if m[3] != "" {
sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing version segment: %w", err)
} }
} else { } else {
sv.patch = 0 sv.patch = 0
@ -615,7 +707,7 @@ func validatePrerelease(p string) error {
eparts := strings.Split(p, ".") eparts := strings.Split(p, ".")
for _, p := range eparts { for _, p := range eparts {
if p == "" { if p == "" {
return ErrInvalidMetadata return ErrInvalidPrerelease
} else if containsOnly(p, num) { } else if containsOnly(p, num) {
if len(p) > 1 && p[0] == '0' { if len(p) > 1 && p[0] == '0' {
return ErrSegmentStartsZero return ErrSegmentStartsZero
@ -643,3 +735,54 @@ func validateMetadata(m string) error {
} }
return nil return nil
} }
// validateVersion checks for common validation issues but may not catch all errors
func validateVersion(m []string) error {
var err error
var v string
if m[1] != "" {
if len(m[1]) > 1 && m[1][0] == '0' {
return ErrSegmentStartsZero
}
_, err = strconv.ParseUint(m[1], 10, 64)
if err != nil {
return fmt.Errorf("error parsing version segment: %w", err)
}
}
if m[2] != "" {
v = strings.TrimPrefix(m[2], ".")
if len(v) > 1 && v[0] == '0' {
return ErrSegmentStartsZero
}
_, err = strconv.ParseUint(v, 10, 64)
if err != nil {
return fmt.Errorf("error parsing version segment: %w", err)
}
}
if m[3] != "" {
v = strings.TrimPrefix(m[3], ".")
if len(v) > 1 && v[0] == '0' {
return ErrSegmentStartsZero
}
_, err = strconv.ParseUint(v, 10, 64)
if err != nil {
return fmt.Errorf("error parsing version segment: %w", err)
}
}
if m[5] != "" {
if err = validatePrerelease(m[5]); err != nil {
return err
}
}
if m[8] != "" {
if err = validateMetadata(m[8]); err != nil {
return err
}
}
return nil
}

2
vendor/modules.txt vendored
View File

@ -5,7 +5,7 @@ dario.cat/mergo
## explicit; go 1.18 ## explicit; go 1.18
github.com/BurntSushi/toml github.com/BurntSushi/toml
github.com/BurntSushi/toml/internal github.com/BurntSushi/toml/internal
# github.com/Masterminds/semver/v3 v3.3.1 # github.com/Masterminds/semver/v3 v3.4.0
## explicit; go 1.21 ## explicit; go 1.21
github.com/Masterminds/semver/v3 github.com/Masterminds/semver/v3
# github.com/Microsoft/go-winio v0.6.2 # github.com/Microsoft/go-winio v0.6.2