1. Giriş
CEL nedir?
CEL, hızlı, taşınabilir ve güvenli bir şekilde yürütülecek şekilde tasarlanmış, Turing tamamlanmamış bir ifade dilidir. CEL tek başına kullanılabilir veya daha büyük bir ürüne yerleştirilebilir.
CEL, kullanıcı kodunun güvenli bir şekilde yürütülebileceği bir dil olarak tasarlanmıştır. Kullanıcının Python kodunda eval() işlevini körü körüne çağırmak tehlikeli olsa da kullanıcının CEL kodunu güvenli bir şekilde yürütebilirsiniz. CEL, daha düşük performanslı olmasına neden olacak davranışları engellediği için nanosaniyelerden mikrosaniyelere kadar güvenli bir şekilde değerlendirme yapar ve performansı kritik olan uygulamalar için idealdir.
CEL, tek satırlık işlevlere veya lambda ifadelerine benzeyen ifadeleri değerlendirir. CEL genellikle Boole kararlar için kullanılsa da JSON veya protobuf mesajları gibi daha karmaşık nesneler oluşturmak için de kullanılabilir.
CEL, projeniz için doğru seçenek mi?
CEL, bir ifadeyi AST'den nanosaniye ile mikrosaniye arasında değerlendirdiğinden CEL'in ideal kullanım alanı, performans açısından kritik yollara sahip uygulamalardır. CEL kodunun AST'ye derlenmesi kritik yollarda yapılmamalıdır. İdeal uygulamalar, yapılandırmanın sık sık yürütüldüğü ve nispeten seyrek olarak değiştirildiği uygulamalardır.
Örneğin, bir hizmete yapılan her HTTP isteğiyle bir güvenlik politikasının yürütülmesi, CEL için ideal bir kullanım alanıdır. Çünkü güvenlik politikası nadiren değişir ve CEL, yanıt süresini ihmal edilebilir düzeyde etkiler. Bu durumda CEL, isteğin izin verilip verilmeyeceğini belirten bir boole değeri döndürür ancak daha karmaşık bir mesaj da döndürebilir.
Bu Codelab'de hangi konular ele alınmaktadır?
Bu codelab'in ilk adımında, CEL kullanmanın nedenleri ve Temel Kavramları açıklanmaktadır. Geri kalan kısımda, yaygın kullanım alanlarını kapsayan kodlama alıştırmaları yer alır. Dil, semantik ve özellikler hakkında daha ayrıntılı bilgi için GitHub'daki CEL Dil Tanımı ve CEL Go Dokümanları'na bakın.
Bu codelab, CEL'i destekleyen hizmetleri kullanmak için CEL'i öğrenmek isteyen geliştiricilere yöneliktir. Bu codelab'de CEL'yi kendi projenize nasıl entegre edeceğiniz açıklanmamaktadır.
Neler öğreneceksiniz?
- CEL'deki temel kavramlar
- Merhaba Dünya: Bir dizeyi değerlendirmek için CEL kullanma
- Değişken oluşturma
- CEL'nin mantıksal VE/VEYA işlemlerinde kısa devre yapmasını anlama
- JSON oluşturmak için CEL'yi kullanma
- CEL'yi kullanarak Protobuffer'lar oluşturma
- Makro Oluşturma
- CEL ifadelerinizi ayarlama yöntemleri
Gerekenler
Ön koşullar
Bu kod laboratuvarı, Protocol Buffers ve Go Lang ile ilgili temel düzeyde bilgi sahibi olunduğu varsayılarak hazırlanmıştır.
Protocol Buffers'ı bilmiyorsanız ilk alıştırma, CEL'in nasıl çalıştığı hakkında fikir verecektir. Ancak daha gelişmiş örneklerde CEL'e giriş olarak Protocol Buffers kullanıldığından bu örnekleri anlamak daha zor olabilir. Öncelikle bu eğitimlerden birini tamamlamanızı öneririz. CEL'i kullanmak için Protocol Buffers'ın gerekli olmadığını ancak bu codelab'de yoğun olarak kullanıldığını unutmayın.
Aşağıdaki komutu çalıştırarak Go'nun yüklendiğini test edebilirsiniz:
go --help
2. Temel kavramlar
Uygulamalar
CEL, genel amaçlıdır ve RPC'leri yönlendirmekten güvenlik politikası tanımlamaya kadar çeşitli uygulamalarda kullanılmıştır. CEL genişletilebilir, uygulamadan bağımsızdır ve bir kez derleme, çok kez değerlendirme iş akışları için optimize edilmiştir.
Birçok hizmet ve uygulama, bildirim temelli yapılandırmaları değerlendirir. Örneğin, rol tabanlı erişim denetimi (RBAC), bir rol ve bir kullanıcı grubu verildiğinde erişim kararı veren bildirimsel bir yapılandırmadır. Bildirim temelli yapılandırmalar% 80 kullanım alanını kapsıyorsa CEL, kullanıcıların daha fazla ifade gücüne ihtiyaç duyduğu durumlarda kalan% 20'yi tamamlamak için yararlı bir araçtır.
Derleme
Bir ifade, bir ortama göre derlenir. Derleme adımında, protobuf biçiminde bir Soyut Sözdizimi Ağacı (AST) oluşturulur. Derlenmiş ifadeler, değerlendirmenin olabildiğince hızlı olması için genellikle gelecekte kullanılmak üzere saklanır. Tek bir derlenmiş ifade, birçok farklı girişle değerlendirilebilir.
İfadeler
Kullanıcılar ifadeleri tanımlar, hizmetler ve uygulamalar ise ifadenin çalışacağı ortamı tanımlar. İşlev imzası, girişleri bildirir ve CEL ifadesinin dışında yazılır. CEL'de kullanılabilen işlev kitaplığı otomatik olarak içe aktarılır.
Aşağıdaki örnekte ifade bir istek nesnesi alır ve istek bir talepler jetonu içerir. İfade, hak talebi jetonunun hâlâ geçerli olup olmadığını belirten bir boole değeri döndürür.
// 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
Ortam
Ortamlar hizmetlere göre tanımlanır. CEL'yi yerleştiren hizmetler ve uygulamalar, ifade ortamını bildirir. Ortam, ifadelerde kullanılabilen değişken ve işlevler topluluğudur.
Proto tabanlı bildirimler, bir ifadedeki tüm tanımlayıcı ve işlev referanslarının doğru şekilde bildirilip kullanıldığından emin olmak için CEL türü denetleyicisi tarafından kullanılır.
Bir ifadeyi ayrıştırmanın üç aşaması
İfade işleme üç aşamadan oluşur: ayrıştırma, kontrol etme ve değerlendirme. CEL için en yaygın kalıp, bir kontrol düzleminin yapılandırma sırasında ifadeleri ayrıştırıp kontrol etmesi ve AST'yi depolamasıdır.

Çalışma zamanında veri düzlemi, AST'yi tekrar tekrar alır ve değerlendirir. CEL, çalışma zamanı verimliliği için optimize edilmiştir ancak ayrıştırma ve kontrol, gecikmenin kritik olduğu kod yollarında yapılmamalıdır.

CEL, ANTLR sözcük ayrıştırıcı / ayrıştırma grameri kullanılarak okunabilir bir ifadeden soyut söz dizimi ağacına ayrıştırılır. Ayrıştırma aşamasında, AST'deki her Expr düğümünün ayrıştırma ve kontrol sırasında oluşturulan meta verilere dizin oluşturmak için kullanılan bir tam sayı kimliği içerdiği proto tabanlı bir soyut söz dizimi ağacı oluşturulur. Ayrıştırma sırasında oluşturulan syntax.proto, ifadenin dize biçiminde yazılanların soyut gösterimini doğru şekilde temsil eder.
Bir ifade ayrıştırıldıktan sonra, ifadedeki tüm değişken ve işlev tanımlayıcılarının bildirilmiş ve doğru şekilde kullanıldığından emin olmak için ortama göre kontrol edilebilir. Tür denetleyicisi, değerlendirme verimliliğini önemli ölçüde artırabilecek tür, değişken ve işlev çözümleme meta verilerini içeren bir checked.proto oluşturur.
CEL değerlendiricinin 3 şeye ihtiyacı vardır:
- Tüm özel uzantılar için işlev bağlamaları
- Değişken bağlamalar
- Değerlendirilecek bir AST
İşlev ve değişken bağlamaları, AST'yi derlemek için kullanılanlarla eşleşmelidir. Bu girişlerin herhangi biri, birden fazla değerlendirmede yeniden kullanılabilir. Örneğin, bir AST birçok değişken bağlama kümesinde değerlendirilebilir, aynı değişkenler birçok AST'ye karşı kullanılabilir veya işlev bağlamaları bir sürecin ömrü boyunca kullanılabilir (yaygın bir durum).
3. Kur
Bu codelab'in kodu, cel-go deposunun codelab klasöründe yer alır. Çözüm, aynı depodaki codelab/solution klasöründe bulunur.
Depoyu klonlayın ve depoya gidin:
git clone https://github.com/google/cel-go.git
cd cel-go/codelab
go run kullanarak kodu çalıştırın:
go run .
Aşağıdaki çıkışı göreceksiniz:
=== 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 ===
CEL paketleri nerede?
Düzenleyicinizde codelab/codelab.go simgesini açın. Bu codelab'deki alıştırmaların yürütülmesini sağlayan ana işlevin ardından üç yardımcı işlev bloğu görmelisiniz. İlk yardımcı grubu, CEL değerlendirmesinin aşamalarında yardımcı olur:
Compileişlevi: Giriş ifadesini ayrıştırır, kontrol eder ve bir ortama göre kontrol eder.Evalişlevi: Derlenmiş bir programı girişe göre değerlendirir.Reportişlevi: Değerlendirme sonucunu güzel bir şekilde yazdırır.
Ayrıca, çeşitli alıştırmalarda giriş oluşturmaya yardımcı olmak için request ve auth yardımcıları sağlanmıştır.
Alıştırmalarda paketler, kısa paket adlarıyla ifade edilir. Ayrıntıları incelemek isterseniz google/cel-go deposundaki paketten kaynak konumuna eşlemeyi aşağıda bulabilirsiniz:
Paket | Kaynak Konumu | Açıklama |
cel | Üst düzey arayüzler | |
ref | Referans arayüzler | |
türler | Çalışma zamanı türü değerleri |
4. Merhaba Dünya!
Tüm programlama dillerinde olduğu gibi, "Hello World!" oluşturup değerlendirerek başlayacağız.
Ortamı yapılandırma
Düzenleyicinizde exercise1 bildirimini bulun ve ortamı ayarlamak için aşağıdakileri doldurun:
// 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
}
CEL uygulamaları, bir ifadeyi bir ortama göre değerlendirir. env, err := cel.NewEnv(), standart ortamı yapılandırır.
Ortam, cel.EnvOption seçenekleri çağrıya iletilerek özelleştirilebilir. Bu seçenekler, makroları devre dışı bırakabilir, özel değişkenleri ve işlevleri tanımlayabilir vb.
Standart CEL ortamı, dil spesifikasyonunda tanımlanan tüm türleri, operatörleri, işlevleri ve makroları destekler.
İfadeyi ayrıştırıp kontrol etme
Ortam yapılandırıldıktan sonra ifadeler ayrıştırılıp kontrol edilebilir. İşlevinize aşağıdakileri ekleyin:
// 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
}
Parse ve Check çağrıları tarafından döndürülen iss değeri, hata olabilecek sorunların listesidir. iss.Err() değeri sıfır değilse söz diziminde veya semantikte hata vardır ve program daha fazla devam edemez. İfade iyi biçimlendirilmişse bu çağrıların sonucu yürütülebilir bir cel.Ast olur.
İfadeyi değerlendirme
İfade ayrıştırılıp cel.Ast içine eklendikten sonra, işlev bağlamaları ve değerlendirme modları işlevsel seçeneklerle özelleştirilebilen, değerlendirilebilir bir programa dönüştürülebilir. Ayrıca, cel.CheckedExprToAst veya cel.ParsedExprToAst işlevlerini kullanarak bir proto'dan cel.Ast okumanın da mümkün olduğunu unutmayın.
Planlanan bir cel.Program, Eval çağrılarak girişlere göre değerlendirilebilir. Eval sonucu; sonucu, değerlendirme ayrıntılarını ve hata durumunu içerir.
Plan ekleme ve eval ile görüşme:
// 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()
}
Kısa olması için yukarıda yer alan hata durumlarını gelecekteki alıştırmalara dahil etmeyeceğiz.
Kodu yürütme
Komut satırında kodu yeniden çalıştırın:
go run .
Gelecekteki alıştırmalar için yer tutucularla birlikte aşağıdaki çıkışı görmelisiniz.
=== Exercise 1: Hello World ===
------ input ------
(interpreter.emptyActivation)
------ result ------
value: Hello, World! (types.String)
5. İşlevlerde değişken kullanma
Çoğu CEL uygulaması, ifadelerde referans verilebilecek değişkenler bildirir. Değişken bildirimleri bir ad ve tür belirtir. Bir değişkenin türü CEL yerleşik türü, iyi bilinen bir protokol arabelleği türü veya tanımlayıcısı CEL'ye de sağlandığı sürece herhangi bir protobuf mesaj türü olabilir.
İşlevi ekleme
Düzenleyicinizde exercise2 bildirimini bulun ve aşağıdakileri ekleyin:
// 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()
}
Hata hakkında daha fazla bilgi edinmek için yeniden çalıştırma
Programı yeniden çalıştırın:
go run .
Aşağıdaki çıkışı göreceksiniz:
ERROR: <input>:1:1: undeclared reference to 'request' (in container '')
| request.auth.claims.group == 'admin'
| ^
Tür denetleyicisi, istek nesnesi için bir hata oluşturur. Bu hata, hatanın oluştuğu kaynak snippet'ini de içerir.
6. Değişkenleri tanımlama
EnvOptions ekle
Düzenleyicide, istek nesnesi için google.rpc.context.AttributeContext.Request türünde bir mesaj olarak aşağıdaki gibi bir bildirim sağlayarak ortaya çıkan hatayı düzeltelim:
// 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()
}
Hata hakkında daha fazla bilgi edinmek için yeniden çalıştırma
Programı tekrar çalıştırma:
go run .
Aşağıdaki hatayı görmeniz gerekir:
ERROR: <input>:1:8: [internal] unexpected failed resolution of 'google.rpc.context.AttributeContext.Request'
| request.auth.claims.group == 'admin'
| .......^
protobuf mesajlarına atıfta bulunan değişkenleri kullanmak için tür denetleyicinin tür tanımlayıcıyı da bilmesi gerekir.
İşlevinizdeki isteğin tanımlayıcısını belirlemek için cel.Types kullanın:
// 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()
}
Yeniden çalıştırma başarılı!
Programı tekrar çalıştırın:
go run .
Aşağıdaki ekranı görmeniz gerekir:
=== 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)
Özetlemek gerekirse, hata için bir değişken tanımladık, buna bir tür tanımlayıcı atadık ve ardından değişkeni ifade değerlendirmesinde referans olarak kullandık.
7. Mantıksal VE/VEYA
CEL'in daha benzersiz özelliklerinden biri, değişme özelliği olan mantıksal operatörleri kullanmasıdır. Koşullu bir dalın her iki tarafı da hatalar veya kısmi giriş olsa bile değerlendirmeyi kısa devre edebilir.
Diğer bir deyişle CEL, mümkün olduğunda sonuç veren bir değerlendirme sırası bulur ve diğer değerlendirme sıralarında oluşabilecek hataları veya eksik verileri yoksayar. Uygulamalar, değerlendirme maliyetini en aza indirmek için bu özellikten yararlanabilir. Bu sayede, sonuç elde etmek için gerekli olmayan pahalı girişlerin toplanması ertelenebilir.
Bir VE/VEYA örneği ekleyip CEL'nin kısa devre değerlendirmesini nasıl yaptığını anlamak için farklı girişlerle deneyeceğiz.
İşlevi oluşturma
Düzenleyicinizde, 3. alıştırmaya aşağıdaki içeriği ekleyin:
// 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"),
),
)
}
Ardından, kullanıcı admin grubunun üyesiyse veya belirli bir e-posta tanımlayıcısına sahipse doğru değerini döndüren şu VEYA ifadesini ekleyin:
// 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)
}
Son olarak, kullanıcıyı boş talep grubuyla değerlendiren eval durumunu ekleyin:
// 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()))
}
Kodu boş talep grubuyla çalıştırın.
Programı yeniden çalıştırdığınızda aşağıdaki yeni çıkışı görürsünüz:
=== 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)
Değerlendirme kaydını güncelleme
Ardından, değerlendirme kaydını boş talep grubuyla farklı bir asıl geçirecek şekilde güncelleyin:
// 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()))
}
Kodu belirli bir zamanda çalıştırma
Programı yeniden çalıştırma,
go run .
şu hatayı görürsünüz:
=== 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
Protobuf'ta hangi alanların ve türlerin bekleneceğini biliriz. Harita ve JSON değerlerinde bir anahtarın mevcut olup olmayacağını bilmiyoruz. Eksik bir anahtar için güvenli bir varsayılan değer olmadığından CEL, varsayılan olarak hataya neden olur.
8. Özel İşlevler
CEL'de birçok yerleşik işlev olsa da özel işlevlerin yararlı olduğu durumlar vardır. Örneğin, özel işlevler yaygın koşullarda kullanıcı deneyimini iyileştirmek veya bağlama duyarlı durumu ortaya çıkarmak için kullanılabilir.
Bu alıştırmada, en çok tercih edilen kontrolleri birlikte paketlemek için bir işlevin nasıl kullanıma sunulacağını keşfedeceğiz.
Özel bir işlevi çağırma
Öncelikle, bir anahtarın haritada bulunup bulunmadığını ve belirli bir değere sahip olup olmadığını belirleyen contains adlı bir geçersiz kılma oluşturma kodunu oluşturun. İşlev tanımı ve işlev bağlama için yer tutucular bırakın:
// 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()
}
Kodu çalıştırın ve hatayı anlayın
Kodu yeniden çalıştırdığınızda aşağıdaki hatayı görmeniz gerekir:
ERROR: <input>:1:29: found no matching overload for 'contains' applied to 'map(string, dyn).(string, string)'
| request.auth.claims.contains('group', 'admin')
| ............................^
Hatayı düzeltmek için, şu anda istek değişkenini bildiren bildirimler listesine contains işlevini eklememiz gerekiyor.
Aşağıdaki 3 satırı ekleyerek parametreli bir tür bildirin. (Bu, CEL için herhangi bir işlev aşırı yüklemesi kadar karmaşıktır.)
// 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()
}
Özel işlevi ekleme
Ardından, parametreli türleri kullanacak yeni bir contains işlevi ekleyeceğiz:
// 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()
}
Hatayı anlamak için programı çalıştırın
Egzersizi çalıştırın. Eksik çalışma zamanı işleviyle ilgili aşağıdaki hatayı görmeniz gerekir:
------ result ------
error: no such overload: contains
cel.FunctionBinding() işlevini kullanarak NewEnv beyanına işlev uygulamasını sağlayın:
// 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()
}
Program artık başarıyla çalıştırılmalıdır:
=== 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)
Hak talebi varsa ne olur?
Ekstra kredi için, giriş üzerinde yönetici talebini ayarlayarak talebin mevcut olduğu durumlarda da içerik aşırı yüklemesinin doğru döndürdüğünü doğrulayın. Aşağıdaki çıkışı göreceksiniz:
=== 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)
Devam etmeden önce mapContainsKeyValue işlevini incelemeniz önerilir:
// 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])
}
Özel işlevlerin imzası, en kolay şekilde genişletilebilmesi için ref.Val türündeki bağımsız değişkenleri bekler. Buradaki dezavantaj, uzantının kolaylığının, tüm değer türlerinin düzgün şekilde işlenmesini sağlamak için uygulayıcıya yük getirmesidir. Giriş bağımsız değişkeni türleri veya sayısı işlev bildirimiyle eşleşmediğinde no such overload hatası döndürülmelidir.
cel.FunctionBinding(), çalışma zamanı sözleşmesinin ortamdaki türü kontrol edilmiş bildirimle eşleşmesini sağlamak için çalışma zamanı türü koruması ekler.
9. JSON oluşturma
CEL, JSON gibi boolean olmayan çıkışlar da üretebilir. İşlevinize aşağıdakileri ekleyin:
// 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()
}
Kodu çalıştırma
Kodu yeniden çalıştırdığınızda aşağıdaki hatayı görmeniz gerekir:
ERROR: <input>:5:11: undeclared reference to 'now' (in container '')
| 'iat': now,
| ..........^
... and more ...
now türündeki cel.TimestampType değişkeni için cel.NewEnv() öğesine bir bildirim ekleyin ve tekrar çalıştırın:
// 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()
}
Kodu tekrar çalıştırın. Bu kez başarılı olacaktır:
=== 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 çalışır ancak çıktı değeri out açıkça JSON'a dönüştürülmelidir. Bu durumda dahili CEL gösterimi, yalnızca JSON'ın destekleyebileceği veya iyi bilinen bir Proto-JSON eşlemesinin olduğu türlere başvurduğu için JSON'a dönüştürülebilir.
// 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()
}
Tür, codelab.go dosyasındaki valueToJSON yardımcı işlevi kullanılarak dönüştürüldüğünde aşağıdaki ek çıkışı görürsünüz:
------ 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. Prototip Oluşturma
CEL, uygulamaya derlenen tüm ileti türleri için protobuf iletileri oluşturabilir. Bir giriş jwt değerinden google.rpc.context.AttributeContext.Request oluşturmak için işlevi ekleyin.
// 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()
}
Kodu çalıştırma
Kodu yeniden çalıştırdığınızda aşağıdaki hatayı görmeniz gerekir:
ERROR: <input>:2:10: undeclared reference to 'Request' (in container '')
| Request{
| .........^
Kapsayıcı, temel olarak bir ad alanına veya pakete eşdeğerdir ancak protobuf mesaj adı kadar ayrıntılı olabilir. CEL kapsayıcıları, belirli bir değişkenin, işlevin veya tür adının nerede tanımlandığını belirlemek için Protobuf ve C++ ile aynı ad alanı çözümleme kurallarını kullanır.
google.rpc.context.AttributeContext kapsayıcısı verildiğinde tür denetleyicisi ve değerlendirici, tüm değişkenler, türler ve işlevler için aşağıdaki tanımlayıcı adlarını dener:
google.rpc.context.AttributeContext.<id>google.rpc.context.<id>google.rpc.<id>google.<id><id>
Mutlak adlar için değişken, tür veya işlev referansının önüne nokta koyun. Örnekte, .<id> ifadesi önce kapsayıcı içinde kontrol etmeden yalnızca üst düzey <id> tanımlayıcısını arar.
CEL ortamı için cel.Container("google.rpc.context.AttributeContext") seçeneğini belirtip tekrar çalıştırmayı deneyin:
// 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)),
)
...
}
Çıkış şu şekilde olmalıdır:
ERROR: <input>:4:16: undeclared reference to 'jwt' (in container 'google.rpc.context.AttributeContext')
| principal: jwt.iss + '/' + jwt.sub,
| ...............^
... ve daha birçok hata...
Ardından, jwt ve now değişkenlerini tanımlayın. Program beklendiği gibi çalışır:
=== 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}
Ek olarak, sonucu nasıl değiştirdiğini görmek için iletide out.Value() işlevini çağırarak türü sarmalından çıkarmanız gerekir.
11. Makrolar
Makrolar, ayrıştırma sırasında CEL programını değiştirmek için kullanılabilir. Makrolar, bir çağrı imzasıyla eşleşir ve yeni bir alt ifade AST'si oluşturmak için giriş çağrısını ve bağımsız değişkenlerini değiştirir.
Makrolar, doğrudan CEL'de yazılamayan karmaşık mantığı AST'de uygulamak için kullanılabilir. Örneğin, has makrosu alan varlığı testini etkinleştirir. exists ve all gibi anlama makroları, bir işlev çağrısını giriş listesi veya haritası üzerinde sınırlı yinelemeyle değiştirir. Bu kavramların ikisi de söz dizimi düzeyinde mümkün değildir ancak makro genişletmeleriyle mümkündür.
Sonraki egzersizi ekleyip çalıştırın:
// 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()
}
Aşağıdaki hataları görmeniz gerekir:
ERROR: <input>:1:25: undeclared reference to 'c' (in container '')
| jwt.extra_claims.exists(c, c.startsWith('group'))
| ........................^
... ve daha fazlası ...
Bu hatalar, makrolar henüz etkinleştirilmediği için oluşur. Makroları etkinleştirmek için cel.ClearMacros() simgesini kaldırın ve tekrar çalıştırın:
=== 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)
Şu anda desteklenen makrolar şunlardır:
Makro | İmza | Açıklama |
tümü | r.all(var, cond) | Koşulun, r aralığındaki tüm değişkenler için doğru olup olmadığını test eder. |
exists | r.exists(var, cond) | Koşulun, r aralığındaki herhangi bir değişken için doğru olup olmadığını test eder. |
exists_one | r.exists_one(var, cond) | Koşulun, r aralığındaki yalnızca bir değişken için doğru olup olmadığını test eder. |
filtrele | r.filter(var, cond) | Listeler için, r aralığındaki her var öğesinin cond koşulunu karşıladığı yeni bir liste oluşturun. Haritalar için, r aralığındaki her anahtar değişkeninin koşulunu karşıladığı yeni bir liste oluşturun. |
harita | r.map(var, expr) | Aralıktaki her değişkenin ifadeyle dönüştürüldüğü yeni bir liste oluşturur. |
r.map(var, cond, expr) | İki bağımsız değişkenli map işleviyle aynıdır ancak değer dönüştürülmeden önce koşullu bir cond filtresi içerir. | |
sahip | has(a.b) | Değer a üzerinde b için varlık testi : Haritalar için JSON test tanımı. Protolarda, varsayılan olmayan temel değer veya ayarlanmış bir mesaj alanı test edilir. |
r aralığı bağımsız değişkeni map türünde olduğunda var, harita anahtarı olur. list türü değerlerde ise var, liste öğesi değeri olur. all, exists, exists_one, filter ve map makroları, girişin boyutuyla sınırlı bir for-each yinelemesi gerçekleştiren bir AST yeniden yazma işlemi yapar.
Sınırlı kapsamlar, CEL programlarının Turing-complete olmamasını sağlar ancak girişle ilgili olarak süper doğrusal sürede değerlendirilir. Bu makroları seyrek olarak veya hiç kullanmayın. Listeleme ifadelerinin yoğun kullanımı, genellikle özel bir işlevin daha iyi bir kullanıcı deneyimi ve daha iyi performans sağlayacağının iyi bir göstergesidir.
12. İnce ayar
Şu anda yalnızca CEL-Go'ya özel olan ancak diğer CEL uygulamalarıyla ilgili gelecek planlarını gösteren birkaç özellik vardır. Aşağıdaki alıştırmada, aynı AST için farklı program planları gösterilmektedir:
// 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
Optimizasyon işareti etkinleştirildiğinde CEL, listeyi oluşturmak ve değişmezleri önceden eşlemek için fazladan zaman harcar ve "in" operatörü gibi belirli çağrıları gerçek bir küme üyeliği testi olacak şekilde optimize eder:
// 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)
Aynı program farklı girişlere karşı birçok kez değerlendirildiğinde optimizasyon iyi bir seçimdir. Ancak program yalnızca bir kez değerlendirilecekse optimizasyon yalnızca ek yük oluşturur.
Kapsamlı Değerlendirme (Exhaustive Eval)
İfade değerlendirme davranışında hata ayıklama için Exhaustive Eval'i kullanabilirsiniz. Bu işlev, ifade değerlendirmesindeki her adımda gözlemlenen değer hakkında bilgi sağlar.
// 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)
Her ifade kimliği için ifade değerlendirme durumunun listesini görürsünüz:
------ 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)
İfade kimliği 2, birinci daldaki in operatörünün sonucuna, ifade kimliği 11 ise ikinci daldaki == operatörüne karşılık gelir. Normal değerlendirme altında, ifade 2 hesaplandıktan sonra kısa devre yapardı. y, uint olmasaydı durum, ifadenin neden başarısız olacağına dair yalnızca bir değil iki neden gösterirdi.
13. Hangi konular ele alındı?
İfade motoruna ihtiyacınız varsa CEL'yi kullanabilirsiniz. CEL, performansın kritik olduğu kullanıcı yapılandırması gerektiren projeler için idealdir.
Önceki alıştırmalarda, verilerinizi CEL'ye aktarma ve çıkışı veya kararı geri alma konusunda rahat olmanızı umuyoruz.
Boole kararı vermekten JSON ve Protobuffer mesajları oluşturmaya kadar yapabileceğiniz işlemler hakkında fikir sahibi olduğunuzu umuyoruz.
İfadelerle nasıl çalışacağınız ve ifadelerin ne yaptığı hakkında fikir sahibi olduğunuzu umuyoruz. Ayrıca, bu süreyi uzatmanın yaygın yollarını da anlıyoruz.