...

Source file src/github.com/Masterminds/semver/v3/constraints.go

Documentation: github.com/Masterminds/semver/v3

     1  package semver
     2  
     3  import (
     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.
    13  type 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.
    19  func 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.
    56  func (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.
    79  func (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  
   116  func (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  
   137  // UnmarshalText implements the encoding.TextUnmarshaler interface.
   138  func (cs *Constraints) UnmarshalText(text []byte) error {
   139  	temp, err := NewConstraint(string(text))
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	*cs = *temp
   145  
   146  	return nil
   147  }
   148  
   149  // MarshalText implements the encoding.TextMarshaler interface.
   150  func (cs Constraints) MarshalText() ([]byte, error) {
   151  	return []byte(cs.String()), nil
   152  }
   153  
   154  var constraintOps map[string]cfunc
   155  var constraintRegex *regexp.Regexp
   156  var constraintRangeRegex *regexp.Regexp
   157  
   158  // Used to find individual constraints within a multi-constraint string
   159  var findConstraintRegex *regexp.Regexp
   160  
   161  // Used to validate an segment of ANDs is valid
   162  var validConstraintRegex *regexp.Regexp
   163  
   164  const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
   165  	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
   166  	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
   167  
   168  func init() {
   169  	constraintOps = map[string]cfunc{
   170  		"":   constraintTildeOrEqual,
   171  		"=":  constraintTildeOrEqual,
   172  		"!=": constraintNotEqual,
   173  		">":  constraintGreaterThan,
   174  		"<":  constraintLessThan,
   175  		">=": constraintGreaterThanEqual,
   176  		"=>": constraintGreaterThanEqual,
   177  		"<=": constraintLessThanEqual,
   178  		"=<": constraintLessThanEqual,
   179  		"~":  constraintTilde,
   180  		"~>": constraintTilde,
   181  		"^":  constraintCaret,
   182  	}
   183  
   184  	ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^`
   185  
   186  	constraintRegex = regexp.MustCompile(fmt.Sprintf(
   187  		`^\s*(%s)\s*(%s)\s*$`,
   188  		ops,
   189  		cvRegex))
   190  
   191  	constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
   192  		`\s*(%s)\s+-\s+(%s)\s*`,
   193  		cvRegex, cvRegex))
   194  
   195  	findConstraintRegex = regexp.MustCompile(fmt.Sprintf(
   196  		`(%s)\s*(%s)`,
   197  		ops,
   198  		cvRegex))
   199  
   200  	// The first time a constraint shows up will look slightly different from
   201  	// future times it shows up due to a leading space or comma in a given
   202  	// string.
   203  	validConstraintRegex = regexp.MustCompile(fmt.Sprintf(
   204  		`^(\s*(%s)\s*(%s)\s*)((?:\s+|,\s*)(%s)\s*(%s)\s*)*$`,
   205  		ops,
   206  		cvRegex,
   207  		ops,
   208  		cvRegex))
   209  }
   210  
   211  // An individual constraint
   212  type constraint struct {
   213  	// The version used in the constraint check. For example, if a constraint
   214  	// is '<= 2.0.0' the con a version instance representing 2.0.0.
   215  	con *Version
   216  
   217  	// The original parsed version (e.g., 4.x from != 4.x)
   218  	orig string
   219  
   220  	// The original operator for the constraint
   221  	origfunc string
   222  
   223  	// When an x is used as part of the version (e.g., 1.x)
   224  	minorDirty bool
   225  	dirty      bool
   226  	patchDirty bool
   227  }
   228  
   229  // Check if a version meets the constraint
   230  func (c *constraint) check(v *Version) (bool, error) {
   231  	return constraintOps[c.origfunc](v, c)
   232  }
   233  
   234  // String prints an individual constraint into a string
   235  func (c *constraint) string() string {
   236  	return c.origfunc + c.orig
   237  }
   238  
   239  type cfunc func(v *Version, c *constraint) (bool, error)
   240  
   241  func parseConstraint(c string) (*constraint, error) {
   242  	if len(c) > 0 {
   243  		m := constraintRegex.FindStringSubmatch(c)
   244  		if m == nil {
   245  			return nil, fmt.Errorf("improper constraint: %s", c)
   246  		}
   247  
   248  		cs := &constraint{
   249  			orig:     m[2],
   250  			origfunc: m[1],
   251  		}
   252  
   253  		ver := m[2]
   254  		minorDirty := false
   255  		patchDirty := false
   256  		dirty := false
   257  		if isX(m[3]) || m[3] == "" {
   258  			ver = fmt.Sprintf("0.0.0%s", m[6])
   259  			dirty = true
   260  		} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
   261  			minorDirty = true
   262  			dirty = true
   263  			ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
   264  		} else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" {
   265  			dirty = true
   266  			patchDirty = true
   267  			ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
   268  		}
   269  
   270  		con, err := NewVersion(ver)
   271  		if err != nil {
   272  
   273  			// The constraintRegex should catch any regex parsing errors. So,
   274  			// we should never get here.
   275  			return nil, errors.New("constraint Parser Error")
   276  		}
   277  
   278  		cs.con = con
   279  		cs.minorDirty = minorDirty
   280  		cs.patchDirty = patchDirty
   281  		cs.dirty = dirty
   282  
   283  		return cs, nil
   284  	}
   285  
   286  	// The rest is the special case where an empty string was passed in which
   287  	// is equivalent to * or >=0.0.0
   288  	con, err := StrictNewVersion("0.0.0")
   289  	if err != nil {
   290  
   291  		// The constraintRegex should catch any regex parsing errors. So,
   292  		// we should never get here.
   293  		return nil, errors.New("constraint Parser Error")
   294  	}
   295  
   296  	cs := &constraint{
   297  		con:        con,
   298  		orig:       c,
   299  		origfunc:   "",
   300  		minorDirty: false,
   301  		patchDirty: false,
   302  		dirty:      true,
   303  	}
   304  	return cs, nil
   305  }
   306  
   307  // Constraint functions
   308  func constraintNotEqual(v *Version, c *constraint) (bool, error) {
   309  	if c.dirty {
   310  
   311  		// If there is a pre-release on the version but the constraint isn't looking
   312  		// for them assume that pre-releases are not compatible. See issue 21 for
   313  		// more details.
   314  		if v.Prerelease() != "" && c.con.Prerelease() == "" {
   315  			return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   316  		}
   317  
   318  		if c.con.Major() != v.Major() {
   319  			return true, nil
   320  		}
   321  		if c.con.Minor() != v.Minor() && !c.minorDirty {
   322  			return true, nil
   323  		} else if c.minorDirty {
   324  			return false, fmt.Errorf("%s is equal to %s", v, c.orig)
   325  		} else if c.con.Patch() != v.Patch() && !c.patchDirty {
   326  			return true, nil
   327  		} else if c.patchDirty {
   328  			// Need to handle prereleases if present
   329  			if v.Prerelease() != "" || c.con.Prerelease() != "" {
   330  				eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0
   331  				if eq {
   332  					return true, nil
   333  				}
   334  				return false, fmt.Errorf("%s is equal to %s", v, c.orig)
   335  			}
   336  			return false, fmt.Errorf("%s is equal to %s", v, c.orig)
   337  		}
   338  	}
   339  
   340  	eq := v.Equal(c.con)
   341  	if eq {
   342  		return false, fmt.Errorf("%s is equal to %s", v, c.orig)
   343  	}
   344  
   345  	return true, nil
   346  }
   347  
   348  func constraintGreaterThan(v *Version, c *constraint) (bool, error) {
   349  
   350  	// If there is a pre-release on the version but the constraint isn't looking
   351  	// for them assume that pre-releases are not compatible. See issue 21 for
   352  	// more details.
   353  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   354  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   355  	}
   356  
   357  	var eq bool
   358  
   359  	if !c.dirty {
   360  		eq = v.Compare(c.con) == 1
   361  		if eq {
   362  			return true, nil
   363  		}
   364  		return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
   365  	}
   366  
   367  	if v.Major() > c.con.Major() {
   368  		return true, nil
   369  	} else if v.Major() < c.con.Major() {
   370  		return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
   371  	} else if c.minorDirty {
   372  		// This is a range case such as >11. When the version is something like
   373  		// 11.1.0 is it not > 11. For that we would need 12 or higher
   374  		return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
   375  	} else if c.patchDirty {
   376  		// This is for ranges such as >11.1. A version of 11.1.1 is not greater
   377  		// which one of 11.2.1 is greater
   378  		eq = v.Minor() > c.con.Minor()
   379  		if eq {
   380  			return true, nil
   381  		}
   382  		return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
   383  	}
   384  
   385  	// If we have gotten here we are not comparing pre-preleases and can use the
   386  	// Compare function to accomplish that.
   387  	eq = v.Compare(c.con) == 1
   388  	if eq {
   389  		return true, nil
   390  	}
   391  	return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
   392  }
   393  
   394  func constraintLessThan(v *Version, c *constraint) (bool, error) {
   395  	// If there is a pre-release on the version but the constraint isn't looking
   396  	// for them assume that pre-releases are not compatible. See issue 21 for
   397  	// more details.
   398  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   399  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   400  	}
   401  
   402  	eq := v.Compare(c.con) < 0
   403  	if eq {
   404  		return true, nil
   405  	}
   406  	return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
   407  }
   408  
   409  func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) {
   410  
   411  	// If there is a pre-release on the version but the constraint isn't looking
   412  	// for them assume that pre-releases are not compatible. See issue 21 for
   413  	// more details.
   414  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   415  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   416  	}
   417  
   418  	eq := v.Compare(c.con) >= 0
   419  	if eq {
   420  		return true, nil
   421  	}
   422  	return false, fmt.Errorf("%s is less than %s", v, c.orig)
   423  }
   424  
   425  func constraintLessThanEqual(v *Version, c *constraint) (bool, error) {
   426  	// If there is a pre-release on the version but the constraint isn't looking
   427  	// for them assume that pre-releases are not compatible. See issue 21 for
   428  	// more details.
   429  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   430  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   431  	}
   432  
   433  	var eq bool
   434  
   435  	if !c.dirty {
   436  		eq = v.Compare(c.con) <= 0
   437  		if eq {
   438  			return true, nil
   439  		}
   440  		return false, fmt.Errorf("%s is greater than %s", v, c.orig)
   441  	}
   442  
   443  	if v.Major() > c.con.Major() {
   444  		return false, fmt.Errorf("%s is greater than %s", v, c.orig)
   445  	} else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty {
   446  		return false, fmt.Errorf("%s is greater than %s", v, c.orig)
   447  	}
   448  
   449  	return true, nil
   450  }
   451  
   452  // ~*, ~>* --> >= 0.0.0 (any)
   453  // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
   454  // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
   455  // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
   456  // ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
   457  // ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
   458  func constraintTilde(v *Version, c *constraint) (bool, error) {
   459  	// If there is a pre-release on the version but the constraint isn't looking
   460  	// for them assume that pre-releases are not compatible. See issue 21 for
   461  	// more details.
   462  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   463  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   464  	}
   465  
   466  	if v.LessThan(c.con) {
   467  		return false, fmt.Errorf("%s is less than %s", v, c.orig)
   468  	}
   469  
   470  	// ~0.0.0 is a special case where all constraints are accepted. It's
   471  	// equivalent to >= 0.0.0.
   472  	if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
   473  		!c.minorDirty && !c.patchDirty {
   474  		return true, nil
   475  	}
   476  
   477  	if v.Major() != c.con.Major() {
   478  		return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
   479  	}
   480  
   481  	if v.Minor() != c.con.Minor() && !c.minorDirty {
   482  		return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig)
   483  	}
   484  
   485  	return true, nil
   486  }
   487  
   488  // When there is a .x (dirty) status it automatically opts in to ~. Otherwise
   489  // it's a straight =
   490  func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) {
   491  	// If there is a pre-release on the version but the constraint isn't looking
   492  	// for them assume that pre-releases are not compatible. See issue 21 for
   493  	// more details.
   494  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   495  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   496  	}
   497  
   498  	if c.dirty {
   499  		return constraintTilde(v, c)
   500  	}
   501  
   502  	eq := v.Equal(c.con)
   503  	if eq {
   504  		return true, nil
   505  	}
   506  
   507  	return false, fmt.Errorf("%s is not equal to %s", v, c.orig)
   508  }
   509  
   510  // ^*      -->  (any)
   511  // ^1.2.3  -->  >=1.2.3 <2.0.0
   512  // ^1.2    -->  >=1.2.0 <2.0.0
   513  // ^1      -->  >=1.0.0 <2.0.0
   514  // ^0.2.3  -->  >=0.2.3 <0.3.0
   515  // ^0.2    -->  >=0.2.0 <0.3.0
   516  // ^0.0.3  -->  >=0.0.3 <0.0.4
   517  // ^0.0    -->  >=0.0.0 <0.1.0
   518  // ^0      -->  >=0.0.0 <1.0.0
   519  func constraintCaret(v *Version, c *constraint) (bool, error) {
   520  	// If there is a pre-release on the version but the constraint isn't looking
   521  	// for them assume that pre-releases are not compatible. See issue 21 for
   522  	// more details.
   523  	if v.Prerelease() != "" && c.con.Prerelease() == "" {
   524  		return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
   525  	}
   526  
   527  	// This less than handles prereleases
   528  	if v.LessThan(c.con) {
   529  		return false, fmt.Errorf("%s is less than %s", v, c.orig)
   530  	}
   531  
   532  	var eq bool
   533  
   534  	// ^ when the major > 0 is >=x.y.z < x+1
   535  	if c.con.Major() > 0 || c.minorDirty {
   536  
   537  		// ^ has to be within a major range for > 0. Everything less than was
   538  		// filtered out with the LessThan call above. This filters out those
   539  		// that greater but not within the same major range.
   540  		eq = v.Major() == c.con.Major()
   541  		if eq {
   542  			return true, nil
   543  		}
   544  		return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
   545  	}
   546  
   547  	// ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1
   548  	if c.con.Major() == 0 && v.Major() > 0 {
   549  		return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
   550  	}
   551  	// If the con Minor is > 0 it is not dirty
   552  	if c.con.Minor() > 0 || c.patchDirty {
   553  		eq = v.Minor() == c.con.Minor()
   554  		if eq {
   555  			return true, nil
   556  		}
   557  		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)
   558  	}
   559  	// ^ when the minor is 0 and minor > 0 is =0.0.z
   560  	if c.con.Minor() == 0 && v.Minor() > 0 {
   561  		return false, fmt.Errorf("%s does not have same minor version as %s", v, c.orig)
   562  	}
   563  
   564  	// At this point the major is 0 and the minor is 0 and not dirty. The patch
   565  	// is not dirty so we need to check if they are equal. If they are not equal
   566  	eq = c.con.Patch() == v.Patch()
   567  	if eq {
   568  		return true, nil
   569  	}
   570  	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)
   571  }
   572  
   573  func isX(x string) bool {
   574  	switch x {
   575  	case "x", "*", "X":
   576  		return true
   577  	default:
   578  		return false
   579  	}
   580  }
   581  
   582  func rewriteRange(i string) string {
   583  	m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
   584  	if m == nil {
   585  		return i
   586  	}
   587  	o := i
   588  	for _, v := range m {
   589  		t := fmt.Sprintf(">= %s, <= %s ", v[1], v[11])
   590  		o = strings.Replace(o, v[0], t, 1)
   591  	}
   592  
   593  	return o
   594  }
   595  

View as plain text