Skip to content

Commit 220312c

Browse files
TristonianJonescopybara-github
authored andcommitted
Support for CelEnvironment to YAML
PiperOrigin-RevId: 751153106
1 parent c98b522 commit 220312c

File tree

8 files changed

+346
-1
lines changed

8 files changed

+346
-1
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ java_library(
8989
name = "environment_yaml_parser",
9090
srcs = [
9191
"CelEnvironmentYamlParser.java",
92+
"CelEnvironmentYamlSerializer.java",
9293
],
9394
tags = [
9495
],

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public static Builder newBuilder() {
154154
.setVariables(ImmutableSet.of())
155155
.setFunctions(ImmutableSet.of());
156156
}
157-
157+
158158
/** Extends the provided {@link CelCompiler} environment with this configuration. */
159159
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
160160
throws CelEnvironmentException {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.bundle;
16+
17+
import com.google.common.collect.ImmutableMap;
18+
import org.yaml.snakeyaml.DumperOptions;
19+
import org.yaml.snakeyaml.Yaml;
20+
import org.yaml.snakeyaml.nodes.Node;
21+
import org.yaml.snakeyaml.representer.Represent;
22+
import org.yaml.snakeyaml.representer.Representer;
23+
24+
/** Serializes a CelEnvironment into a YAML file. */
25+
public final class CelEnvironmentYamlSerializer extends Representer {
26+
27+
private static DumperOptions initDumperOptions() {
28+
DumperOptions options = new DumperOptions();
29+
options.setIndent(2);
30+
options.setPrettyFlow(true);
31+
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
32+
return options;
33+
}
34+
35+
private static final DumperOptions YAML_OPTIONS = initDumperOptions();
36+
37+
private static final CelEnvironmentYamlSerializer INSTANCE = new CelEnvironmentYamlSerializer();
38+
39+
private CelEnvironmentYamlSerializer() {
40+
super(YAML_OPTIONS);
41+
this.multiRepresenters.put(CelEnvironment.class, new RepresentCelEnvironment());
42+
this.multiRepresenters.put(CelEnvironment.VariableDecl.class, new RepresentVariableDecl());
43+
this.multiRepresenters.put(CelEnvironment.FunctionDecl.class, new RepresentFunctionDecl());
44+
this.multiRepresenters.put(CelEnvironment.OverloadDecl.class, new RepresentOverloadDecl());
45+
this.multiRepresenters.put(CelEnvironment.TypeDecl.class, new RepresentTypeDecl());
46+
this.multiRepresenters.put(
47+
CelEnvironment.ExtensionConfig.class, new RepresentExtensionConfig());
48+
}
49+
50+
public static String toYaml(CelEnvironment environment) {
51+
// Yaml is not thread-safe, so we create a new instance for each serialization.
52+
Yaml yaml = new Yaml(INSTANCE, YAML_OPTIONS);
53+
return yaml.dump(environment);
54+
}
55+
56+
private final class RepresentCelEnvironment implements Represent {
57+
@Override
58+
public Node representData(Object data) {
59+
CelEnvironment environment = (CelEnvironment) data;
60+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
61+
configMap.put("name", environment.name());
62+
if (!environment.description().isEmpty()) {
63+
configMap.put("description", environment.description());
64+
}
65+
if (!environment.container().isEmpty()) {
66+
configMap.put("container", environment.container());
67+
}
68+
if (!environment.extensions().isEmpty()) {
69+
configMap.put("extensions", environment.extensions().asList());
70+
}
71+
if (!environment.variables().isEmpty()) {
72+
configMap.put("variables", environment.variables().asList());
73+
}
74+
if (!environment.functions().isEmpty()) {
75+
configMap.put("functions", environment.functions().asList());
76+
}
77+
return represent(configMap.buildOrThrow());
78+
}
79+
}
80+
81+
private final class RepresentExtensionConfig implements Represent {
82+
@Override
83+
public Node representData(Object data) {
84+
CelEnvironment.ExtensionConfig extension = (CelEnvironment.ExtensionConfig) data;
85+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
86+
configMap.put("name", extension.name());
87+
if (extension.version() > 0 && extension.version() != Integer.MAX_VALUE) {
88+
configMap.put("version", extension.version());
89+
}
90+
return represent(configMap.buildOrThrow());
91+
}
92+
}
93+
94+
private final class RepresentVariableDecl implements Represent {
95+
@Override
96+
public Node representData(Object data) {
97+
CelEnvironment.VariableDecl variable = (CelEnvironment.VariableDecl) data;
98+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
99+
configMap.put("name", variable.name()).put("type_name", variable.type().name());
100+
if (!variable.type().params().isEmpty()) {
101+
configMap.put("params", variable.type().params());
102+
}
103+
return represent(configMap.buildOrThrow());
104+
}
105+
}
106+
107+
private final class RepresentFunctionDecl implements Represent {
108+
@Override
109+
public Node representData(Object data) {
110+
CelEnvironment.FunctionDecl function = (CelEnvironment.FunctionDecl) data;
111+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
112+
configMap.put("name", function.name()).put("overloads", function.overloads().asList());
113+
return represent(configMap.buildOrThrow());
114+
}
115+
}
116+
117+
private final class RepresentOverloadDecl implements Represent {
118+
@Override
119+
public Node representData(Object data) {
120+
CelEnvironment.OverloadDecl overload = (CelEnvironment.OverloadDecl) data;
121+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
122+
configMap.put("id", overload.id());
123+
if (overload.target().isPresent()) {
124+
configMap.put("target", overload.target().get());
125+
}
126+
configMap.put("args", overload.arguments()).put("return", overload.returnType());
127+
return represent(configMap.buildOrThrow());
128+
}
129+
}
130+
131+
private final class RepresentTypeDecl implements Represent {
132+
@Override
133+
public Node representData(Object data) {
134+
CelEnvironment.TypeDecl type = (CelEnvironment.TypeDecl) data;
135+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
136+
configMap.put("type_name", type.name());
137+
if (!type.params().isEmpty()) {
138+
configMap.put("params", type.params());
139+
}
140+
if (type.isTypeParam()) {
141+
configMap.put("is_type_param", type.isTypeParam());
142+
}
143+
return represent(configMap.buildOrThrow());
144+
}
145+
}
146+
}

bundle/src/test/java/dev/cel/bundle/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ java_library(
1010
testonly = True,
1111
srcs = glob(["*Test.java"]),
1212
resources = [
13+
"//testing/environment:dump_env",
1314
"//testing/environment:extended_env",
1415
],
1516
deps = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.bundle;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static java.nio.charset.StandardCharsets.UTF_8;
19+
20+
import com.google.common.base.Ascii;
21+
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableSet;
23+
import com.google.common.io.Resources;
24+
import dev.cel.bundle.CelEnvironment.ExtensionConfig;
25+
import dev.cel.bundle.CelEnvironment.FunctionDecl;
26+
import dev.cel.bundle.CelEnvironment.OverloadDecl;
27+
import dev.cel.bundle.CelEnvironment.TypeDecl;
28+
import dev.cel.bundle.CelEnvironment.VariableDecl;
29+
import java.io.IOException;
30+
import java.net.URL;
31+
import org.junit.Test;
32+
import org.junit.runner.RunWith;
33+
import org.junit.runners.JUnit4;
34+
35+
@RunWith(JUnit4.class)
36+
public final class CelEnvironmentYamlSerializerTest {
37+
38+
@Test
39+
public void toYaml_success() throws Exception {
40+
CelEnvironment environment =
41+
CelEnvironment.newBuilder()
42+
.setName("dump_env")
43+
.setDescription("dump_env description")
44+
.setContainer("test.container")
45+
.addExtensions(
46+
ImmutableSet.of(
47+
ExtensionConfig.of("bindings"),
48+
ExtensionConfig.of("encoders"),
49+
ExtensionConfig.of("lists"),
50+
ExtensionConfig.of("math"),
51+
ExtensionConfig.of("optional"),
52+
ExtensionConfig.of("protos"),
53+
ExtensionConfig.of("sets"),
54+
ExtensionConfig.of("strings", 1)))
55+
.setVariables(
56+
ImmutableSet.of(
57+
VariableDecl.create(
58+
"request", TypeDecl.create("google.rpc.context.AttributeContext.Request")),
59+
VariableDecl.create(
60+
"map_var",
61+
TypeDecl.newBuilder()
62+
.setName("map")
63+
.addParams(TypeDecl.create("string"))
64+
.addParams(TypeDecl.create("string"))
65+
.build())))
66+
.setFunctions(
67+
ImmutableSet.of(
68+
FunctionDecl.create(
69+
"getOrDefault",
70+
ImmutableSet.of(
71+
OverloadDecl.newBuilder()
72+
.setId("getOrDefault_key_value")
73+
.setTarget(
74+
TypeDecl.newBuilder()
75+
.setName("map")
76+
.addParams(
77+
TypeDecl.newBuilder()
78+
.setName("K")
79+
.setIsTypeParam(true)
80+
.build())
81+
.addParams(
82+
TypeDecl.newBuilder()
83+
.setName("V")
84+
.setIsTypeParam(true)
85+
.build())
86+
.build())
87+
.setArguments(
88+
ImmutableList.of(
89+
TypeDecl.newBuilder()
90+
.setName("K")
91+
.setIsTypeParam(true)
92+
.build(),
93+
TypeDecl.newBuilder()
94+
.setName("V")
95+
.setIsTypeParam(true)
96+
.build()))
97+
.setReturnType(
98+
TypeDecl.newBuilder().setName("V").setIsTypeParam(true).build())
99+
.build())),
100+
FunctionDecl.create(
101+
"coalesce",
102+
ImmutableSet.of(
103+
OverloadDecl.newBuilder()
104+
.setId("coalesce_null_int")
105+
.setTarget(TypeDecl.create("google.protobuf.Int64Value"))
106+
.setArguments(ImmutableList.of(TypeDecl.create("int")))
107+
.setReturnType(TypeDecl.create("int"))
108+
.build()))))
109+
.build();
110+
111+
String yamlOutput = CelEnvironmentYamlSerializer.toYaml(environment);
112+
try {
113+
String yamlFileContent = readFile("environment/dump_env.yaml");
114+
assertThat(yamlFileContent).endsWith(yamlOutput);
115+
} catch (IOException e) {
116+
throw new RuntimeException(e);
117+
}
118+
}
119+
120+
private static String readFile(String path) throws IOException {
121+
URL url = Resources.getResource(Ascii.toLowerCase(path));
122+
return Resources.toString(url, UTF_8);
123+
}
124+
}

testing/environment/BUILD.bazel

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ package(
44
default_visibility = ["//:internal"],
55
)
66

7+
alias(
8+
name = "dump_env",
9+
actual = "//testing/src/test/resources/environment:dump_env",
10+
)
11+
712
alias(
813
name = "extended_env",
914
actual = "//testing/src/test/resources/environment:extended_env",

testing/src/test/resources/environment/BUILD.bazel

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ package(
88
],
99
)
1010

11+
filegroup(
12+
name = "dump_env",
13+
srcs = ["dump_env.yaml"],
14+
)
15+
1116
filegroup(
1217
name = "extended_env",
1318
srcs = ["extended_env.yaml"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: dump_env
16+
description: dump_env description
17+
container: test.container
18+
extensions:
19+
- name: bindings
20+
- name: encoders
21+
- name: lists
22+
- name: math
23+
- name: optional
24+
- name: protos
25+
- name: sets
26+
- name: strings
27+
version: 1
28+
variables:
29+
- name: request
30+
type_name: google.rpc.context.AttributeContext.Request
31+
- name: map_var
32+
type_name: map
33+
params:
34+
- type_name: string
35+
- type_name: string
36+
functions:
37+
- name: getOrDefault
38+
overloads:
39+
- id: getOrDefault_key_value
40+
target:
41+
type_name: map
42+
params:
43+
- type_name: K
44+
is_type_param: true
45+
- type_name: V
46+
is_type_param: true
47+
args:
48+
- type_name: K
49+
is_type_param: true
50+
- type_name: V
51+
is_type_param: true
52+
return:
53+
type_name: V
54+
is_type_param: true
55+
- name: coalesce
56+
overloads:
57+
- id: coalesce_null_int
58+
target:
59+
type_name: google.protobuf.Int64Value
60+
args:
61+
- type_name: int
62+
return:
63+
type_name: int

0 commit comments

Comments
 (0)