1. Pengantar
Apa itu CEL?
CEL adalah bahasa ekspresi lengkap non-Turing yang didesain agar cepat, portabel, dan aman untuk dijalankan. CEL dapat digunakan sendiri, atau disematkan ke dalam produk yang lebih besar.
CEL dirancang sebagai bahasa yang aman untuk mengeksekusi kode pengguna. Meskipun memanggil eval()
secara membabi buta pada kode python pengguna itu berbahaya, Anda dapat mengeksekusi kode CEL pengguna dengan aman. Dan karena CEL mencegah perilaku yang akan membuatnya kurang berperforma baik, CEL akan mengevaluasi dengan aman pada urutan nanodetik hingga mikrodetik; Mode ini ideal untuk aplikasi yang penting bagi performa.
CEL mengevaluasi ekspresi, yang mirip dengan fungsi baris tunggal atau ekspresi lambda. Meskipun CEL biasanya digunakan untuk keputusan boolean, CEL juga dapat digunakan untuk membuat objek yang lebih kompleks seperti pesan JSON atau protobuf.
Apakah CEL tepat untuk proyek Anda?
Karena CEL mengevaluasi ekspresi dari AST dalam nanodetik ke mikrodetik, kasus penggunaan ideal untuk CEL adalah aplikasi dengan jalur yang penting bagi performa. Kompilasi kode CEL ke dalam AST tidak boleh dilakukan di jalur kritis; ideal adalah aplikasi yang konfigurasinya sering dieksekusi dan relatif jarang dimodifikasi.
Misalnya, menjalankan kebijakan keamanan dengan setiap permintaan HTTP ke layanan adalah kasus penggunaan yang ideal untuk CEL karena kebijakan keamanan jarang berubah, dan CEL akan memiliki dampak yang kecil pada waktu respons. Dalam hal ini, CEL menampilkan boolean, jika permintaan tersebut diizinkan atau tidak diizinkan, tetapi dapat menampilkan pesan yang lebih kompleks.
Apa yang dibahas dalam Codelab ini?
Langkah pertama codelab ini membahas motivasi untuk menggunakan CEL dan Konsep Inti-nya. Bagian lainnya dikhususkan untuk Latihan coding yang mencakup kasus penggunaan umum. Untuk pembahasan lebih mendalam tentang bahasa, semantik, dan fitur, lihat Definisi Bahasa CEL di GitHub dan Dokumen CEL Go.
Codelab ini ditujukan untuk developer yang ingin mempelajari CEL agar dapat menggunakan layanan yang sudah mendukung CEL. Codelab ini tidak membahas cara mengintegrasikan CEL ke dalam project Anda sendiri.
Yang akan Anda pelajari
- Konsep inti dari CEL
- Halo, Dunia: Menggunakan CEL untuk mengevaluasi String
- Membuat variabel
- Memahami korsleting CEL dalam operasi AND/OR logis
- Cara menggunakan CEL untuk membangun JSON
- Cara menggunakan CEL untuk membangun Protobuffer
- Membuat Makro
- Cara menyesuaikan ekspresi CEL
Yang Anda butuhkan
Prasyarat
Codelab ini dibuat berdasarkan pemahaman dasar tentang Buffering Protokol dan Go Lang.
Jika Anda tidak terbiasa dengan Buffering Protokol, latihan pertama akan memberi Anda gambaran tentang cara kerja CEL, tetapi karena contoh yang lebih canggih menggunakan Buffering Protokol sebagai input ke CEL, contoh tersebut mungkin lebih sulit dipahami. Pertimbangkan untuk menyelesaikan salah satu tutorial ini terlebih dahulu. Perhatikan bahwa Buffering Protokol tidak diperlukan untuk menggunakan CEL, tetapi digunakan secara ekstensif dalam codelab ini.
Anda dapat menguji bahwa go telah diinstal dengan menjalankan:
go --help
2. Konsep utama
Aplikasi
CEL memiliki tujuan umum, dan telah digunakan untuk berbagai aplikasi, mulai dari mengarahkan RPC, hingga menentukan kebijakan keamanan. CEL dapat diperluas, tidak bergantung pada aplikasi, dan dioptimalkan untuk alur kerja kompilasi satu kali dan evaluasi banyak.
Banyak layanan dan aplikasi mengevaluasi konfigurasi deklaratif. Misalnya, Role-Based Access Control (RBAC) adalah konfigurasi deklaratif yang menghasilkan keputusan akses berdasarkan peran dan sekumpulan pengguna. Jika konfigurasi deklaratif adalah kasus penggunaan 80%, maka CEL adalah alat yang berguna untuk membulatkan 20% sisanya saat pengguna membutuhkan daya yang lebih ekspresif.
Kompilasi
Ekspresi dikompilasi terhadap lingkungan. Langkah kompilasi menghasilkan Abstrak Syntax Tree (AST) dalam bentuk protobuf. Ekspresi yang dikompilasi umumnya disimpan untuk digunakan di masa mendatang agar evaluasi tetap secepat mungkin. Satu ekspresi yang dikompilasi dapat dievaluasi dengan banyak input yang berbeda.
Ekspresi
Pengguna menentukan ekspresi; dan aplikasi menentukan lingkungan tempatnya dijalankan. Tanda tangan fungsi mendeklarasikan input, dan ditulis di luar ekspresi CEL. Library fungsi yang tersedia untuk CEL diimpor secara otomatis.
Pada contoh berikut, ekspresi mengambil objek permintaan, dan permintaan menyertakan token klaim. Ekspresi tersebut menampilkan boolean yang menunjukkan apakah token klaim masih valid.
// Check whether a JSON Web Token has expired by inspecting the 'exp' claim.
//
// Args:
// claims - authentication claims.
// now - timestamp indicating the current system time.
// Returns: true if the token has expired.
//
timestamp(claims["exp"]) < now
Lingkungan
Lingkungan ditentukan oleh layanan. Layanan dan aplikasi yang menyematkan CEL mendeklarasikan lingkungan ekspresi. Lingkungan adalah kumpulan variabel dan fungsi yang dapat digunakan dalam ekspresi.
Deklarasi berbasis proto digunakan oleh pemeriksa jenis CEL untuk memastikan semua referensi ID dan fungsi dalam ekspresi dideklarasikan dan digunakan dengan benar.
Tiga fase penguraian ekspresi
Ada tiga fase dalam memproses ekspresi: mengurai, memeriksa, dan mengevaluasi. Pola yang paling umum untuk CEL adalah bidang kontrol untuk mengurai dan memeriksa ekspresi pada waktu konfigurasi, dan menyimpan AST.
Saat runtime, bidang data mengambil dan mengevaluasi AST berulang kali. CEL dioptimalkan untuk efisiensi runtime, tetapi penguraian dan pemeriksaan tidak boleh dilakukan di jalur kode penting latensi.
CEL diuraikan dari ekspresi yang dapat dibaca manusia menjadi hierarki sintaksis abstrak menggunakan tata bahasa lexer / Parser ANTLR. Fase penguraian memunculkan hierarki sintaksis abstrak berbasis proto dengan setiap node Expr di AST berisi ID bilangan bulat yang digunakan untuk mengindeks ke dalam metadata yang dihasilkan selama penguraian dan pemeriksaan. syntax.proto yang dihasilkan selama penguraian dengan tepat mewakili representasi abstrak dari apa yang diketik di bentuk string ekspresi.
Setelah diuraikan, ekspresi dapat diperiksa dengan lingkungan untuk memastikan semua ID variabel dan fungsi dalam ekspresi telah dideklarasikan dan digunakan dengan benar. Pemeriksa jenis menghasilkan checked.proto yang mencakup metadata resolusi jenis, variabel, dan fungsi yang dapat meningkatkan efisiensi evaluasi secara drastis.
Evaluator CEL membutuhkan 3 hal:
- Binding fungsi untuk semua ekstensi kustom
- Binding variabel
- AST yang akan dievaluasi
Fungsi dan binding variabel harus sesuai dengan yang digunakan untuk mengompilasi AST. Salah satu dari input ini dapat digunakan kembali di beberapa evaluasi, seperti AST yang dievaluasi pada banyak set binding variabel, atau variabel yang sama yang digunakan terhadap banyak AST, atau binding fungsi yang digunakan sepanjang masa proses (kasus umum).
3. Siapkan
Kode untuk codelab ini berada di folder codelab
dari repo cel-go
. Solusi ini tersedia di folder codelab/solution
dari repo yang sama.
Clone dan cd ke repo:
git clone https://github.com/google/cel-go.git
cd cel-go/codelab
Jalankan kode menggunakan go run
:
go run .
Anda akan melihat output berikut:
=== Exercise 1: Hello World ===
=== Exercise 2: Variables ===
=== Exercise 3: Logical AND/OR ===
=== Exercise 4: Customization ===
=== Exercise 5: Building JSON ===
=== Exercise 6: Building Protos ===
=== Exercise 7: Macros ===
=== Exercise 8: Tuning ===
Di mana paket CEL?
Di editor Anda, buka codelab/codelab.go
. Anda akan melihat fungsi utama yang mendorong eksekusi latihan di codelab ini, diikuti dengan tiga blok fungsi bantuan. Set helper pertama membantu fase evaluasi CEL:
- Fungsi
Compile
: Mengurai dan memeriksa serta memasukkan ekspresi terhadap lingkungan - Fungsi
Eval
: Mengevaluasi program yang dikompilasi terhadap input - Fungsi
Report
: Pretty mencetak hasil evaluasi
Selain itu, helper request
dan auth
telah disediakan untuk membantu konstruksi input untuk berbagai latihan.
Latihan ini akan merujuk pada paket berdasarkan nama paketnya yang pendek. Pemetaan dari paket ke lokasi sumber dalam repo google/cel-go
ada di bawah ini jika Anda ingin menggali detailnya:
Paket | Lokasi Sumber | Deskripsi |
cel | Antarmuka tingkat atas | |
referensi | Antarmuka referensi | |
tipe | Nilai jenis runtime |
4. Halo, Dunia!
Dalam tradisi semua bahasa pemrograman, kita akan mulai dengan membuat dan mengevaluasi "Hello World!".
Mengonfigurasi lingkungan
Di editor Anda, cari deklarasi exercise1
, lalu isi hal berikut untuk menyiapkan lingkungan:
// exercise1 evaluates a simple literal expression: "Hello, World!"
//
// Compile, eval, profit!
func exercise1() {
fmt.Println("=== Exercise 1: Hello World ===\n")
// Create the standard environment.
env, err := cel.NewEnv()
if err != nil {
glog.Exitf("env error: %v", err)
}
// Will add the parse and check steps here
}
Aplikasi CEL mengevaluasi suatu ekspresi terhadap Lingkungan. env, err := cel.NewEnv()
mengonfigurasi lingkungan standar.
Lingkungan dapat disesuaikan dengan memberikan opsi cel.EnvOption
untuk panggilan. Opsi tersebut dapat menonaktifkan makro, mendeklarasikan fungsi dan variabel kustom, dll.
Lingkungan CEL standar mendukung semua jenis, operator, fungsi, dan makro yang ditentukan dalam spesifikasi bahasa.
Urai dan periksa ekspresi
Setelah lingkungan dikonfigurasi, ekspresi dapat diuraikan dan diperiksa. Tambahkan hal berikut ke fungsi Anda:
// exercise1 evaluates a simple literal expression: "Hello, World!"
//
// Compile, eval, profit!
func exercise1() {
fmt.Println("=== Exercise 1: Hello World ===\n")
// Create the standard environment.
env, err := cel.NewEnv()
if err != nil {
glog.Exitf("env error: %v", err)
}
// Check that the expression compiles and returns a String.
ast, iss := env.Parse(`"Hello, World!"`)
// Report syntactic errors, if present.
if iss.Err() != nil {
glog.Exit(iss.Err())
}
// Type-check the expression for correctness.
checked, iss := env.Check(ast)
// Report semantic errors, if present.
if iss.Err() != nil {
glog.Exit(iss.Err())
}
// Check the output type is a string.
if !reflect.DeepEqual(checked.OutputType(), cel.StringType) {
glog.Exitf(
"Got %v, wanted %v output type",
checked.OutputType(), cel.StringType,
)
}
// Will add the planning step here
}
Nilai iss
yang ditampilkan oleh panggilan Parse
dan Check
adalah daftar masalah yang dapat berupa error. Jika iss.Err()
tidak bernilai nol, berarti terjadi error dalam sintaksis atau semantik, dan program tidak dapat melanjutkan program. Jika ekspresi terbentuk dengan baik, hasil panggilan ini adalah cel.Ast
yang dapat dieksekusi.
Mengevaluasi ekspresi
Setelah ekspresi diuraikan dan diperiksa menjadi cel.Ast
, ekspresi tersebut dapat dikonversi menjadi program yang dapat dievaluasi yang binding fungsi dan mode evaluasinya dapat disesuaikan dengan opsi fungsional. Perlu diketahui bahwa Anda juga dapat membaca cel.Ast
dari proto menggunakan fungsi cel.CheckedExprToAst atau cel.ParsedExprToAst.
Setelah cel.Program
direncanakan, hal tersebut dapat dievaluasi terhadap input dengan memanggil Eval
. Hasil Eval
akan berisi hasil, detail evaluasi, dan status error.
Tambahkan perencanaan dan hubungi eval
:
// exercise1 evaluates a simple literal expression: "Hello, World!"
//
// Compile, eval, profit!
func exercise1() {
fmt.Println("=== Exercise 1: Hello World ===\n")
// Create the standard environment.
env, err := cel.NewEnv()
if err != nil {
glog.Exitf("env error: %v", err)
}
// Check that the expression compiles and returns a String.
ast, iss := env.Parse(`"Hello, World!"`)
// Report syntactic errors, if present.
if iss.Err() != nil {
glog.Exit(iss.Err())
}
// Type-check the expression for correctness.
checked, iss := env.Check(ast)
// Report semantic errors, if present.
if iss.Err() != nil {
glog.Exit(iss.Err())
}
// Check the output type is a string.
if !reflect.DeepEqual(checked.OutputType(), cel.StringType) {
glog.Exitf(
"Got %v, wanted %v output type",
checked.OutputType(), cel.StringType)
}
// Plan the program.
program, err := env.Program(checked)
if err != nil {
glog.Exitf("program error: %v", err)
}
// Evaluate the program without any additional arguments.
eval(program, cel.NoVars())
fmt.Println()
}
Agar lebih singkat, kami akan menghapus kasus error yang disertakan di atas dari latihan mendatang.
Menjalankan kode
Pada command line, jalankan kembali kode:
go run .
Anda akan melihat output berikut, bersama dengan placeholder untuk latihan mendatang.
=== Exercise 1: Hello World ===
------ input ------
(interpreter.emptyActivation)
------ result ------
value: Hello, World! (types.String)
5. Menggunakan variabel dalam fungsi
Sebagian besar aplikasi CEL akan mendeklarasikan variabel yang dapat direferensikan dalam ekspresi. Deklarasi variabel menentukan nama dan jenis. Jenis variabel dapat berupa jenis bawaan CEL, jenis populer buffering protokol, atau jenis pesan protobuf apa pun selama deskriptornya juga diberikan kepada CEL.
Menambahkan fungsi
Di editor Anda, temukan deklarasi exercise2
, dan tambahkan hal berikut:
// exercise2 shows how to declare and use variables in expressions.
//
// Given a request of type google.rpc.context.AttributeContext.Request
// determine whether a specific auth claim is set.
func exercise2() {
fmt.Println("=== Exercise 2: Variables ===\n")
env, err := cel.NewEnv(
// Add cel.EnvOptions values here.
)
if err != nil {
glog.Exit(err)
}
ast := compile(env, `request.auth.claims.group == 'admin'`, cel.BoolType)
program, _ := env.Program(ast)
// Evaluate a request object that sets the proper group claim.
claims := map[string]string{"group": "admin"}
eval(program, request(auth("user:me@acme.co", claims), time.Now()))
fmt.Println()
}
Menjalankan kembali dan memahami error
Jalankan kembali program:
go run .
Anda akan melihat output berikut:
ERROR: <input>:1:1: undeclared reference to 'request' (in container '')
| request.auth.claims.group == 'admin'
| ^
Pemeriksa jenis menghasilkan error untuk objek permintaan yang dengan mudah menyertakan cuplikan sumber tempat error terjadi.
6. Mendeklarasikan variabel
Tambahkan EnvOptions
Di editor, mari kita perbaiki error yang dihasilkan dengan memberikan deklarasi untuk objek permintaan sebagai pesan berjenis google.rpc.context.AttributeContext.Request
seperti berikut:
// exercise2 shows how to declare and use variables in expressions.
//
// Given a `request` of type `google.rpc.context.AttributeContext.Request`
// determine whether a specific auth claim is set.
func exercise2() {
fmt.Println("=== Exercise 2: Variables ===\n")
env, err := cel.NewEnv(
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
)
if err != nil {
glog.Exit(err)
}
ast := compile(env, `request.auth.claims.group == 'admin'`, cel.BoolType)
program, _ := env.Program(ast)
// Evaluate a request object that sets the proper group claim.
claims := map[string]string{"group": "admin"}
eval(program, request(auth("user:me@acme.co", claims), time.Now()))
fmt.Println()
}
Menjalankan kembali dan memahami error
Menjalankan kembali program:
go run .
Anda akan melihat error berikut:
ERROR: <input>:1:8: [internal] unexpected failed resolution of 'google.rpc.context.AttributeContext.Request'
| request.auth.claims.group == 'admin'
| .......^
Untuk menggunakan variabel yang merujuk ke pesan protobuf, pemeriksa jenis juga perlu mengetahui deskriptor jenis.
Gunakan cel.Types
untuk menentukan deskriptor permintaan dalam fungsi Anda:
// exercise2 shows how to declare and use variables in expressions.
//
// Given a `request` of type `google.rpc.context.AttributeContext.Request`
// determine whether a specific auth claim is set.
func exercise2() {
fmt.Println("=== Exercise 2: Variables ===\n")
env, err := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
)
if err != nil {
glog.Exit(err)
}
ast := compile(env, `request.auth.claims.group == 'admin'`, cel.BoolType)
program, _ := env.Program(ast)
// Evaluate a request object that sets the proper group claim.
claims := map[string]string{"group": "admin"}
eval(program, request(auth("user:me@acme.co", claims), time.Now()))
fmt.Println()
}
Berhasil dijalankan kembali!
Jalankan lagi program:
go run .
Anda akan melihat berikut ini:
=== Exercise 2: Variables ===
request.auth.claims.group == 'admin'
------ input ------
request = time: <
seconds: 1569255569
>
auth: <
principal: "user:me@acme.co"
claims: <
fields: <
key: "group"
value: <
string_value: "admin"
>
>
>
>
------ result ------
value: true (types.Bool)
Untuk meninjau, kita baru saja mendeklarasikan variabel untuk error, memberinya deskriptor jenis, lalu mereferensikan variabel tersebut dalam evaluasi ekspresi.
7. Logika AND/OR
Salah satu fitur unik CEL adalah penggunaan operator logika komutatif. Kedua sisi cabang bersyarat dapat menimbulkan hubungan pendek pada evaluasi, bahkan saat terjadi error atau input parsial.
Dengan kata lain, CEL menemukan urutan evaluasi yang memberikan hasil bila memungkinkan, mengabaikan kesalahan atau bahkan kehilangan data yang mungkin terjadi dalam urutan evaluasi lainnya. Aplikasi dapat mengandalkan properti ini untuk meminimalkan biaya evaluasi, sehingga menunda pengumpulan input yang mahal ketika hasil dapat dicapai tanpanya.
Kita akan menambahkan contoh AND/OR (DAN/ATAU), lalu mencobanya dengan berbagai input untuk memahami cara evaluasi arus pendek CEL.
Membuat fungsi
Di editor Anda, tambahkan konten berikut ke latihan 3:
// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks if the `request.auth.claims.group`
// value is equal to admin or the `request.auth.principal` is
// `user:me@acme.co`. Issue two requests, one that specifies the proper
// user, and one that specifies an unexpected user.
func exercise3() {
fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
)
}
Selanjutnya, sertakan pernyataan OR ini yang akan menampilkan benar jika pengguna adalah anggota grup admin
, atau memiliki ID email tertentu:
// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks if the `request.auth.claims.group`
// value is equal to admin or the `request.auth.principal` is
// `user:me@acme.co`. Issue two requests, one that specifies the proper
// user, and one that specifies an unexpected user.
func exercise3() {
fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
)
ast := compile(env,
`request.auth.claims.group == 'admin'
|| request.auth.principal == 'user:me@acme.co'`,
cel.BoolType)
program, _ := env.Program(ast)
}
Terakhir, tambahkan kasus eval
yang mengevaluasi pengguna dengan kumpulan klaim kosong:
// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks whether the request.auth.claims.group
// value is equal to admin or the request.auth.principal is
// user:me@acme.co. Issue two requests, one that specifies the proper user,
// and one that specifies an unexpected user.
func exercise3() {
fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
)
ast := compile(env,
`request.auth.claims.group == 'admin'
|| request.auth.principal == 'user:me@acme.co'`,
cel.BoolType)
program, _ := env.Program(ast)
emptyClaims := map[string]string{}
eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
}
Jalankan kode dengan kumpulan klaim kosong
Menjalankan kembali program, Anda akan melihat output baru berikut:
=== Exercise 3: Logical AND/OR ===
request.auth.claims.group == 'admin'
|| request.auth.principal == 'user:me@acme.co'
------ input ------
request = time: <
seconds: 1569302377
>
auth: <
principal: "user:me@acme.co"
claims: <
>
>
------ result ------
value: true (types.Bool)
Memperbarui kasus evaluasi
Selanjutnya, perbarui kasus evaluasi untuk meneruskan akun utama yang berbeda dengan kumpulan klaim kosong:
// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks whether the request.auth.claims.group
// value is equal to admin or the request.auth.principal is
// user:me@acme.co. Issue two requests, one that specifies the proper user,
// and one that specifies an unexpected user.
func exercise3() {
fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
)
ast := compile(env,
`request.auth.claims.group == 'admin'
|| request.auth.principal == 'user:me@acme.co'`,
cel.BoolType)
program, _ := env.Program(ast)
emptyClaims := map[string]string{}
eval(program, request(auth("other:me@acme.co", emptyClaims), time.Now()))
}
Jalankan kode dengan waktu
Menjalankan kembali program,
go run .
Anda akan melihat error berikut:
=== Exercise 3: Logical AND/OR ===
request.auth.claims.group == 'admin'
|| request.auth.principal == 'user:me@acme.co'
------ input ------
request = time: <
seconds: 1588989833
>
auth: <
principal: "user:me@acme.co"
claims: <
>
>
------ result ------
value: true (types.Bool)
------ input ------
request = time: <
seconds: 1588989833
>
auth: <
principal: "other:me@acme.co"
claims: <
>
>
------ result ------
error: no such key: group
Di protobuf, kami mengetahui kolom dan jenis yang diperlukan. Dalam nilai peta dan JSON, kita tidak tahu apakah kunci akan ada. Karena tidak ada nilai default yang aman untuk kunci yang hilang, CEL secara default akan menampilkan error.
8. Fungsi Kustom
Meskipun CEL menyertakan banyak fungsi bawaan, ada kalanya fungsi kustom berguna. Misalnya, fungsi kustom dapat digunakan untuk meningkatkan kualitas pengalaman pengguna untuk kondisi umum atau mengekspos status sensitif konteks
Dalam latihan ini, kita akan mempelajari cara mengekspos fungsi untuk memaketkan pemeriksaan yang biasa digunakan.
Memanggil fungsi kustom
Pertama, buat kode untuk menyiapkan penggantian bernama contains
yang menentukan apakah kunci ada di peta dan memiliki nilai tertentu. Sisakan placeholder untuk definisi fungsi dan binding fungsi:
// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
fmt.Println("=== Exercise 4: Customization ===\n")
// Determine whether an optional claim is set to the proper value. The
// custom map.contains(key, value) function is used as an alternative to:
// key in map && map[key] == value
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
// Declare the request.
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
// Add the custom function declaration and binding here with cel.Function()
)
ast := compile(env,
`request.auth.claims.contains('group', 'admin')`,
cel.BoolType)
// Construct the program plan and provide the 'contains' function impl.
// Output: false
program, _ := env.Program(ast)
emptyClaims := map[string]string{}
eval(
program,
request(auth("user:me@acme.co", emptyClaims), time.Now()),
)
fmt.Println()
}
Menjalankan kode dan memahami error
Menjalankan kembali kode, Anda akan melihat error berikut:
ERROR: <input>:1:29: found no matching overload for 'contains' applied to 'map(string, dyn).(string, string)'
| request.auth.claims.contains('group', 'admin')
| ............................^
Untuk memperbaiki error ini, kita harus menambahkan fungsi contains
ke daftar deklarasi yang saat ini mendeklarasikan variabel permintaan.
Deklarasikan jenis berparameter dengan menambahkan 3 baris berikut. (Ini serumit hal ini karena kelebihan beban fungsi untuk CEL)
// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
fmt.Println("=== Exercise 4: Customization ===\n")
// Determine whether an optional claim is set to the proper value. The custom
// map.contains(key, value) function is used as an alternative to:
// key in map && map[key] == value
// Useful components of the type-signature for 'contains'.
typeParamA := cel.TypeParamType("A")
typeParamB := cel.TypeParamType("B")
mapAB := cel.MapType(typeParamA, typeParamB)
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
// Declare the request.
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
// Add the custom function declaration and binding here with cel.Function()
)
ast := compile(env,
`request.auth.claims.contains('group', 'admin')`,
cel.BoolType)
// Construct the program plan.
// Output: false
program, _ := env.Program(ast)
emptyClaims := map[string]string{}
eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
fmt.Println()
}
Menambahkan fungsi kustom
Selanjutnya, kita akan menambahkan fungsi contain baru yang akan menggunakan jenis berparameter:
// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
fmt.Println("=== Exercise 4: Customization ===\n")
// Determine whether an optional claim is set to the proper value. The custom
// map.contains(key, value) function is used as an alternative to:
// key in map && map[key] == value
// Useful components of the type-signature for 'contains'.
typeParamA := cel.TypeParamType("A")
typeParamB := cel.TypeParamType("B")
mapAB := cel.MapType(typeParamA, typeParamB)
// Env declaration.
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
// Declare the request.
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
// Declare the custom contains function and its implementation.
cel.Function("contains",
cel.MemberOverload(
"map_contains_key_value",
[]*cel.Type{mapAB, typeParamA, typeParamB},
cel.BoolType,
// Provide the implementation using cel.FunctionBinding()
),
),
)
ast := compile(env,
`request.auth.claims.contains('group', 'admin')`,
cel.BoolType)
// Construct the program plan and provide the 'contains' function impl.
// Output: false
program, _ := env.Program(ast)
emptyClaims := map[string]string{}
eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
fmt.Println()
}
Menjalankan program untuk memahami error
Jalankan latihan. Anda akan melihat error berikut terkait fungsi runtime yang tidak ada:
------ result ------
error: no such overload: contains
Berikan implementasi fungsi ke deklarasi NewEnv
menggunakan fungsi cel.FunctionBinding()
:
// exercise4 demonstrates how to extend CEL with custom functions.
//
// Declare a contains member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
fmt.Println("=== Exercise 4: Customization ===\n")
// Determine whether an optional claim is set to the proper value. The custom
// map.contains(key, value) function is used as an alternative to:
// key in map && map[key] == value
// Useful components of the type-signature for 'contains'.
typeParamA := cel.TypeParamType("A")
typeParamB := cel.TypeParamType("B")
mapAB := cel.MapType(typeParamA, typeParamB)
// Env declaration.
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
// Declare the request.
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
// Declare the custom contains function and its implementation.
cel.Function("contains",
cel.MemberOverload(
"map_contains_key_value",
[]*cel.Type{mapAB, typeParamA, typeParamB},
cel.BoolType,
cel.FunctionBinding(mapContainsKeyValue)),
),
)
ast := compile(env,
`request.auth.claims.contains('group', 'admin')`,
cel.BoolType)
// Construct the program plan.
// Output: false
program, err := env.Program(ast)
if err != nil {
glog.Exit(err)
}
eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
claims := map[string]string{"group": "admin"}
eval(program, request(auth("user:me@acme.co", claims), time.Now()))
fmt.Println()
}
Kini program akan berhasil dijalankan:
=== Exercise 4: Custom Functions ===
request.auth.claims.contains('group', 'admin')
------ input ------
request = time: <
seconds: 1569302377
>
auth: <
principal: "user:me@acme.co"
claims: <
>
>
------ result ------
value: false (types.Bool)
Apa yang terjadi jika klaim dibuat?
Untuk kredit tambahan, coba tetapkan klaim admin pada input untuk memastikan bahwa kelebihan muatan juga menampilkan nilai benar saat klaim ada. Anda akan melihat output berikut:
=== Exercise 4: Customization ===
request.auth.claims.contains('group','admin')
------ input ------
request = time: <
seconds: 1588991010
>
auth: <
principal: "user:me@acme.co"
claims: <
>
>
------ result ------
value: false (types.Bool)
------ input ------
request = time: <
seconds: 1588991010
>
auth: <
principal: "user:me@acme.co"
claims: <
fields: <
key: "group"
value: <
string_value: "admin"
>
>
>
>
------ result ------
value: true (types.Bool)
Sebelum melanjutkan, ada baiknya memeriksa fungsi mapContainsKeyValue
itu sendiri:
// mapContainsKeyValue implements the custom function:
// map.contains(key, value) -> bool.
func mapContainsKeyValue(args ...ref.Val) ref.Val {
// The declaration of the function ensures that only arguments which match
// the mapContainsKey signature will be provided to the function.
m := args[0].(traits.Mapper)
// CEL has many interfaces for dealing with different type abstractions.
// The traits.Mapper interface unifies field presence testing on proto
// messages and maps.
key := args[1]
v, found := m.Find(key)
// If not found and the value was non-nil, the value is an error per the
// `Find` contract. Propagate it accordingly. Such an error might occur with
// a map whose key-type is listed as 'dyn'.
if !found {
if v != nil {
return types.ValOrErr(v, "unsupported key type")
}
// Return CEL False if the key was not found.
return types.False
}
// Otherwise whether the value at the key equals the value provided.
return v.Equal(args[2])
}
Untuk memberikan kemudahan ekstensi, tanda tangan untuk fungsi kustom mengharapkan argumen jenis ref.Val
. Keuntungan di sini adalah kemudahan ekstensi menambah beban pada implementasi untuk memastikan semua jenis nilai ditangani dengan benar. Jika jenis atau jumlah argumen input tidak cocok dengan deklarasi fungsi, error no such overload
akan ditampilkan.
cel.FunctionBinding()
menambahkan guard jenis runtime untuk memastikan bahwa kontrak runtime cocok dengan deklarasi yang telah diperiksa jenisnya di lingkungan.
9. Membuat JSON
CEL juga dapat menghasilkan output non-boolean, seperti JSON. Tambahkan hal berikut ke fungsi Anda:
// exercise5 covers how to build complex objects as CEL literals.
//
// Given the input now, construct a JWT with an expiry of 5 minutes.
func exercise5() {
fmt.Println("=== Exercise 5: Building JSON ===\n")
env, _ := cel.NewEnv(
// Declare the 'now' variable as a Timestamp.
// cel.Variable("now", cel.TimestampType),
)
// Note the quoted keys in the CEL map literal. For proto messages the
// field names are unquoted as they represent well-defined identifiers.
ast := compile(env, `
{'sub': 'serviceAccount:delegate@acme.co',
'aud': 'my-project',
'iss': 'auth.acme.com:12350',
'iat': now,
'nbf': now,
'exp': now + duration('300s'),
'extra_claims': {
'group': 'admin'
}}`,
cel.MapType(cel.StringType, cel.DynType))
program, _ := env.Program(ast)
out, _, _ := eval(
program,
map[string]interface{}{
"now": &tpb.Timestamp{Seconds: time.Now().Unix()},
},
)
fmt.Printf("------ type conversion ------\n%v\n", out)
fmt.Println()
}
Menjalankan kode
Menjalankan kembali kode, Anda akan melihat error berikut:
ERROR: <input>:5:11: undeclared reference to 'now' (in container '')
| 'iat': now,
| ..........^
... and more ...
Tambahkan deklarasi untuk variabel now
dari jenis cel.TimestampType
ke cel.NewEnv()
lalu jalankan lagi:
// exercise5 covers how to build complex objects as CEL literals.
//
// Given the input now, construct a JWT with an expiry of 5 minutes.
func exercise5() {
fmt.Println("=== Exercise 5: Building JSON ===\n")
env, _ := cel.NewEnv(
cel.Variable("now", cel.TimestampType),
)
// Note the quoted keys in the CEL map literal. For proto messages the
// field names are unquoted as they represent well-defined identifiers.
ast := compile(env, `
{'sub': 'serviceAccount:delegate@acme.co',
'aud': 'my-project',
'iss': 'auth.acme.com:12350',
'iat': now,
'nbf': now,
'exp': now + duration('300s'),
'extra_claims': {
'group': 'admin'
}}`,
cel.MapType(cel.StringType, cel.DynType))
// Hint:
// Convert `out` to JSON using the valueToJSON() helper method.
// The valueToJSON() function calls ConvertToNative(&structpb.Value{})
// to adapt the CEL value to a protobuf JSON representation and then
// uses the jsonpb.Marshaler utilities on the output to render the JSON
// string.
program, _ := env.Program(ast)
out, _, _ := eval(
program,
map[string]interface{}{
"now": time.Now(),
},
)
fmt.Printf("------ type conversion ------\n%v\n", out)
fmt.Println()
}
Jalankan ulang kode, dan seharusnya berhasil:
=== Exercise 5: Building JSON ===
{'aud': 'my-project',
'exp': now + duration('300s'),
'extra_claims': {
'group': 'admin'
},
'iat': now,
'iss': 'auth.acme.com:12350',
'nbf': now,
'sub': 'serviceAccount:delegate@acme.co'
}
------ input ------
now = seconds: 1569302377
------ result ------
value: &{0xc0002eaf00 map[aud:my-project exp:{0xc0000973c0} extra_claims:0xc000097400 iat:{0xc000097300} iss:auth.acme.com:12350 nbf:{0xc000097300} sub:serviceAccount:delegate@acme.co] {0x8c8f60 0xc00040a6c0 21}} (*types.baseMap)
------ type conversion ------
&{0xc000313510 map[aud:my-project exp:{0xc000442510} extra_claims:0xc0004acdc0 iat:{0xc000442450} iss:auth.acme.com:12350 nbf:{0xc000442450} sub:serviceAccount:delegate@acme.co] {0x9d0ce0 0xc0004424b0 21}}
Program berjalan, tetapi nilai output out
harus dikonversi secara eksplisit ke JSON. Representasi CEL internal dalam hal ini adalah konvertibel JSON karena hanya merujuk pada jenis yang dapat didukung JSON atau yang sudah dikenal sebagai pemetaan Proto ke JSON.
// exercise5 covers how to build complex objects as CEL literals.
//
// Given the input now, construct a JWT with an expiry of 5 minutes.
func exercise5() {
fmt.Println("=== Exercise 5: Building JSON ===\n")
...
fmt.Printf("------ type conversion ------\n%v\n", valueToJSON(out))
fmt.Println()
}
Setelah jenis tersebut dikonversi menggunakan fungsi bantuan valueToJSON
dalam file codelab.go
, Anda akan melihat output tambahan berikut:
------ type conversion ------
{
"aud": "my-project",
"exp": "2019-10-13T05:54:29Z",
"extra_claims": {
"group": "admin"
},
"iat": "2019-10-13T05:49:29Z",
"iss": "auth.acme.com:12350",
"nbf": "2019-10-13T05:49:29Z",
"sub": "serviceAccount:delegate@acme.co"
}
10. Membuat Proto
CEL dapat membangun pesan protobuf untuk jenis pesan apa pun yang dikompilasi ke dalam aplikasi. Tambahkan fungsi untuk mem-build google.rpc.context.AttributeContext.Request
dari jwt
input
// exercise6 describes how to build proto message types within CEL.
//
// Given an input jwt and time now construct a
// google.rpc.context.AttributeContext.Request with the time and auth
// fields populated according to the go/api-attributes specification.
func exercise6() {
fmt.Println("=== Exercise 6: Building Protos ===\n")
// Construct an environment and indicate that the container for all references
// within the expression is `google.rpc.context.AttributeContext`.
requestType := &rpcpb.AttributeContext_Request{}
env, _ := cel.NewEnv(
// Add cel.Container() option for 'google.rpc.context.AttributeContext'
cel.Types(requestType),
// Add cel.Variable() option for 'jwt' as a map(string, Dyn) type
// and for 'now' as a timestamp.
)
// Compile the Request message construction expression and validate that
// the resulting expression type matches the fully qualified message name.
//
// Note: the field names within the proto message types are not quoted as they
// are well-defined names composed of valid identifier characters. Also, note
// that when building nested proto objects, the message name needs to prefix
// the object construction.
ast := compile(env, `
Request{
auth: Auth{
principal: jwt.iss + '/' + jwt.sub,
audiences: [jwt.aud],
presenter: 'azp' in jwt ? jwt.azp : "",
claims: jwt
},
time: now
}`,
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
)
program, _ := env.Program(ast)
// Construct the message. The result is a ref.Val that returns a dynamic
// proto message.
out, _, _ := eval(
program,
map[string]interface{}{
"jwt": map[string]interface{}{
"sub": "serviceAccount:delegate@acme.co",
"aud": "my-project",
"iss": "auth.acme.com:12350",
"extra_claims": map[string]string{
"group": "admin",
},
},
"now": time.Now(),
},
)
// Hint: Unwrap the CEL value to a proto. Make sure to use the
// `ConvertToNative(reflect.TypeOf(requestType))` to convert the dynamic proto
// message to the concrete proto message type expected.
fmt.Printf("------ type unwrap ------\n%v\n", out)
fmt.Println()
}
Menjalankan kode
Menjalankan kembali kode, Anda akan melihat error berikut:
ERROR: <input>:2:10: undeclared reference to 'Request' (in container '')
| Request{
| .........^
Container ini pada dasarnya setara dengan namespace atau paket, tetapi bahkan dapat sedetail nama pesan protobuf. Penampung CEL menggunakan aturan resolusi namespace yang sama seperti Protobuf dan C++ untuk menentukan tempat variabel, fungsi, atau nama jenis tertentu dideklarasikan.
Dengan penampung google.rpc.context.AttributeContext
, pemeriksa jenis dan evaluator akan mencoba nama ID berikut untuk semua variabel, jenis, dan fungsi:
google.rpc.context.AttributeContext.<id>
google.rpc.context.<id>
google.rpc.<id>
google.<id>
<id>
Untuk nama absolut, beri awalan pada variabel, jenis, atau referensi fungsi dengan titik di awal. Dalam contoh, ekspresi .<id>
hanya akan menelusuri ID <id>
tingkat teratas tanpa memeriksa dalam penampung terlebih dahulu.
Coba tentukan opsi cel.Container("google.rpc.context.AttributeContext")
untuk lingkungan CEL dan jalankan lagi:
// exercise6 describes how to build proto message types within CEL.
//
// Given an input jwt and time now construct a
// google.rpc.context.AttributeContext.Request with the time and auth
// fields populated according to the go/api-attributes specification.
func exercise6() {
fmt.Println("=== Exercise 6: Building Protos ===\n")
// Construct an environment and indicate that the container for all references
// within the expression is `google.rpc.context.AttributeContext`.
requestType := &rpcpb.AttributeContext_Request{}
env, _ := cel.NewEnv(
// Add cel.Container() option for 'google.rpc.context.AttributeContext'
cel.Container("google.rpc.context.AttributeContext.Request"),
cel.Types(requestType),
// Later, add cel.Variable() options for 'jwt' as a map(string, Dyn) type
// and for 'now' as a timestamp.
// cel.Variable("now", cel.TimestampType),
// cel.Variable("jwt", cel.MapType(cel.StringType, cel.DynType)),
)
...
}
Output Anda akan terlihat seperti berikut:
ERROR: <input>:4:16: undeclared reference to 'jwt' (in container 'google.rpc.context.AttributeContext')
| principal: jwt.iss + '/' + jwt.sub,
| ...............^
... dan masih banyak error lagi...
Selanjutnya, deklarasikan variabel jwt
dan now
, lalu program akan berjalan seperti yang diharapkan:
=== Exercise 6: Building Protos ===
Request{
auth: Auth{
principal: jwt.iss + '/' + jwt.sub,
audiences: [jwt.aud],
presenter: 'azp' in jwt ? jwt.azp : "",
claims: jwt
},
time: now
}
------ input ------
jwt = {
"aud": "my-project",
"extra_claims": {
"group": "admin"
},
"iss": "auth.acme.com:12350",
"sub": "serviceAccount:delegate@acme.co"
}
now = seconds: 1588993027
------ result ------
value: &{0xc000485270 0xc000274180 {0xa6ee80 0xc000274180 22} 0xc0004be140 0xc0002bf700 false} (*types.protoObj)
----- type unwrap ----
&{0xc000485270 0xc000274180 {0xa6ee80 0xc000274180 22} 0xc0004be140 0xc0002bf700 false}
Untuk kredit tambahan, Anda juga harus membuka jenis dengan memanggil out.Value()
pada pesan untuk melihat bagaimana hasilnya berubah.
11. Makro
Makro dapat digunakan untuk memanipulasi program CEL pada waktu penguraian. Makro mencocokkan tanda tangan panggilan dan memanipulasi panggilan input dan argumennya guna menghasilkan AST subekspresi baru.
Makro dapat digunakan untuk menerapkan logika kompleks dalam AST yang tidak dapat ditulis secara langsung dalam CEL. Misalnya, makro has
memungkinkan pengujian kehadiran kolom. Makro pemahaman seperti ada dan semuanya menggantikan panggilan fungsi dengan iterasi terbatas pada daftar input atau peta. Kedua konsep tidak mungkin dilakukan pada tingkat sintaksis, tetapi keduanya dimungkinkan melalui perluasan makro.
Tambahkan dan jalankan latihan berikutnya:
// exercise7 introduces macros for dealing with repeated fields and maps.
//
// Determine whether the jwt.extra_claims has at least one key that starts
// with the group prefix, and ensure that all group-like keys have list
// values containing only strings that end with '@acme.co`.
func exercise7() {
fmt.Println("=== Exercise 7: Macros ===\n")
env, _ := cel.NewEnv(
cel.ClearMacros(),
cel.Variable("jwt", cel.MapType(cel.StringType, cel.DynType)),
)
ast := compile(env,
`jwt.extra_claims.exists(c, c.startsWith('group'))
&& jwt.extra_claims
.filter(c, c.startsWith('group'))
.all(c, jwt.extra_claims[c]
.all(g, g.endsWith('@acme.co')))`,
cel.BoolType)
program, _ := env.Program(ast)
// Evaluate a complex-ish JWT with two groups that satisfy the criteria.
// Output: true.
eval(program,
map[string]interface{}{
"jwt": map[string]interface{}{
"sub": "serviceAccount:delegate@acme.co",
"aud": "my-project",
"iss": "auth.acme.com:12350",
"extra_claims": map[string][]string{
"group1": {"admin@acme.co", "analyst@acme.co"},
"labels": {"metadata", "prod", "pii"},
"groupN": {"forever@acme.co"},
},
},
})
fmt.Println()
}
Anda akan melihat error berikut:
ERROR: <input>:1:25: undeclared reference to 'c' (in container '')
| jwt.extra_claims.exists(c, c.startsWith('group'))
| ........................^
... dan banyak lagi ...
Error ini terjadi karena makro belum diaktifkan. Untuk mengaktifkan makro, hapus cel.ClearMacros()
, dan jalankan lagi:
=== Exercise 7: Macros ===
jwt.extra_claims.exists(c, c.startsWith('group'))
&& jwt.extra_claims
.filter(c, c.startsWith('group'))
.all(c, jwt.extra_claims[c]
.all(g, g.endsWith('@acme.co')))
------ input ------
jwt = {
"aud": "my-project",
"extra_claims": {
"group1": [
"admin@acme.co",
"analyst@acme.co"
],
"groupN": [
"forever@acme.co"
],
"labels": [
"metadata",
"prod",
"pii"
]
},
"iss": "auth.acme.com:12350",
"sub": "serviceAccount:delegate@acme.co"
}
------ result ------
value: true (types.Bool)
Berikut adalah makro yang saat ini didukung:
Macro | Tanda Tangan | Deskripsi |
semua | r.all(var; cond) | Uji apakah cond mengevaluasi benar untuk semua var dalam rentang r. |
ada | r.exists(var, cond) | Uji apakah cond mengevaluasi true untuk apa pun var dalam rentang r. |
exists_one | r.exists_one(var, cond) | Uji apakah cond bernilai benar untuk hanya satu var dalam rentang r. |
filter | r.filter(var; cond) | Untuk daftar, buat daftar baru dengan setiap var elemen dalam rentang r memenuhi kondisi kondisi. Untuk peta, buat daftar baru dengan setiap var kunci dalam rentang r memenuhi kondisi kondisi. |
peta | r.map(var; expr) | Buat daftar baru dengan setiap var dalam rentang r ditransformasi oleh expr. |
r.map(var, cond, expr) | Sama seperti peta two-arg, tetapi dengan filter cond kondisional sebelum nilai diubah. | |
berisi | memiliki(a.b) | Uji kehadiran b pada nilai a : Untuk peta, definisi pengujian json. Untuk proto, menguji nilai primitif non-default atau kolom pesan yang telah ditetapkan. |
Jika argumen rentang r adalah jenis map
, var
akan menjadi kunci peta, dan untuk nilai jenis list
, var
akan menjadi nilai elemen daftar. Makro all
, exists
, exists_one
, filter
, dan map
melakukan penulisan ulang AST yang melakukan setiap iterasi yang dibatasi oleh ukuran input.
Pemahaman terbatas memastikan bahwa program CEL tidak akan menyelesaikan Turing, tetapi melakukan evaluasi dalam waktu super-linear sehubungan dengan input. Gunakan makro ini seperlunya atau tidak sama sekali. Penggunaan pemahaman yang berlebihan biasanya merupakan indikator yang baik bahwa fungsi kustom akan memberikan pengalaman pengguna dan performa yang lebih baik.
12. Tuning
Ada beberapa fitur yang saat ini eksklusif untuk CEL-Go, tetapi merupakan indikasi rencana mendatang untuk implementasi CEL lainnya. Latihan berikut menampilkan berbagai rencana program untuk AST yang sama:
// exercise8 covers features of CEL-Go which can be used to improve
// performance and debug evaluation behavior.
//
// Turn on the optimization, exhaustive eval, and state tracking
// ProgramOption flags to see the impact on evaluation behavior.
func exercise8() {
fmt.Println("=== Exercise 8: Tuning ===\n")
// Declare the x and 'y' variables as input into the expression.
env, _ := cel.NewEnv(
cel.Variable("x", cel.IntType),
cel.Variable("y", cel.UintType),
)
ast := compile(env,
`x in [1, 2, 3, 4, 5] && type(y) == uint`,
cel.BoolType)
// Try the different cel.EvalOptions flags when evaluating this AST for
// the following use cases:
// - cel.OptOptimize: optimize the expression performance.
// - cel.OptExhaustiveEval: turn off short-circuiting.
// - cel.OptTrackState: track state and compute a residual using the
// interpreter.PruneAst function.
program, _ := env.Program(ast)
eval(program, cel.NoVars())
fmt.Println()
}
Optimize
Jika tanda pengoptimalan diaktifkan, CEL akan meluangkan waktu ekstra untuk membuat literal daftar dan peta terlebih dahulu serta mengoptimalkan panggilan tertentu seperti operator in agar menjadi pengujian keanggotaan set benar:
// Turn on optimization.
trueVars := map[string]interface{}{"x": int64(4), "y": uint64(2)}
program, _ := env.Program(ast, cel.EvalOptions(cel.OptOptimize))
// Try benchmarking with the optimization flag on and off.
eval(program, trueVars)
Jika program yang sama dievaluasi berkali-kali terhadap input yang berbeda, pengoptimalan adalah pilihan yang baik. Namun, ketika program hanya akan dievaluasi sekali, pengoptimalan hanya akan menambah overhead.
Uji Coba Gratis
Exhaustive Eval dapat berguna untuk men-debug perilaku evaluasi ekspresi karena memberikan insight tentang nilai yang diamati pada setiap langkah dalam evaluasi ekspresi.
// Turn on exhaustive eval to see what the evaluation state looks like.
// The input is structure to show a false on the first branch, and true
// on the second.
falseVars := map[string]interface{}{"x": int64(6), "y": uint64(2)}
program, _ = env.Program(ast, cel.EvalOptions(cel.OptExhaustiveEval))
eval(program, falseVars)
Anda akan melihat daftar status evaluasi ekspresi untuk setiap ID ekspresi:
------ eval states ------
1: 6 (types.Int)
2: false (types.Bool)
3: &{0xc0000336b0 [1 2 3 4 5] {0x89f020 0xc000434760 151}} (*types.baseList)
4: 1 (types.Int)
5: 2 (types.Int)
6: 3 (types.Int)
7: 4 (types.Int)
8: 5 (types.Int)
9: uint (*types.Type)
10: 2 (types.Uint)
11: true (types.Bool)
12: uint (*types.Type)
13: false (types.Bool)
ID ekspresi 2 sesuai dengan hasil operator in di angka pertama. cabang, dan id ekspresi 11 sesuai dengan operator == di detik. Dalam evaluasi normal, ekspresi akan mengalami hubungan arus pendek setelah 2 dihitung. Jika y bukan uint, status akan menunjukkan dua alasan mengapa ekspresi akan gagal dan bukan hanya satu.
13. Apa saja yang telah dibahas?
Jika Anda memerlukan mesin ekspresi, pertimbangkan untuk menggunakan CEL. CEL ideal untuk project yang perlu menjalankan konfigurasi pengguna yang mengutamakan performa.
Pada latihan sebelumnya, kami berharap Anda merasa nyaman meneruskan data ke CEL dan mengeluarkan output atau keputusan.
Kami harap Anda memiliki pemahaman tentang jenis operasi yang dapat Anda lakukan, mulai dari keputusan boolean hingga menghasilkan pesan JSON dan Protobuffer.
Kami harap Anda memahami cara menggunakan ekspresi tersebut, dan apa yang dilakukannya. Selain itu, kami memahami cara umum untuk memperluasnya.