@@ -16,6 +16,8 @@ package cel
1616
1717import (
1818 "errors"
19+ "fmt"
20+ "math"
1921 "sync"
2022
2123 "github.com/google/cel-go/checker"
@@ -24,12 +26,15 @@ import (
2426 celast "github.com/google/cel-go/common/ast"
2527 "github.com/google/cel-go/common/containers"
2628 "github.com/google/cel-go/common/decls"
29+ "github.com/google/cel-go/common/env"
30+ "github.com/google/cel-go/common/stdlib"
2731 "github.com/google/cel-go/common/types"
2832 "github.com/google/cel-go/common/types/ref"
2933 "github.com/google/cel-go/interpreter"
3034 "github.com/google/cel-go/parser"
3135
3236 exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
37+ "google.golang.org/protobuf/reflect/protoreflect"
3338)
3439
3540// Source interface representing a user-provided expression.
@@ -127,12 +132,13 @@ type Env struct {
127132 Container * containers.Container
128133 variables []* decls.VariableDecl
129134 functions map [string ]* decls.FunctionDecl
130- macros []parser.Macro
135+ macros []Macro
136+ contextProto protoreflect.MessageDescriptor
131137 adapter types.Adapter
132138 provider types.Provider
133139 features map [int ]bool
134140 appliedFeatures map [int ]bool
135- libraries map [string ]bool
141+ libraries map [string ]SingletonLibrary
136142 validators []ASTValidator
137143 costOptions []checker.CostOption
138144
@@ -151,6 +157,115 @@ type Env struct {
151157 progOpts []ProgramOption
152158}
153159
160+ // ToConfig produces a YAML-serializable env.Config object from the given environment.
161+ //
162+ // The serialized configuration value is intended to represent a baseline set of config
163+ // options which could be used as input to an EnvOption to configure the majority of the
164+ // environment from a file.
165+ //
166+ // Note: validators, features, flags, and safe-guard settings are not yet supported by
167+ // the serialize method. Since optimizers are a separate construct from the environment
168+ // and the standard expression components (parse, check, evalute), they are also not
169+ // supported by the serialize method.
170+ func (e * Env ) ToConfig (name string ) (* env.Config , error ) {
171+ conf := env .NewConfig (name )
172+ // Container settings
173+ if e .Container != containers .DefaultContainer {
174+ conf .SetContainer (e .Container .Name ())
175+ }
176+ for _ , typeName := range e .Container .AliasSet () {
177+ conf .AddImports (env .NewImport (typeName ))
178+ }
179+
180+ libOverloads := map [string ][]string {}
181+ for libName , lib := range e .libraries {
182+ // Track the options which have been configured by a library and
183+ // then diff the library version against the configured function
184+ // to detect incremental overloads or rewrites.
185+ libEnv , _ := NewCustomEnv ()
186+ libEnv , _ = Lib (lib )(libEnv )
187+ for fnName , fnDecl := range libEnv .Functions () {
188+ if len (fnDecl .OverloadDecls ()) == 0 {
189+ continue
190+ }
191+ overloads , exist := libOverloads [fnName ]
192+ if ! exist {
193+ overloads = make ([]string , 0 , len (fnDecl .OverloadDecls ()))
194+ }
195+ for _ , o := range fnDecl .OverloadDecls () {
196+ overloads = append (overloads , o .ID ())
197+ }
198+ libOverloads [fnName ] = overloads
199+ }
200+ subsetLib , canSubset := lib .(LibrarySubsetter )
201+ alias := ""
202+ if aliasLib , canAlias := lib .(LibraryAliaser ); canAlias {
203+ alias = aliasLib .LibraryAlias ()
204+ libName = alias
205+ }
206+ if libName == "stdlib" && canSubset {
207+ conf .SetStdLib (subsetLib .LibrarySubset ())
208+ continue
209+ }
210+ version := uint32 (math .MaxUint32 )
211+ if versionLib , isVersioned := lib .(LibraryVersioner ); isVersioned {
212+ version = versionLib .LibraryVersion ()
213+ }
214+ conf .AddExtensions (env .NewExtension (libName , version ))
215+ }
216+
217+ // If this is a custom environment without the standard env, mark the stdlib as disabled.
218+ if conf .StdLib == nil && ! e .HasLibrary ("cel.lib.std" ) {
219+ conf .SetStdLib (env .NewLibrarySubset ().SetDisabled (true ))
220+ }
221+
222+ // Serialize the variables
223+ vars := make ([]* decls.VariableDecl , 0 , len (e .Variables ()))
224+ stdTypeVars := map [string ]* decls.VariableDecl {}
225+ for _ , v := range stdlib .Types () {
226+ stdTypeVars [v .Name ()] = v
227+ }
228+ for _ , v := range e .Variables () {
229+ if _ , isStdType := stdTypeVars [v .Name ()]; isStdType {
230+ continue
231+ }
232+ vars = append (vars , v )
233+ }
234+ if e .contextProto != nil {
235+ conf .SetContextVariable (env .NewContextVariable (string (e .contextProto .FullName ())))
236+ skipVariables := map [string ]bool {}
237+ fields := e .contextProto .Fields ()
238+ for i := 0 ; i < fields .Len (); i ++ {
239+ field := fields .Get (i )
240+ variable , err := fieldToVariable (field )
241+ if err != nil {
242+ return nil , fmt .Errorf ("could not serialize context field variable %q, reason: %w" , field .FullName (), err )
243+ }
244+ skipVariables [variable .Name ()] = true
245+ }
246+ for _ , v := range vars {
247+ if _ , found := skipVariables [v .Name ()]; ! found {
248+ conf .AddVariableDecls (v )
249+ }
250+ }
251+ } else {
252+ conf .AddVariableDecls (vars ... )
253+ }
254+
255+ // Serialize functions which are distinct from the ones configured by libraries.
256+ for fnName , fnDecl := range e .Functions () {
257+ if excludedOverloads , found := libOverloads [fnName ]; found {
258+ if newDecl := fnDecl .Subset (decls .ExcludeOverloads (excludedOverloads ... )); newDecl != nil {
259+ conf .AddFunctionDecls (newDecl )
260+ }
261+ } else {
262+ conf .AddFunctionDecls (fnDecl )
263+ }
264+ }
265+
266+ return conf , nil
267+ }
268+
154269// NewEnv creates a program environment configured with the standard library of CEL functions and
155270// macros. The Env value returned can parse and check any CEL program which builds upon the core
156271// features documented in the CEL specification.
@@ -194,7 +309,7 @@ func NewCustomEnv(opts ...EnvOption) (*Env, error) {
194309 provider : registry ,
195310 features : map [int ]bool {},
196311 appliedFeatures : map [int ]bool {},
197- libraries : map [string ]bool {},
312+ libraries : map [string ]SingletonLibrary {},
198313 validators : []ASTValidator {},
199314 progOpts : []ProgramOption {},
200315 costOptions : []checker.CostOption {},
@@ -362,7 +477,7 @@ func (e *Env) Extend(opts ...EnvOption) (*Env, error) {
362477 for k , v := range e .functions {
363478 funcsCopy [k ] = v
364479 }
365- libsCopy := make (map [string ]bool , len (e .libraries ))
480+ libsCopy := make (map [string ]SingletonLibrary , len (e .libraries ))
366481 for k , v := range e .libraries {
367482 libsCopy [k ] = v
368483 }
@@ -376,6 +491,7 @@ func (e *Env) Extend(opts ...EnvOption) (*Env, error) {
376491 variables : varsCopy ,
377492 functions : funcsCopy ,
378493 macros : macsCopy ,
494+ contextProto : e .contextProto ,
379495 progOpts : progOptsCopy ,
380496 adapter : adapter ,
381497 features : featuresCopy ,
@@ -399,8 +515,8 @@ func (e *Env) HasFeature(flag int) bool {
399515
400516// HasLibrary returns whether a specific SingletonLibrary has been configured in the environment.
401517func (e * Env ) HasLibrary (libName string ) bool {
402- configured , exists := e .libraries [libName ]
403- return exists && configured
518+ _ , exists := e .libraries [libName ]
519+ return exists
404520}
405521
406522// Libraries returns a list of SingletonLibrary that have been configured in the environment.
@@ -423,6 +539,11 @@ func (e *Env) Functions() map[string]*decls.FunctionDecl {
423539 return e .functions
424540}
425541
542+ // Variables returns the set of variables associated with the environment.
543+ func (e * Env ) Variables () []* decls.VariableDecl {
544+ return e .variables
545+ }
546+
426547// HasValidator returns whether a specific ASTValidator has been configured in the environment.
427548func (e * Env ) HasValidator (name string ) bool {
428549 for _ , v := range e .validators {
0 commit comments