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