Skip to content

Commit 97d5ca4

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
Add versions to the 'optional' library to gradually expose new functions.
PiperOrigin-RevId: 782140219
1 parent 97667f5 commit 97d5ca4

6 files changed

Lines changed: 136 additions & 7 deletions

File tree

bundle/src/main/java/dev/cel/bundle/CelEnvironment.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
import dev.cel.compiler.CelCompilerBuilder;
4747
import dev.cel.compiler.CelCompilerLibrary;
4848
import dev.cel.extensions.CelExtensions;
49-
import dev.cel.extensions.CelOptionalLibrary;
5049
import dev.cel.parser.CelStandardMacro;
5150
import dev.cel.runtime.CelRuntimeBuilder;
5251
import dev.cel.runtime.CelRuntimeLibrary;
@@ -684,8 +683,8 @@ enum CanonicalCelExtension {
684683
(options, version) -> CelExtensions.math(options, version),
685684
(options, version) -> CelExtensions.math(options, version)),
686685
OPTIONAL(
687-
(options, version) -> CelOptionalLibrary.INSTANCE,
688-
(options, version) -> CelOptionalLibrary.INSTANCE),
686+
(options, version) -> CelExtensions.optional(version),
687+
(options, version) -> CelExtensions.optional(version)),
689688
STRINGS(
690689
(options, version) -> CelExtensions.strings(),
691690
(options, version) -> CelExtensions.strings()),

extensions/src/main/java/dev/cel/extensions/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ java_library(
2727
":sets_function",
2828
":strings",
2929
"//common:options",
30+
"//extensions:optional_library",
3031
"@maven//:com_google_guava_guava",
3132
],
3233
)

extensions/src/main/java/dev/cel/extensions/CelExtensions.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ public final class CelExtensions {
3838
private static final CelListsExtensions LISTS_EXTENSIONS_ALL = new CelListsExtensions();
3939
private static final CelRegexExtensions REGEX_EXTENSIONS = new CelRegexExtensions();
4040

41+
/**
42+
* Implementation of optional values.
43+
*
44+
* <p>Refer to README.md for available functions.
45+
*/
46+
public static CelOptionalLibrary optional() {
47+
return CelOptionalLibrary.INSTANCE;
48+
}
49+
50+
/**
51+
* Implementation of optional values.
52+
*
53+
* <p>Refer to README.md for available functions for each supported version.
54+
*/
55+
public static CelOptionalLibrary optional(int version) {
56+
return new CelOptionalLibrary(version);
57+
}
58+
4159
/**
4260
* Extended functions for string manipulation.
4361
*

extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ public enum Function {
6666
OPTIONAL_UNWRAP("optional.unwrap"),
6767
OPTIONAL_OF_NON_ZERO_VALUE("optional.ofNonZeroValue"),
6868
OR("or"),
69-
OR_VALUE("orValue");
69+
OR_VALUE("orValue"),
70+
FIRST("first"),
71+
LAST("last");
72+
7073
private final String functionName;
7174

7275
public String getFunction() {
@@ -80,6 +83,16 @@ public String getFunction() {
8083

8184
private static final String UNUSED_ITER_VAR = "#unused";
8285

86+
private final int version;
87+
88+
CelOptionalLibrary() {
89+
this(Integer.MAX_VALUE);
90+
}
91+
92+
CelOptionalLibrary(int version) {
93+
this.version = version;
94+
}
95+
8396
@Override
8497
public void setParserOptions(CelParserBuilder parserBuilder) {
8598
if (!parserBuilder.getOptions().enableOptionalSyntax()) {
@@ -90,8 +103,10 @@ public void setParserOptions(CelParserBuilder parserBuilder) {
90103
}
91104
parserBuilder.addMacros(
92105
CelMacro.newReceiverMacro("optMap", 2, CelOptionalLibrary::expandOptMap));
93-
parserBuilder.addMacros(
94-
CelMacro.newReceiverMacro("optFlatMap", 2, CelOptionalLibrary::expandOptFlatMap));
106+
if (version >= 1) {
107+
parserBuilder.addMacros(
108+
CelMacro.newReceiverMacro("optFlatMap", 2, CelOptionalLibrary::expandOptFlatMap));
109+
}
95110
}
96111

97112
@Override
@@ -172,6 +187,23 @@ public void setCheckerOptions(CelCheckerBuilder checkerBuilder) {
172187
optionalTypeV,
173188
OptionalType.create(mapTypeKv),
174189
paramTypeK)));
190+
if (version >= 2) {
191+
checkerBuilder.addFunctionDeclarations(
192+
CelFunctionDecl.newFunctionDeclaration(
193+
Function.FIRST.functionName,
194+
CelOverloadDecl.newMemberOverload(
195+
"optional_list_first",
196+
"Return the first value in a list if present, otherwise optional.none()",
197+
optionalTypeV,
198+
listTypeV)),
199+
CelFunctionDecl.newFunctionDeclaration(
200+
Function.LAST.functionName,
201+
CelOverloadDecl.newMemberOverload(
202+
"optional_list_last",
203+
"Return the last value in a list if present, otherwise optional.none()",
204+
optionalTypeV,
205+
listTypeV)));
206+
}
175207
}
176208

177209
@Override
@@ -241,6 +273,14 @@ public void setRuntimeOptions(
241273
Optional.class,
242274
Long.class,
243275
CelOptionalLibrary::indexOptionalList));
276+
277+
if (version >= 2) {
278+
runtimeBuilder.addFunctionBindings(
279+
CelFunctionBinding.from(
280+
"optional_list_first", Collection.class, CelOptionalLibrary::listOptionalFirst),
281+
CelFunctionBinding.from(
282+
"optional_list_last", Collection.class, CelOptionalLibrary::listOptionalLast));
283+
}
244284
}
245285

246286
private static ImmutableList<Object> elideOptionalCollection(Collection<Optional<Object>> list) {
@@ -372,5 +412,27 @@ private static Object indexOptionalList(Optional<?> optionalList, long index) {
372412
return Optional.of(list.get(castIndex));
373413
}
374414

375-
private CelOptionalLibrary() {}
415+
private static Object listOptionalFirst(Collection<Object> list) {
416+
if (list.isEmpty()) {
417+
return Optional.empty();
418+
}
419+
if (list instanceof List) {
420+
return Optional.of(((List<Object>) list).get(0));
421+
}
422+
return list.stream().findFirst();
423+
}
424+
425+
private static Object listOptionalLast(Collection<Object> list) {
426+
if (list.isEmpty()) {
427+
return Optional.empty();
428+
}
429+
if (list instanceof List) {
430+
return Optional.of(((List<Object>) list).get(list.size() - 1));
431+
}
432+
Object last = null;
433+
for (Object element : list) {
434+
last = element;
435+
}
436+
return Optional.of(last);
437+
}
376438
}

extensions/src/main/java/dev/cel/extensions/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,36 @@ lists.range(5) -> [0, 1, 2, 3, 4]
722722
lists.range(0) -> []
723723
```
724724

725+
### Last
726+
727+
Introduced in the 'optional' extension version 2
728+
729+
Returns an optional with the last value from the list or `optional.None` if the
730+
list is empty.
731+
732+
<list(T)>.last() -> <Optional(T)>
733+
734+
Examples:
735+
736+
[1, 2, 3].last().value() == 3
737+
[].last().orValue('test') == 'test'
738+
739+
This is syntactic sugar for list[list.size()-1].
740+
741+
### First
742+
743+
Introduced in the 'optional' extension version 2
744+
745+
Returns an optional with the first value from the list or `optional.None` if the
746+
list is empty.
747+
748+
<list(T)>.first() -> <Optional(T)>
749+
750+
Examples:
751+
752+
[1, 2, 3].first().value() == 1
753+
[].first().orValue('test') == 'test'
754+
725755
## Regex
726756

727757
Regex introduces support for regular expressions in CEL.

extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
@RunWith(TestParameterInjector.class)
5959
@SuppressWarnings("unchecked")
60+
@SuppressWarnings("SingleTestParameter")
6061
public class CelOptionalLibraryTest {
6162

6263
@SuppressWarnings("ImmutableEnumChecker") // Test only
@@ -1496,4 +1497,22 @@ public void optionalType_typeComparison() throws Exception {
14961497

14971498
assertThat(cel.createProgram(ast).eval()).isEqualTo(true);
14981499
}
1500+
1501+
@Test
1502+
@TestParameters("{expression: '[].first().hasValue() == false'}")
1503+
@TestParameters("{expression: '[\"a\",\"b\",\"c\"].first().value() == \"a\"'}")
1504+
public void listFirst_success(String expression) throws Exception {
1505+
Cel cel = newCelBuilder().build();
1506+
boolean result = (boolean) cel.createProgram(cel.compile(expression).getAst()).eval();
1507+
assertThat(result).isTrue();
1508+
}
1509+
1510+
@Test
1511+
@TestParameters("{expression: '[].last().hasValue() == false'}")
1512+
@TestParameters("{expression: '[1, 2, 3].last().value() == 3'}")
1513+
public void listLast_success(String expression) throws Exception {
1514+
Cel cel = newCelBuilder().build();
1515+
boolean result = (boolean) cel.createProgram(cel.compile(expression).getAst()).eval();
1516+
assertThat(result).isTrue();
1517+
}
14991518
}

0 commit comments

Comments
 (0)