Coder Social home page Coder Social logo

jafaircl / cel Goto Github PK

View Code? Open in Web Editor NEW
0.0 1.0 0.0 327 KB

WIP: Fast, portable, non-Turing complete expression evaluation (TypeScript)

License: Apache License 2.0

TypeScript 67.76% Go 0.48% Java 30.24% ANTLR 1.05% JavaScript 0.46%
cel expression-evaluator expression-parser typescript

cel's Introduction

Common Expression Language

NOTE: This is a work in progress and should not be used in production

@celjs/parser on npm

The Common Expression Language (CEL) is a non-Turing complete language designed for simplicity, speed, safety, and portability. CEL's C-like syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript.

// Check whether a resource name starts with a group name.
resource.name.startsWith("/groups/" + auth.claims.group)
// Determine whether the request is in the permitted time window.
request.time - resource.age < duration("24h")
// Check whether all resource names in a list match a given filter.
auth.claims.email_verified && resources.all(r, r.startsWith(auth.claims.email))

A CEL "program" is a single expression. The examples have been tagged as java, go, and typescript within the markdown to showcase the commonality of the syntax.

CEL is ideal for lightweight expression evaluation when a fully sandboxed scripting language is too resource intensive. To get started, try the Codelab.



Overview

Determine the variables and functions you want to provide to CEL. Parse and check an expression to make sure it's valid. Then evaluate the output AST against some input. Checking is optional, but strongly encouraged.

Environment Setup

Let's expose name and group variables to CEL using the cel.Declarations environment option:

import { Decl } from '@buf/google_cel-spec.bufbuild_es/cel/expr/checked_pb';
import { CELEnvironment } from '@celjs/parser';

const environment = new CELEnvironment([
    Decl.fromJson({
        name: 'name',
        ident: {
            type: {
                primitive: Type_PrimitiveType.STRING,
            },
        },
    }),
    Decl.fromJson({
        name: 'group',
        ident: {
            type: {
                primitive: Type_PrimitiveType.STRING,
            },
        },
    }),
]);

That's it. The environment is ready to be used for parsing and type-checking. CEL supports all the usual primitive types in addition to lists, maps, as well as first-class support for JSON and Protocol Buffers.

Parse and Check

The parsing phase indicates whether the expression is syntactically valid and expands any macros present within the environment. Parsing and checking are more computationally expensive than evaluation, and it is recommended that expressions be parsed and checked ahead of time.

The parse and check phases are combined for convenience into the Compile step:

const ast = environment.compile(`name.startsWith("/groups/" + group)`);
const program = environment.program(ast);

The cel.Program generated at the end of parse and check is stateless, thread-safe, and cachable.

Macros

Macros are optional but enabled by default. Macros were introduced to support optional CEL features that might not be desired in all use cases without the syntactic burden and complexity such features might desire if they were part of the core CEL syntax. Macros are expanded at parse time and their expansions are type-checked at check time.

For example, when macros are enabled it is possible to support bounded iteration / fold operators. The macros all, exists, exists_one, filter, and map are particularly useful for evaluating a single predicate against list and map values.

// Ensure all tweets are less than 140 chars
tweets.all(t, t.size() <= 140)

The has macro is useful for unifying field presence testing logic across protobuf types and dynamic (JSON-like) types.

// Test whether the field is a non-default value if proto-based, or defined
// in the JSON case.
has(message.field)

Both cases traditionally require special syntax at the language level, but these features are exposed via macros in CEL.

Evaluate

Now, evaluate for fun and profit. The evaluation is thread-safe and side-effect free. Many different inputs can be sent to the same cel.Program and if fields are present in the input, but not referenced in the expression, they are ignored. Note that the output of eval will be an ExprValue object. A helper function, exprValueToNative, is provided which will convert the expression value to a native TypeScript value.

// The `out` var contains the output of a successful evaluation.
const out = program.eval({
    name: Value.fromJson({ stringValue: '/groups/acme.co/documents/secret-stuff' }),
    group: Value.fromJson({ stringValue: 'acme.co' }),
})
console.log(out) // new ExprValue({ kind: { case: 'value', value: { kind: { case: 'boolValue', value: true, }, }, }, })
console.log(exprValueToNative(out)) // true

Helper Functions

If your use case involves evaluating the same expression against different variables, it is highly suggested to compile and evaluate in separate steps. This will make sure that your evaluations are not slowed down by repeatedly compiling the same program. If, however, your use case is to use an expression once and discard it, we provide some helper functions to deal with those use cases.

parse will parse your expression to a CEL Expr object.

import { Expr } from '@buf/google_cel-spec.bufbuild_es/cel/expr/syntax_pb';
import { parse } from '@celjs/parser'; 

const parsed = parse('2 + 2');
parsed === Expr.fromJson({
  "id": "3",
  "callExpr": {
    "function": "_+_",
    "args": [
      {
        "id": "1",
        "constExpr": {
          "int64Value": "2"
        }
      },
      {
        "id": "2",
        "constExpr": {
          "int64Value": "2"
        }
      }
    ]
  }
})

parseAndEval will parse and evaluate your expression and return the result as an ExprValue object.

import { ExprValue } from '@buf/google_cel-spec.bufbuild_es/cel/expr/eval_pb';
import { parseAndEval } from '@celjs/parser'; 

const evaluated = parseAndEval(`2 + 2`);
evaluated === ExprValue.fromJson({
  "value": {
    "int64Value": "4"
  }
})

parseAndEvalToNative will parse and evaluate your expression and return the result as a native TypeScript value.

import { parseAndEvalToNative } from '@celjs/parser'; 

const native = parseAndEvalToNative(`2 + 2`);
native === 4

Install

Run npm install @celjs/parser to install the package from npm.

Common Questions

Why not JavaScript, Lua, or WASM?

JavaScript and Lua are rich languages that require sandboxing to execute safely. Sandboxing is costly and factors into the "what will I let users evaluate?" question heavily when the answer is anything more than O(n) complexity.

CEL evaluates linearly with respect to the size of the expression and the input being evaluated when macros are disabled. The only functions beyond the built-ins that may be invoked are provided by the host environment. While extension functions may be more complex, this is a choice by the application embedding CEL.

But, why not WASM? WASM is an excellent choice for certain applications and is far superior to embedded JavaScript and Lua, but it does not have support for garbage collection and non-primitive object types require semi-expensive calls across modules. In most cases CEL will be faster and just as portable for its intended use case, though for node.js and web-based execution CEL too may offer a WASM evaluator with direct to WASM compilation.

Do I need to Parse and Check?

Checking is an optional, but strongly suggested, step in CEL expression validation. It is sufficient in some cases to simply Parse and rely on the runtime bindings and error handling to do the right thing.

Where can I learn more about the language?

  • See the CEL Spec for the specification and conformance test suite.
  • Ask for support on the CEL Go Discuss Google group.

How can I contribute?

  • Pull requests are welcome. A code of conduct and more formal contributing guidelines are coming soon.
  • Use GitHub Issues to request features or report bugs.

License

Released under the Apache License.

Disclaimer: This is not an official Google product.

cel's People

Contributors

jafaircl avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.