1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package containers defines types and functions for resolving qualified names within a namespace
// or type provided to CEL.
package containers
import (
"fmt"
"strings"
"github.com/google/cel-go/common/ast"
)
var (
// DefaultContainer has an empty container name.
DefaultContainer *Container = nil
// Empty map to search for aliases when needed.
noAliases = make(map[string]string)
)
// NewContainer creates a new Container with the fully-qualified name.
func NewContainer(opts ...ContainerOption) (*Container, error) {
var c *Container
var err error
for _, opt := range opts {
c, err = opt(c)
if err != nil {
return nil, err
}
}
return c, nil
}
// Container holds a reference to an optional qualified container name and set of aliases.
//
// The program container can be used to simplify variable, function, and type specification within
// CEL programs and behaves more or less like a C++ namespace. See ResolveCandidateNames for more
// details.
type Container struct {
name string
aliases map[string]string
}
// Extend creates a new Container with the existing settings and applies a series of
// ContainerOptions to further configure the new container.
func (c *Container) Extend(opts ...ContainerOption) (*Container, error) {
if c == nil {
return NewContainer(opts...)
}
// Copy the name and aliases of the existing container.
ext := &Container{name: c.Name()}
if len(c.aliasSet()) > 0 {
aliasSet := make(map[string]string, len(c.aliasSet()))
for k, v := range c.aliasSet() {
aliasSet[k] = v
}
ext.aliases = aliasSet
}
// Apply the new options to the container.
var err error
for _, opt := range opts {
ext, err = opt(ext)
if err != nil {
return nil, err
}
}
return ext, nil
}
// Name returns the fully-qualified name of the container.
//
// The name may conceptually be a namespace, package, or type.
func (c *Container) Name() string {
if c == nil {
return ""
}
return c.name
}
// ResolveCandidateNames returns the candidates name of namespaced identifiers in C++ resolution
// order.
//
// Names which shadow other names are returned first. If a name includes a leading dot ('.'),
// the name is treated as an absolute identifier which cannot be shadowed.
//
// Given a container name a.b.c.M.N and a type name R.s, this will deliver in order:
//
// a.b.c.M.N.R.s
// a.b.c.M.R.s
// a.b.c.R.s
// a.b.R.s
// a.R.s
// R.s
//
// If aliases or abbreviations are configured for the container, then alias names will take
// precedence over containerized names.
func (c *Container) ResolveCandidateNames(name string) []string {
if strings.HasPrefix(name, ".") {
qn := name[1:]
alias, isAlias := c.findAlias(qn)
if isAlias {
return []string{alias}
}
return []string{qn}
}
alias, isAlias := c.findAlias(name)
if isAlias {
return []string{alias}
}
if c.Name() == "" {
return []string{name}
}
nextCont := c.Name()
candidates := []string{nextCont + "." + name}
for i := strings.LastIndex(nextCont, "."); i >= 0; i = strings.LastIndex(nextCont, ".") {
nextCont = nextCont[:i]
candidates = append(candidates, nextCont+"."+name)
}
return append(candidates, name)
}
// aliasSet returns the alias to fully-qualified name mapping stored in the container.
func (c *Container) aliasSet() map[string]string {
if c == nil || c.aliases == nil {
return noAliases
}
return c.aliases
}
// findAlias takes a name as input and returns an alias expansion if one exists.
//
// If the name is qualified, the first component of the qualified name is checked against known
// aliases. Any alias that is found in a qualified name is expanded in the result:
//
// alias: R -> my.alias.R
// name: R.S.T
// output: my.alias.R.S.T
//
// Note, the name must not have a leading dot.
func (c *Container) findAlias(name string) (string, bool) {
// If an alias exists for the name, ensure it is searched last.
simple := name
qualifier := ""
dot := strings.Index(name, ".")
if dot >= 0 {
simple = name[0:dot]
qualifier = name[dot:]
}
alias, found := c.aliasSet()[simple]
if !found {
return "", false
}
return alias + qualifier, true
}
// ContainerOption specifies a functional configuration option for a Container.
//
// Note, ContainerOption implementations must be able to handle nil container inputs.
type ContainerOption func(*Container) (*Container, error)
// Abbrevs configures a set of simple names as abbreviations for fully-qualified names.
//
// An abbreviation (abbrev for short) is a simple name that expands to a fully-qualified name.
// Abbreviations can be useful when working with variables, functions, and especially types from
// multiple namespaces:
//
// // CEL object construction
// qual.pkg.version.ObjTypeName{
// field: alt.container.ver.FieldTypeName{value: ...}
// }
//
// Only one the qualified names above may be used as the CEL container, so at least one of these
// references must be a long qualified name within an otherwise short CEL program. Using the
// following abbreviations, the program becomes much simpler:
//
// // CEL Go option
// Abbrevs("qual.pkg.version.ObjTypeName", "alt.container.ver.FieldTypeName")
// // Simplified Object construction
// ObjTypeName{field: FieldTypeName{value: ...}}
//
// There are a few rules for the qualified names and the simple abbreviations generated from them:
// - Qualified names must be dot-delimited, e.g. `package.subpkg.name`.
// - The last element in the qualified name is the abbreviation.
// - Abbreviations must not collide with each other.
// - The abbreviation must not collide with unqualified names in use.
//
// Abbreviations are distinct from container-based references in the following important ways:
// - Abbreviations must expand to a fully-qualified name.
// - Expanded abbreviations do not participate in namespace resolution.
// - Abbreviation expansion is done instead of the container search for a matching identifier.
// - Containers follow C++ namespace resolution rules with searches from the most qualified name
// to the least qualified name.
// - Container references within the CEL program may be relative, and are resolved to fully
// qualified names at either type-check time or program plan time, whichever comes first.
//
// If there is ever a case where an identifier could be in both the container and as an
// abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
// preserved between compilations even as the container evolves.
func Abbrevs(qualifiedNames ...string) ContainerOption {
return func(c *Container) (*Container, error) {
for _, qn := range qualifiedNames {
ind := strings.LastIndex(qn, ".")
if ind <= 0 || ind >= len(qn)-1 {
return nil, fmt.Errorf(
"invalid qualified name: %s, wanted name of the form 'qualified.name'", qn)
}
alias := qn[ind+1:]
var err error
c, err = aliasAs("abbreviation", qn, alias)(c)
if err != nil {
return nil, err
}
}
return c, nil
}
}
// Alias associates a fully-qualified name with a user-defined alias.
//
// In general, Abbrevs is preferred to Alias since the names generated from the Abbrevs option
// are more easily traced back to source code. The Alias option is useful for propagating alias
// configuration from one Container instance to another, and may also be useful for remapping
// poorly chosen protobuf message / package names.
//
// Note: all of the rules that apply to Abbrevs also apply to Alias.
func Alias(qualifiedName, alias string) ContainerOption {
return aliasAs("alias", qualifiedName, alias)
}
func aliasAs(kind, qualifiedName, alias string) ContainerOption {
return func(c *Container) (*Container, error) {
if len(alias) == 0 || strings.Contains(alias, ".") {
return nil, fmt.Errorf(
"%s must be non-empty and simple (not qualified): %s=%s", kind, kind, alias)
}
if qualifiedName[0:1] == "." {
return nil, fmt.Errorf("qualified name must not begin with a leading '.': %s",
qualifiedName)
}
ind := strings.LastIndex(qualifiedName, ".")
if ind <= 0 || ind == len(qualifiedName)-1 {
return nil, fmt.Errorf("%s must refer to a valid qualified name: %s",
kind, qualifiedName)
}
aliasRef, found := c.aliasSet()[alias]
if found {
return nil, fmt.Errorf(
"%s collides with existing reference: name=%s, %s=%s, existing=%s",
kind, qualifiedName, kind, alias, aliasRef)
}
if strings.HasPrefix(c.Name(), alias+".") || c.Name() == alias {
return nil, fmt.Errorf(
"%s collides with container name: name=%s, %s=%s, container=%s",
kind, qualifiedName, kind, alias, c.Name())
}
if c == nil {
c = &Container{}
}
if c.aliases == nil {
c.aliases = make(map[string]string)
}
c.aliases[alias] = qualifiedName
return c, nil
}
}
// Name sets the fully-qualified name of the Container.
func Name(name string) ContainerOption {
return func(c *Container) (*Container, error) {
if len(name) > 0 && name[0:1] == "." {
return nil, fmt.Errorf("container name must not contain a leading '.': %s", name)
}
if c.Name() == name {
return c, nil
}
if c == nil {
return &Container{name: name}, nil
}
c.name = name
return c, nil
}
}
// ToQualifiedName converts an expression AST into a qualified name if possible, with a boolean
// 'found' value that indicates if the conversion is successful.
func ToQualifiedName(e ast.Expr) (string, bool) {
switch e.Kind() {
case ast.IdentKind:
id := e.AsIdent()
return id, true
case ast.SelectKind:
sel := e.AsSelect()
// Test only expressions are not valid as qualified names.
if sel.IsTestOnly() {
return "", false
}
if qual, found := ToQualifiedName(sel.Operand()); found {
return qual + "." + sel.FieldName(), true
}
}
return "", false
}
|