1package semver 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "regexp" 8 "strings" 9) 10 11// Constraints is one or more constraint that a semantic version can be 12// checked against. 13type Constraints struct { 14 constraints [][]*constraint 15} 16 17// NewConstraint returns a Constraints instance that a Version instance can 18// be checked against. If there is a parse error it will be returned. 19func NewConstraint(c string) (*Constraints, error) { 20 21 // Rewrite - ranges into a comparison operation. 22 c = rewriteRange(c) 23 24 ors := strings.Split(c, "||") 25 or := make([][]*constraint, len(ors)) 26 for k, v := range ors { 27 28 // TODO: Find a way to validate and fetch all the constraints in a simpler form 29 30 // Validate the segment 31 if !validConstraintRegex.MatchString(v) { 32 return nil, fmt.Errorf("improper constraint: %s", v) 33 } 34 35 cs := findConstraintRegex.FindAllString(v, -1) 36 if cs == nil { 37 cs = append(cs, v) 38 } 39 result := make([]*constraint, len(cs)) 40 for i, s := range cs { 41 pc, err := parseConstraint(s) 42 if err != nil { 43 return nil, err 44 } 45 46 result[i] = pc 47 } 48 or[k] = result 49 } 50 51 o := &Constraints{constraints: or} 52 return o, nil 53} 54 55// Check tests if a version satisfies the constraints. 56func (cs Constraints) Check(v *Version) bool { 57 // TODO(mattfarina): For v4 of this library consolidate the Check and Validate 58 // functions as the underlying functions make that possible now. 59 // loop over the ORs and check the inner ANDs 60 for _, o := range cs.constraints { 61 joy := true 62 for _, c := range o { 63 if check, _ := c.check(v); !check { 64 joy = false 65 break 66 } 67 } 68 69 if joy { 70 return true 71 } 72 } 73 74 return false 75} 76 77// Validate checks if a version satisfies a constraint. If not a slice of 78// reasons for the failure are returned in addition to a bool. 79func (cs Constraints) Validate(v *Version) (bool, []error) { 80 // loop over the ORs and check the inner ANDs 81 var e []error 82 83 // Capture the prerelease message only once. When it happens the first time 84 // this var is marked 85 var prerelesase bool 86 for _, o := range cs.constraints { 87 joy := true 88 for _, c := range o { 89 // Before running the check handle the case there the version is 90 // a prerelease and the check is not searching for prereleases. 91 if c.con.pre == "" && v.pre != "" { 92 if !prerelesase { 93 em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 94 e = append(e, em) 95 prerelesase = true 96 } 97 joy = false 98 99 } else { 100 101 if _, err := c.check(v); err != nil { 102 e = append(e, err) 103 joy = false 104 } 105 } 106 } 107 108 if joy { 109 return true, []error{} 110 } 111 } 112 113 return false, e 114} 115 116func (cs Constraints) String() string { 117 buf := make([]string, len(cs.constraints)) 118 var tmp bytes.Buffer 119 120 for k, v := range cs.constraints { 121 tmp.Reset() 122 vlen := len(v) 123 for kk, c := range v { 124 tmp.WriteString(c.string()) 125 126 // Space separate the AND conditions 127 if vlen > 1 && kk < vlen-1 { 128 tmp.WriteString(" ") 129 } 130 } 131 buf[k] = tmp.String() 132 } 133 134 return strings.Join(buf, " || ") 135} 136 137var constraintOps map[string]cfunc 138var constraintRegex *regexp.Regexp 139var constraintRangeRegex *regexp.Regexp 140 141// Used to find individual constraints within a multi-constraint string 142var findConstraintRegex *regexp.Regexp 143 144// Used to validate an segment of ANDs is valid 145var validConstraintRegex *regexp.Regexp 146 147const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + 148 `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + 149 `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` 150 151func init() { 152 constraintOps = map[string]cfunc{ 153 "": constraintTildeOrEqual, 154 "=": constraintTildeOrEqual, 155 "!=": constraintNotEqual, 156 ">": constraintGreaterThan, 157 "<": constraintLessThan, 158 ">=": constraintGreaterThanEqual, 159 "=>": constraintGreaterThanEqual, 160 "<=": constraintLessThanEqual, 161 "=<": constraintLessThanEqual, 162 "~": constraintTilde, 163 "~>": constraintTilde, 164 "^": constraintCaret, 165 } 166 167 ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^` 168 169 constraintRegex = regexp.MustCompile(fmt.Sprintf( 170 `^\s*(%s)\s*(%s)\s*$`, 171 ops, 172 cvRegex)) 173 174 constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( 175 `\s*(%s)\s+-\s+(%s)\s*`, 176 cvRegex, cvRegex)) 177 178 findConstraintRegex = regexp.MustCompile(fmt.Sprintf( 179 `(%s)\s*(%s)`, 180 ops, 181 cvRegex)) 182 183 validConstraintRegex = regexp.MustCompile(fmt.Sprintf( 184 `^(\s*(%s)\s*(%s)\s*\,?)+$`, 185 ops, 186 cvRegex)) 187} 188 189// An individual constraint 190type constraint struct { 191 // The version used in the constraint check. For example, if a constraint 192 // is '<= 2.0.0' the con a version instance representing 2.0.0. 193 con *Version 194 195 // The original parsed version (e.g., 4.x from != 4.x) 196 orig string 197 198 // The original operator for the constraint 199 origfunc string 200 201 // When an x is used as part of the version (e.g., 1.x) 202 minorDirty bool 203 dirty bool 204 patchDirty bool 205} 206 207// Check if a version meets the constraint 208func (c *constraint) check(v *Version) (bool, error) { 209 return constraintOps[c.origfunc](v, c) 210} 211 212// String prints an individual constraint into a string 213func (c *constraint) string() string { 214 return c.origfunc + c.orig 215} 216 217type cfunc func(v *Version, c *constraint) (bool, error) 218 219func parseConstraint(c string) (*constraint, error) { 220 if len(c) > 0 { 221 m := constraintRegex.FindStringSubmatch(c) 222 if m == nil { 223 return nil, fmt.Errorf("improper constraint: %s", c) 224 } 225 226 cs := &constraint{ 227 orig: m[2], 228 origfunc: m[1], 229 } 230 231 ver := m[2] 232 minorDirty := false 233 patchDirty := false 234 dirty := false 235 if isX(m[3]) || m[3] == "" { 236 ver = "0.0.0" 237 dirty = true 238 } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { 239 minorDirty = true 240 dirty = true 241 ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) 242 } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { 243 dirty = true 244 patchDirty = true 245 ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) 246 } 247 248 con, err := NewVersion(ver) 249 if err != nil { 250 251 // The constraintRegex should catch any regex parsing errors. So, 252 // we should never get here. 253 return nil, errors.New("constraint Parser Error") 254 } 255 256 cs.con = con 257 cs.minorDirty = minorDirty 258 cs.patchDirty = patchDirty 259 cs.dirty = dirty 260 261 return cs, nil 262 } 263 264 // The rest is the special case where an empty string was passed in which 265 // is equivalent to * or >=0.0.0 266 con, err := StrictNewVersion("0.0.0") 267 if err != nil { 268 269 // The constraintRegex should catch any regex parsing errors. So, 270 // we should never get here. 271 return nil, errors.New("constraint Parser Error") 272 } 273 274 cs := &constraint{ 275 con: con, 276 orig: c, 277 origfunc: "", 278 minorDirty: false, 279 patchDirty: false, 280 dirty: true, 281 } 282 return cs, nil 283} 284 285// Constraint functions 286func constraintNotEqual(v *Version, c *constraint) (bool, error) { 287 if c.dirty { 288 289 // If there is a pre-release on the version but the constraint isn't looking 290 // for them assume that pre-releases are not compatible. See issue 21 for 291 // more details. 292 if v.Prerelease() != "" && c.con.Prerelease() == "" { 293 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 294 } 295 296 if c.con.Major() != v.Major() { 297 return true, nil 298 } 299 if c.con.Minor() != v.Minor() && !c.minorDirty { 300 return true, nil 301 } else if c.minorDirty { 302 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 303 } else if c.con.Patch() != v.Patch() && !c.patchDirty { 304 return true, nil 305 } else if c.patchDirty { 306 // Need to handle prereleases if present 307 if v.Prerelease() != "" || c.con.Prerelease() != "" { 308 eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 309 if eq { 310 return true, nil 311 } 312 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 313 } 314 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 315 } 316 } 317 318 eq := v.Equal(c.con) 319 if eq { 320 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 321 } 322 323 return true, nil 324} 325 326func constraintGreaterThan(v *Version, c *constraint) (bool, error) { 327 328 // If there is a pre-release on the version but the constraint isn't looking 329 // for them assume that pre-releases are not compatible. See issue 21 for 330 // more details. 331 if v.Prerelease() != "" && c.con.Prerelease() == "" { 332 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 333 } 334 335 var eq bool 336 337 if !c.dirty { 338 eq = v.Compare(c.con) == 1 339 if eq { 340 return true, nil 341 } 342 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 343 } 344 345 if v.Major() > c.con.Major() { 346 return true, nil 347 } else if v.Major() < c.con.Major() { 348 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 349 } else if c.minorDirty { 350 // This is a range case such as >11. When the version is something like 351 // 11.1.0 is it not > 11. For that we would need 12 or higher 352 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 353 } else if c.patchDirty { 354 // This is for ranges such as >11.1. A version of 11.1.1 is not greater 355 // which one of 11.2.1 is greater 356 eq = v.Minor() > c.con.Minor() 357 if eq { 358 return true, nil 359 } 360 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 361 } 362 363 // If we have gotten here we are not comparing pre-preleases and can use the 364 // Compare function to accomplish that. 365 eq = v.Compare(c.con) == 1 366 if eq { 367 return true, nil 368 } 369 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 370} 371 372func constraintLessThan(v *Version, c *constraint) (bool, error) { 373 // If there is a pre-release on the version but the constraint isn't looking 374 // for them assume that pre-releases are not compatible. See issue 21 for 375 // more details. 376 if v.Prerelease() != "" && c.con.Prerelease() == "" { 377 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 378 } 379 380 eq := v.Compare(c.con) < 0 381 if eq { 382 return true, nil 383 } 384 return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) 385} 386 387func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { 388 389 // If there is a pre-release on the version but the constraint isn't looking 390 // for them assume that pre-releases are not compatible. See issue 21 for 391 // more details. 392 if v.Prerelease() != "" && c.con.Prerelease() == "" { 393 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 394 } 395 396 eq := v.Compare(c.con) >= 0 397 if eq { 398 return true, nil 399 } 400 return false, fmt.Errorf("%s is less than %s", v, c.orig) 401} 402 403func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { 404 // If there is a pre-release on the version but the constraint isn't looking 405 // for them assume that pre-releases are not compatible. See issue 21 for 406 // more details. 407 if v.Prerelease() != "" && c.con.Prerelease() == "" { 408 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 409 } 410 411 var eq bool 412 413 if !c.dirty { 414 eq = v.Compare(c.con) <= 0 415 if eq { 416 return true, nil 417 } 418 return false, fmt.Errorf("%s is greater than %s", v, c.orig) 419 } 420 421 if v.Major() > c.con.Major() { 422 return false, fmt.Errorf("%s is greater than %s", v, c.orig) 423 } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty { 424 return false, fmt.Errorf("%s is greater than %s", v, c.orig) 425 } 426 427 return true, nil 428} 429 430// ~*, ~>* --> >= 0.0.0 (any) 431// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 432// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 433// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 434// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 435// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 436func constraintTilde(v *Version, c *constraint) (bool, error) { 437 // If there is a pre-release on the version but the constraint isn't looking 438 // for them assume that pre-releases are not compatible. See issue 21 for 439 // more details. 440 if v.Prerelease() != "" && c.con.Prerelease() == "" { 441 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 442 } 443 444 if v.LessThan(c.con) { 445 return false, fmt.Errorf("%s is less than %s", v, c.orig) 446 } 447 448 // ~0.0.0 is a special case where all constraints are accepted. It's 449 // equivalent to >= 0.0.0. 450 if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && 451 !c.minorDirty && !c.patchDirty { 452 return true, nil 453 } 454 455 if v.Major() != c.con.Major() { 456 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) 457 } 458 459 if v.Minor() != c.con.Minor() && !c.minorDirty { 460 return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig) 461 } 462 463 return true, nil 464} 465 466// When there is a .x (dirty) status it automatically opts in to ~. Otherwise 467// it's a straight = 468func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { 469 // If there is a pre-release on the version but the constraint isn't looking 470 // for them assume that pre-releases are not compatible. See issue 21 for 471 // more details. 472 if v.Prerelease() != "" && c.con.Prerelease() == "" { 473 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 474 } 475 476 if c.dirty { 477 return constraintTilde(v, c) 478 } 479 480 eq := v.Equal(c.con) 481 if eq { 482 return true, nil 483 } 484 485 return false, fmt.Errorf("%s is not equal to %s", v, c.orig) 486} 487 488// ^* --> (any) 489// ^1.2.3 --> >=1.2.3 <2.0.0 490// ^1.2 --> >=1.2.0 <2.0.0 491// ^1 --> >=1.0.0 <2.0.0 492// ^0.2.3 --> >=0.2.3 <0.3.0 493// ^0.2 --> >=0.2.0 <0.3.0 494// ^0.0.3 --> >=0.0.3 <0.0.4 495// ^0.0 --> >=0.0.0 <0.1.0 496// ^0 --> >=0.0.0 <1.0.0 497func constraintCaret(v *Version, c *constraint) (bool, error) { 498 // If there is a pre-release on the version but the constraint isn't looking 499 // for them assume that pre-releases are not compatible. See issue 21 for 500 // more details. 501 if v.Prerelease() != "" && c.con.Prerelease() == "" { 502 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 503 } 504 505 // This less than handles prereleases 506 if v.LessThan(c.con) { 507 return false, fmt.Errorf("%s is less than %s", v, c.orig) 508 } 509 510 var eq bool 511 512 // ^ when the major > 0 is >=x.y.z < x+1 513 if c.con.Major() > 0 || c.minorDirty { 514 515 // ^ has to be within a major range for > 0. Everything less than was 516 // filtered out with the LessThan call above. This filters out those 517 // that greater but not within the same major range. 518 eq = v.Major() == c.con.Major() 519 if eq { 520 return true, nil 521 } 522 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) 523 } 524 525 // ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1 526 if c.con.Major() == 0 && v.Major() > 0 { 527 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) 528 } 529 // If the con Minor is > 0 it is not dirty 530 if c.con.Minor() > 0 || c.patchDirty { 531 eq = v.Minor() == c.con.Minor() 532 if eq { 533 return true, nil 534 } 535 return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig) 536 } 537 538 // At this point the major is 0 and the minor is 0 and not dirty. The patch 539 // is not dirty so we need to check if they are equal. If they are not equal 540 eq = c.con.Patch() == v.Patch() 541 if eq { 542 return true, nil 543 } 544 return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig) 545} 546 547func isX(x string) bool { 548 switch x { 549 case "x", "*", "X": 550 return true 551 default: 552 return false 553 } 554} 555 556func rewriteRange(i string) string { 557 m := constraintRangeRegex.FindAllStringSubmatch(i, -1) 558 if m == nil { 559 return i 560 } 561 o := i 562 for _, v := range m { 563 t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) 564 o = strings.Replace(o, v[0], t, 1) 565 } 566 567 return o 568} 569