github.com/blend/go-sdk@v1.20240719.1/semver/constraint.go (about) 1 /* 2 3 Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package semver 9 10 import ( 11 "fmt" 12 "reflect" 13 "regexp" 14 "strings" 15 ) 16 17 // Constraint represents a single constraint for a version, such as 18 // ">= 1.0". 19 type Constraint struct { 20 f constraintFunc 21 check *Version 22 original string 23 } 24 25 // Constraints is a slice of constraints. We make a custom type so that 26 // we can add methods to it. 27 type Constraints []*Constraint 28 29 type constraintFunc func(v, c *Version) bool 30 31 var constraintOperators map[string]constraintFunc 32 33 var constraintRegexp *regexp.Regexp 34 35 func init() { 36 constraintOperators = map[string]constraintFunc{ 37 "": constraintEqual, 38 "=": constraintEqual, 39 "!=": constraintNotEqual, 40 ">": constraintGreaterThan, 41 "<": constraintLessThan, 42 ">=": constraintGreaterThanEqual, 43 "<=": constraintLessThanEqual, 44 "~>": constraintPessimistic, 45 } 46 47 ops := make([]string, 0, len(constraintOperators)) 48 for k := range constraintOperators { 49 ops = append(ops, regexp.QuoteMeta(k)) 50 } 51 52 constraintRegexp = regexp.MustCompile(fmt.Sprintf( 53 `^\s*(%s)\s*(%s)\s*$`, 54 strings.Join(ops, "|"), 55 VersionRegexpRaw)) 56 } 57 58 // NewConstraint will parse one or more constraints from the given 59 // constraint string. The string must be a comma-separated list of 60 // constraints. 61 func NewConstraint(v string) (Constraints, error) { 62 vs := strings.Split(v, ",") 63 result := make([]*Constraint, len(vs)) 64 for i, single := range vs { 65 c, err := parseSingle(single) 66 if err != nil { 67 return nil, err 68 } 69 70 result[i] = c 71 } 72 73 return Constraints(result), nil 74 } 75 76 // Check tests if a version satisfies all the constraints. 77 func (cs Constraints) Check(v *Version) bool { 78 for _, c := range cs { 79 if !c.Check(v) { 80 return false 81 } 82 } 83 84 return true 85 } 86 87 // Returns the string format of the constraints 88 func (cs Constraints) String() string { 89 csStr := make([]string, len(cs)) 90 for i, c := range cs { 91 csStr[i] = c.String() 92 } 93 94 return strings.Join(csStr, ",") 95 } 96 97 // Check tests if a constraint is validated by the given version. 98 func (c *Constraint) Check(v *Version) bool { 99 return c.f(v, c.check) 100 } 101 102 // String returns the original string. 103 func (c *Constraint) String() string { 104 return c.original 105 } 106 107 func parseSingle(v string) (*Constraint, error) { 108 matches := constraintRegexp.FindStringSubmatch(v) 109 if matches == nil { 110 return nil, fmt.Errorf("malformed constraint: %s", v) 111 } 112 113 check, err := NewVersion(matches[2]) 114 if err != nil { 115 return nil, err 116 } 117 118 return &Constraint{ 119 f: constraintOperators[matches[1]], 120 check: check, 121 original: v, 122 }, nil 123 } 124 125 func prereleaseCheck(v, c *Version) bool { 126 switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; { 127 case cPre && vPre: 128 // A constraint with a pre-release can only match a pre-release version 129 // with the same base segments. 130 return reflect.DeepEqual(c.Segments64(), v.Segments64()) 131 132 case !cPre && vPre: 133 // A constraint without a pre-release can only match a version without a 134 // pre-release. 135 return false 136 137 case cPre && !vPre: 138 // OK, except with the pessimistic operator 139 case !cPre && !vPre: 140 // OK 141 } 142 return true 143 } 144 145 //------------------------------------------------------------------- 146 // Constraint functions 147 //------------------------------------------------------------------- 148 149 func constraintEqual(v, c *Version) bool { 150 return v.Equal(c) 151 } 152 153 func constraintNotEqual(v, c *Version) bool { 154 return !v.Equal(c) 155 } 156 157 func constraintGreaterThan(v, c *Version) bool { 158 return prereleaseCheck(v, c) && v.Compare(c) == 1 159 } 160 161 func constraintLessThan(v, c *Version) bool { 162 return prereleaseCheck(v, c) && v.Compare(c) == -1 163 } 164 165 func constraintGreaterThanEqual(v, c *Version) bool { 166 return prereleaseCheck(v, c) && v.Compare(c) >= 0 167 } 168 169 func constraintLessThanEqual(v, c *Version) bool { 170 return prereleaseCheck(v, c) && v.Compare(c) <= 0 171 } 172 173 func constraintPessimistic(v, c *Version) bool { 174 // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases 175 if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") { 176 return false 177 } 178 179 // If the version being checked is naturally less than the constraint, then there 180 // is no way for the version to be valid against the constraint 181 if v.LessThan(c) { 182 return false 183 } 184 // We'll use this more than once, so grab the length now so it's a little cleaner 185 // to write the later checks 186 cs := len(c.segments) 187 188 // If the version being checked has less specificity than the constraint, then there 189 // is no way for the version to be valid against the constraint 190 if cs > len(v.segments) { 191 return false 192 } 193 194 // Check the segments in the constraint against those in the version. If the version 195 // being checked, at any point, does not have the same values in each index of the 196 // constraints segments, then it cannot be valid against the constraint. 197 for i := 0; i < c.si-1; i++ { 198 if v.segments[i] != c.segments[i] { 199 return false 200 } 201 } 202 203 // Check the last part of the segment in the constraint. If the version segment at 204 // this index is less than the constraints segment at this index, then it cannot 205 // be valid against the constraint 206 return c.segments[cs-1] <= v.segments[cs-1] 207 }