Skip to content

Commit fbbbecf

Browse files
Added support for range version support in semver (#18557)
* Added support for range version support in semver Signed-off-by: Shruti Garg <[email protected]> * Added test coverage for missing lines Signed-off-by: Shruti Garg <[email protected]> * Fixed for non reachable code line and tests Signed-off-by: Shruti Garg <[email protected]> --------- Signed-off-by: Shruti Garg <[email protected]>
1 parent a04c5ba commit fbbbecf

File tree

4 files changed

+446
-4
lines changed

4 files changed

+446
-4
lines changed

libs/core/src/main/java/org/opensearch/semver/SemverRange.java

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
import org.opensearch.semver.expr.Caret;
1717
import org.opensearch.semver.expr.Equal;
1818
import org.opensearch.semver.expr.Expression;
19+
import org.opensearch.semver.expr.Range;
1920
import org.opensearch.semver.expr.Tilde;
2021

2122
import java.io.IOException;
23+
import java.util.Locale;
2224
import java.util.Objects;
2325
import java.util.Optional;
26+
import java.util.regex.Matcher;
27+
import java.util.regex.Pattern;
2428

2529
import static java.util.Arrays.stream;
2630

@@ -31,19 +35,24 @@
3135
* <li>'=' Requires exact match with the range version. For example, =1.2.3 range would match only 1.2.3</li>
3236
* <li>'~' Allows for patch version variability starting from the range version. For example, ~1.2.3 range would match versions greater than or equal to 1.2.3 but less than 1.3.0</li>
3337
* <li>'^' Allows for patch and minor version variability starting from the range version. For example, ^1.2.3 range would match versions greater than or equal to 1.2.3 but less than 2.0.0</li>
38+
* <li>Explicit ranges: [2.0.0,3.0.0], (2.0.0,3.0.0), [2.0.0,3.0.0), (2.0.0,3.0.0]</li>
3439
* </ul>
3540
*
3641
* @opensearch.api
3742
*/
3843
@PublicApi(since = "2.13.0")
3944
public class SemverRange implements ToXContentFragment {
4045

46+
private static final Pattern RANGE_PATTERN = Pattern.compile("([\\[\\(])([\\d.]+)\\s*,\\s*([\\d.]+)([\\]\\)])");
47+
4148
private final Version rangeVersion;
4249
private final RangeOperator rangeOperator;
50+
private final Expression expression;
4351

4452
public SemverRange(final Version rangeVersion, final RangeOperator rangeOperator) {
4553
this.rangeVersion = rangeVersion;
4654
this.rangeOperator = rangeOperator;
55+
this.expression = rangeOperator.expression;
4756
}
4857

4958
/**
@@ -52,6 +61,23 @@ public SemverRange(final Version rangeVersion, final RangeOperator rangeOperator
5261
* @return a {@code SemverRange}
5362
*/
5463
public static SemverRange fromString(final String range) {
64+
// Check if it's a range expression
65+
Matcher matcher = RANGE_PATTERN.matcher(range);
66+
if (matcher.matches()) {
67+
char leftBracket = matcher.group(1).charAt(0);
68+
String lowerVersionStr = matcher.group(2);
69+
String upperVersionStr = matcher.group(3);
70+
char rightBracket = matcher.group(4).charAt(0);
71+
72+
Version lowerVersion = Version.fromString(matcher.group(2));
73+
Version upperVersion = Version.fromString(matcher.group(3));
74+
boolean includeLower = leftBracket == '[';
75+
boolean includeUpper = rightBracket == ']';
76+
77+
Range rangeExpression = new Range(lowerVersion, upperVersion, includeLower, includeUpper);
78+
return new SemverRange(lowerVersion, RangeOperator.RANGE, rangeExpression);
79+
}
80+
5581
RangeOperator rangeOperator = RangeOperator.fromRange(range);
5682
String version = range.replaceFirst(rangeOperator.asEscapedString(), "");
5783
if (!Version.stringHasLength(version)) {
@@ -60,6 +86,12 @@ public static SemverRange fromString(final String range) {
6086
return new SemverRange(Version.fromString(version), rangeOperator);
6187
}
6288

89+
public SemverRange(Version rangeVersion, RangeOperator operator, Expression customExpression) {
90+
this.rangeVersion = rangeVersion;
91+
this.rangeOperator = operator;
92+
this.expression = customExpression;
93+
}
94+
6395
/**
6496
* Return the range operator for this range.
6597
* @return range operator
@@ -94,7 +126,7 @@ public boolean isSatisfiedBy(final String versionToEvaluate) {
94126
* @see #isSatisfiedBy(String)
95127
*/
96128
public boolean isSatisfiedBy(final Version versionToEvaluate) {
97-
return this.rangeOperator.expression.evaluate(this.rangeVersion, versionToEvaluate);
129+
return this.expression.evaluate(this.rangeVersion, versionToEvaluate);
98130
}
99131

100132
@Override
@@ -106,16 +138,29 @@ public boolean equals(@Nullable final Object o) {
106138
return false;
107139
}
108140
SemverRange range = (SemverRange) o;
109-
return Objects.equals(rangeVersion, range.rangeVersion) && rangeOperator == range.rangeOperator;
141+
return Objects.equals(rangeVersion, range.rangeVersion)
142+
&& rangeOperator == range.rangeOperator
143+
&& Objects.equals(expression, range.expression);
110144
}
111145

112146
@Override
113147
public int hashCode() {
114-
return Objects.hash(rangeVersion, rangeOperator);
148+
return Objects.hash(rangeVersion, rangeOperator, expression);
115149
}
116150

117151
@Override
118152
public String toString() {
153+
if (rangeOperator == RangeOperator.RANGE && expression instanceof Range) {
154+
Range range = (Range) expression;
155+
return String.format(
156+
Locale.ROOT,
157+
"%s%s,%s%s",
158+
range.isIncludeLower() ? "[" : "(",
159+
range.getLowerBound(),
160+
range.getUpperBound(),
161+
range.isIncludeUpper() ? "]" : ")"
162+
);
163+
}
119164
return rangeOperator.asString() + rangeVersion;
120165
}
121166

@@ -128,10 +173,10 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa
128173
* A range operator.
129174
*/
130175
public enum RangeOperator {
131-
132176
EQ("=", new Equal()),
133177
TILDE("~", new Tilde()),
134178
CARET("^", new Caret()),
179+
RANGE("range", new Range()),
135180
DEFAULT("", new Equal());
136181

137182
private final String operator;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.semver.expr;
10+
11+
import org.opensearch.Version;
12+
13+
import java.util.Objects;
14+
15+
/**
16+
* Expression to evaluate version compatibility within a specified range with configurable bounds.
17+
*/
18+
public class Range implements Expression {
19+
private final Version lowerBound;
20+
private final Version upperBound;
21+
private final boolean includeLower;
22+
private final boolean includeUpper;
23+
24+
public Range() {
25+
this.lowerBound = Version.fromString("0.0.0"); // Minimum version
26+
this.upperBound = Version.fromString("999.999.999"); // Maximum version
27+
this.includeLower = true; // Default to inclusive bounds
28+
this.includeUpper = true;
29+
}
30+
31+
public Range(Version lowerBound, Version upperBound, boolean includeLower, boolean includeUpper) {
32+
if (lowerBound == null) {
33+
throw new IllegalArgumentException("Lower bound cannot be null");
34+
}
35+
if (upperBound == null) {
36+
throw new IllegalArgumentException("Upper bound cannot be null");
37+
}
38+
if (lowerBound.after(upperBound)) {
39+
throw new IllegalArgumentException("Lower bound must be less than or equal to upper bound");
40+
}
41+
this.lowerBound = lowerBound;
42+
this.upperBound = upperBound;
43+
this.includeLower = includeLower;
44+
this.includeUpper = includeUpper;
45+
}
46+
47+
public void updateRange(Range other) {
48+
if (other == null) {
49+
throw new IllegalArgumentException("Range cannot be null");
50+
}
51+
}
52+
53+
@Override
54+
public boolean evaluate(final Version rangeVersion, final Version versionToEvaluate) {
55+
56+
boolean satisfiesLower = includeLower ? versionToEvaluate.onOrAfter(lowerBound) : versionToEvaluate.after(lowerBound);
57+
58+
boolean satisfiesUpper = includeUpper ? versionToEvaluate.onOrBefore(upperBound) : versionToEvaluate.before(upperBound);
59+
60+
return satisfiesLower && satisfiesUpper;
61+
}
62+
63+
@Override
64+
public boolean equals(Object o) {
65+
if (this == o) return true;
66+
if (o == null || getClass() != o.getClass()) return false;
67+
Range range = (Range) o;
68+
return includeLower == range.includeLower
69+
&& includeUpper == range.includeUpper
70+
&& Objects.equals(lowerBound, range.lowerBound)
71+
&& Objects.equals(upperBound, range.upperBound);
72+
}
73+
74+
@Override
75+
public int hashCode() {
76+
return Objects.hash(lowerBound, upperBound, includeLower, includeUpper);
77+
}
78+
79+
public boolean isIncludeLower() {
80+
return includeLower;
81+
}
82+
83+
public boolean isIncludeUpper() {
84+
return includeUpper;
85+
}
86+
87+
public Version getLowerBound() {
88+
return lowerBound;
89+
}
90+
91+
public Version getUpperBound() {
92+
return upperBound;
93+
}
94+
95+
}

0 commit comments

Comments
 (0)