CEL-Go कोडलैब (कोड बनाना सीखना): तेज़, सुरक्षित, एम्बेड किए गए एक्सप्रेशन

1. परिचय

सीईएल क्या है?

सीईएल, एक्सप्रेशन वाली ऐसी भाषा है जो पूरी तरह तैयार नहीं होती. इसे तेज़, पोर्टेबल, और सुरक्षित तरीके से एक्ज़ीक्यूट करने के लिए डिज़ाइन किया गया है. सीईएल को अलग से या किसी बड़े प्रॉडक्ट के साथ जोड़ा जा सकता है.

सीईएल को ऐसी भाषा के तौर पर डिज़ाइन किया गया है जिसमें उपयोगकर्ता कोड को एक्ज़ीक्यूट करना सुरक्षित हो. किसी उपयोगकर्ता के Python कोड पर, बिना देखे eval() को कॉल करना खतरनाक हो सकता है. हालांकि, उपयोगकर्ता के सीईएल कोड को सुरक्षित तरीके से एक्ज़ीक्यूट किया जा सकता है. सीईएल, ऐसे व्यवहार पर रोक लगाता है जो उसे कम परफ़ॉर्म करने वाला बनाता है. इसलिए, यह नैनोसेकंड से माइक्रोसेकंड के क्रम में सुरक्षित तरीके से आकलन करता है; यह उन ऐप्लिकेशन के लिए बिलकुल सही है जो परफ़ॉर्मेंस के लिए अहम हैं.

सीईएल, सिंगल लाइन फ़ंक्शन या लैम्डा एक्सप्रेशन से मिलते-जुलते एक्सप्रेशन का आकलन करता है. आम तौर पर, सीईएल का इस्तेमाल बूलियन फै़सले लेने के लिए किया जाता है. हालांकि, इसका इस्तेमाल JSON या प्रोटोबफ़ मैसेज जैसे ज़्यादा मुश्किल ऑब्जेक्ट को बनाने में भी किया जा सकता है.

क्या सीईएल आपके प्रोजेक्ट के लिए सही है?

सीईएल, एएसटी से किसी एक्सप्रेशन की जांच नैनोसेकंड से माइक्रोसेकंड तक करती है. इसलिए, सीईएल के लिए एएसटी के एक्सप्रेशन का सबसे सही इस्तेमाल वे ऐप्लिकेशन हैं जिनकी परफ़ॉर्मेंस के लिए क्रिटिकल पाथ बनाए जाते हैं. क्रिटिकल पाथ में, सीईएल कोड को एएसटी में कंपाइल नहीं किया जाना चाहिए; सबसे बेहतर ऐप्लिकेशन वे होते हैं जिनमें कॉन्फ़िगरेशन अक्सर एक्ज़ीक्यूट किया जाता है और उनमें कभी-कभी बदलाव किया जाता है.

उदाहरण के लिए, किसी सेवा के लिए हर एचटीटीपी अनुरोध के साथ एक सुरक्षा नीति लागू करना, सीईएल के लिए इस्तेमाल का बेहतर उदाहरण है. इसकी वजह यह है कि सुरक्षा नीति में बहुत कम बदलाव होते हैं और रिस्पॉन्स समय पर सीईएल का थोड़ा-बहुत असर पड़ता है. इस मामले में, अगर अनुरोध की अनुमति दी जानी चाहिए या नहीं, तो सीईएल एक बूलियन दिखाता है. हालांकि, इससे ज़्यादा मुश्किल मैसेज दिख सकता है.

इस कोडलैब (कोड बनाना सीखना) में क्या-क्या शामिल है?

इस कोडलैब के पहले चरण में, सीईएल और इसके मुख्य सिद्धांत के इस्तेमाल के बारे में बताया गया है. इसके बाद, कसरत को कोडिंग करने के लिए इस्तेमाल किया जाता है. इनमें इस्तेमाल के सामान्य उदाहरण शामिल किए जाते हैं. भाषा, सिमेंटिक्स, और सुविधाओं के बारे में ज़्यादा जानने के लिए, GitHub और CEL Go Docs पर सीईएल लैंग्वेज डेफ़िनिशन देखें.

यह कोडलैब उन डेवलपर के लिए है जो सीईएल के साथ काम करने वाली सेवाओं का इस्तेमाल करने के लिए, सीईएल सीखना चाहते हैं. इस कोडलैब में, सीईएल को अपने प्रोजेक्ट में इंटिग्रेट करने का तरीका नहीं बताया गया है.

आपको इनके बारे में जानकारी मिलेगी

  • सीईएल के मुख्य सिद्धांत
  • नमस्ते, दुनिया: किसी स्ट्रिंग का आकलन करने के लिए सीईएल का इस्तेमाल करना
  • वैरिएबल बनाना
  • लॉजिकल AND/OR ऑपरेशन में सीईएल के शॉर्ट-सर्किटिंग को समझना
  • JSON बनाने के लिए, सीईएल का इस्तेमाल करने का तरीका
  • प्रोटोबफ़र बनाने के लिए, सीईएल का इस्तेमाल कैसे करें
  • मैक्रो बनाना
  • अपने सीईएल एक्सप्रेशन को बेहतर बनाने के तरीके

आपको इन चीज़ों की ज़रूरत होगी

ज़रूरी शर्तें

यह कोडलैब, प्रोटोकॉल बफ़र और Go Lang के बारे में बुनियादी जानकारी पर आधारित है.

अगर आपको प्रोटोकॉल बफ़र के बारे में नहीं पता है, तो पहले अभ्यास से आपको यह पता चलेगा कि सीईएल कैसे काम करता है. हालांकि, जिन उदाहरणों में प्रोटोकॉल बफ़र का इस्तेमाल सीईएल के लिए इनपुट के तौर पर किया गया है उन्हें समझना मुश्किल हो सकता है. सबसे पहले इन ट्यूटोरियल में से किसी एक की मदद से काम करें. ध्यान दें कि सीईएल का इस्तेमाल करने के लिए प्रोटोकॉल बफ़र की ज़रूरत नहीं होती है, लेकिन कोडलैब में इनका इस्तेमाल बड़े पैमाने पर किया जाता है.

आप चलाकर जांच कर सकते हैं कि go इंस्टॉल किया गया है या नहीं:

go --help

2. मुख्य सिद्धांत

ऐप्लिकेशन

सीईएल का इस्तेमाल अलग-अलग तरह के कामों के लिए किया जाता है. जैसे, आरपीसी रूट करने से लेकर सुरक्षा नीति तय करने तक. सीईएल, एक्सटेंसिबल है और यह ऐप्लिकेशन ऐग्नोस्टिक है. साथ ही, उसे एक बार कंपाइल करने और कई वर्कफ़्लो का आकलन करने के लिए ऑप्टिमाइज़ किया गया है.

कई सेवाएं और ऐप्लिकेशन, डिक्लेरेटिव कॉन्फ़िगरेशन की जांच करते हैं. उदाहरण के लिए, भूमिका पर आधारित ऐक्सेस कंट्रोल (आरबीएसी), जानकारी देने वाला एक कॉन्फ़िगरेशन है. इससे किसी भूमिका और उपयोगकर्ताओं को दिए गए ऐक्सेस के बारे में फ़ैसला लिया जाता है. अगर डिक्लेरेटिव कॉन्फ़िगरेशन 80% इस्तेमाल के उदाहरण हैं, तो उपयोगकर्ताओं को ज़्यादा अच्छे तरीके से जानकारी देने की ज़रूरत होने पर, सीईएल बाकी 20% को राउंड आउट करने में मददगार साबित होगा.

कंपाइलेशन

एक्सप्रेशन को किसी एनवायरमेंट में कंपाइल किया जाता है. कंपाइलेशन चरण में, प्रोटोबफ़ रूप में एक ऐब्स्ट्रैक्ट सिंटैक्स ट्री (एएसटी) बनता है. कंपाइल किए गए एक्सप्रेशन को आम तौर पर बाद में इस्तेमाल करने के लिए सेव किया जाता है, ताकि आकलन की प्रोसेस को तेज़ी से पूरा किया जा सके. कंपाइल किए गए एक एक्सप्रेशन का आकलन, कई अलग-अलग इनपुट की मदद से किया जा सकता है.

एक्सप्रेशन

उपयोगकर्ता एक्सप्रेशन तय करते हैं; सेवाओं और ऐप्लिकेशन उस परिवेश को परिभाषित करते हैं, जहां यह चलता है. फ़ंक्शन सिग्नेचर, इनपुट के बारे में बताता है और इसे सीईएल एक्सप्रेशन के बाहर लिखा जाता है. सीईएल के लिए उपलब्ध फ़ंक्शन की लाइब्रेरी अपने-आप इंपोर्ट होती है.

यहां दिए गए उदाहरण में, एक्सप्रेशन में एक अनुरोध ऑब्जेक्ट को शामिल किया जाता है और अनुरोध में दावे का टोकन शामिल होता है. इस एक्सप्रेशन से यह पता चलता है कि दावों वाला टोकन अब भी मान्य है या नहीं.

// 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

परिवेश

एनवायरमेंट को सेवाओं के हिसाब से तय किया जाता है. सीईएल को एम्बेड करने वाली सेवाएं और ऐप्लिकेशन, एक्सप्रेशन एनवायरमेंट के बारे में बताते हैं. एनवायरमेंट, वैरिएबल और फ़ंक्शन का कलेक्शन होता है. इनका इस्तेमाल एक्सप्रेशन में किया जा सकता है.

सीईएल टाइप-चेकर, प्रोटो-आधारित एलानों का इस्तेमाल यह पक्का करने के लिए करता है कि किसी एक्सप्रेशन में मौजूद सभी आइडेंटिफ़ायर और फ़ंक्शन रेफ़रंस का सही तरीके से एलान किया गया हो और उनका सही तरीके से इस्तेमाल किया गया हो.

किसी एक्सप्रेशन को पार्स करने के तीन चरण

एक्सप्रेशन को प्रोसेस करने के तीन चरण होते हैं: पार्स करें, जांच करें, और आकलन करें. सीईएल का सबसे सामान्य पैटर्न, कंट्रोल प्लेन के लिए है, जो कॉन्फ़िगरेशन के समय एक्सप्रेशन को पार्स करने और उनकी जांच करने के साथ-साथ एएसटी को स्टोर करता है.

c71fc08068759f81.png

रनटाइम के दौरान, डेटा प्लेन, एएसटी को बार-बार इकट्ठा करता है और उसका आकलन करता है. सीईएल को रनटाइम के दौरान बेहतर तरीके से इस्तेमाल करने के लिए ऑप्टिमाइज़ किया गया है. हालांकि, लेटेंसी वाले क्रिटिकल कोड पाथ में पार्स और जांच नहीं की जानी चाहिए.

49ab7d8517143b66.png

सीईएल को ANTLR लेक्सर / पार्सर व्याकरण का इस्तेमाल करके, ऐसे एक्सप्रेशन से ऐब्सट्रैक्ट सिंटैक्स ट्री में पार्स किया जाता है जिसे कोई भी व्यक्ति आसानी से पढ़ सकता है. पार्स फ़ेज़, प्रोटो पर आधारित ऐब्स्ट्रैक्ट सिंटैक्स ट्री का उत्सर्जन करता है. इसमें एएसटी के हर Expr नोड में एक पूर्णांक आईडी होता है. इसका इस्तेमाल पार्स और जांच के दौरान जनरेट किए गए मेटाडेटा को इंडेक्स करने के लिए किया जाता है. पार्स करने के दौरान बनाया गया syntax.proto, एक्सप्रेशन के स्ट्रिंग रूप में टाइप किए गए शब्द को एब्सट्रैक्ट तरीके से दिखाता है.

एक्सप्रेशन को पार्स करने के बाद, इसकी जांच एनवायरमेंट के हिसाब से की जा सकती है. इससे यह पक्का किया जा सकता है कि एक्सप्रेशन में सभी वैरिएबल और फ़ंक्शन आइडेंटिफ़ायर का एलान कर दिया गया है और उन्हें सही तरीके से इस्तेमाल किया जा रहा है. टाइप-चेकर एक checked.proto बनाता है, जिसमें टाइप, वैरिएबल, और फ़ंक्शन रिज़ॉल्यूशन का मेटाडेटा शामिल होता है. यह मेटाडेटा, आकलन की क्षमता को काफ़ी बेहतर बना सकता है.

सीईएल का आकलन करने वाले को तीन चीज़ों की ज़रूरत होती है:

  • किसी भी कस्टम एक्सटेंशन के लिए फ़ंक्शन बाइंडिंग
  • वैरिएबल बाइंडिंग
  • आकलन करने के लिए AST

फ़ंक्शन और वैरिएबल बाइंडिंग, एएसटी को कंपाइल करने के लिए इस्तेमाल की गई वैल्यू से मेल खानी चाहिए. इनमें से किसी भी इनपुट को एक से ज़्यादा इवैलुएशन में फिर से इस्तेमाल किया जा सकता है. जैसे, वैरिएबल बाइंडिंग के कई सेट में AST का आकलन करना, कई एएसटी के लिए एक ही वैरिएबल का इस्तेमाल करना या किसी प्रोसेस के लाइफ़टाइम में इस्तेमाल की जाने वाली फ़ंक्शन बाइंडिंग (एक सामान्य मामला).

3. सेट करें

इस कोडलैब का कोड, cel-go रेपो के codelab फ़ोल्डर में मौजूद होता है. यह समाधान इसी रेपो के codelab/solution फ़ोल्डर में उपलब्ध है.

क्लोन करें और cd, रेपो में बदलें:

git clone https://github.com/google/cel-go.git 
cd cel-go/codelab

go run का इस्तेमाल करके कोड चलाएं:

go run .

आपको यह आउटपुट दिखेगा:

=== 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 ===

सीईएल पैकेज कहां हैं?

अपने एडिटर में, codelab/codelab.go खोलें. यहां आपको मुख्य फ़ंक्शन दिखेगा, जो कोडलैब के इस तरीके में एक्सरसाइज़ चलाने का काम करता है. इसके बाद, हेल्पर फ़ंक्शन के तीन ब्लॉक होते हैं. हेल्पर का पहला सेट, सीईएल के आकलन के अलग-अलग चरणों में मदद करता है:

  • Compile फ़ंक्शन: किसी एनवायरमेंट के ख़िलाफ़ एक्सप्रेशन को पार्स करता है और जांच करता है. साथ ही, एक्सप्रेशन को इनपुट भी करता है
  • Eval फ़ंक्शन: किसी इनपुट के लिए, कंपाइल प्रोग्राम का आकलन करता है
  • Report फ़ंक्शन: आकलन के नतीजे को सुंदर प्रिंट करता है

इसके अलावा, अलग-अलग कसरतों में इनपुट बनाने में मदद करने के लिए, request और auth हेल्पर भी दिए गए हैं.

इन अभ् यासों को उनके छोटे पैकेज नाम से पैकेज के रूप में देखा जाएगा. अगर आपको इसके बारे में ज़्यादा जानकारी चाहिए, तो google/cel-go रेपो में, पैकेज से सोर्स जगह तक की मैपिंग नीचे दी गई है:

पैकेज

सोर्स की जगह

ब्यौरा

cel

cel-go/cel

टॉप लेवल इंटरफ़ेस

संदर्भ

cel-go/common/types/ref

रेफ़रंस इंटरफ़ेस

प्रकार

cel-go/common/types

रनटाइम टाइप की वैल्यू

4. नमस्ते!

सभी प्रोग्रामिंग भाषाओं की परंपरा के तहत, हम "नमस्ते वर्ल्ड!" बनाकर और उसका आकलन करके शुरुआत करेंगे.

एनवायरमेंट को कॉन्फ़िगर करना

अपने एडिटर में, exercise1 का एलान देखें. इसके बाद, एनवायरमेंट सेट अप करने के लिए यह जानकारी भरें:

// 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
}

सीईएल ऐप्लिकेशन, किसी एक्सप्रेशन का आकलन किसी एनवायरमेंट के ख़िलाफ़ करते हैं. env, err := cel.NewEnv(), स्टैंडर्ड एनवायरमेंट को कॉन्फ़िगर करता है.

कॉल में cel.EnvOption विकल्प देकर, एनवायरमेंट को पसंद के मुताबिक बनाया जा सकता है. इन विकल्पों की मदद से, मैक्रो को बंद किया जा सकता है. साथ ही, कस्टम वैरिएबल और फ़ंक्शन वगैरह का एलान किया जा सकता है.

स्टैंडर्ड सीईएल एनवायरमेंट, भाषा की खास बातों में बताए गए सभी टाइप, ऑपरेटर, फ़ंक्शन, और मैक्रो के साथ काम करता है.

एक्सप्रेशन को पार्स करें और सही का निशान लगाएं

एनवायरमेंट को कॉन्फ़िगर करने के बाद, एक्सप्रेशन को पार्स किया जा सकता है और उनकी जांच की जा सकती है. अपने फ़ंक्शन में यह जोड़ें:

// 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 और Check कॉल से मिली iss वैल्यू, उन समस्याओं की सूची है जिनमें गड़बड़ियां हो सकती हैं. अगर iss.Err() शून्य नहीं है, तो सिंटैक्स या सिमेंटिक्स में कोई गड़बड़ी है और प्रोग्राम को आगे नहीं बढ़ाया जा सकता. एक्सप्रेशन के सही होने पर, इन कॉल का नतीजा एक्ज़ीक्यूटेबल cel.Ast होता है.

व्यंजक का मान निकालें

एक्सप्रेशन को पार्स करने और cel.Ast में चेक इन करने के बाद, इसे ऐसे प्रोग्राम में बदला जा सकता है जिसका आकलन किया जा सके. इसकी फ़ंक्शन बाइंडिंग और इवैलुएशन मोड को फ़ंक्शनल विकल्पों के साथ अपनी पसंद के मुताबिक बनाया जा सकता है. ध्यान दें, cel.CheckedExprToAst या cel.ParsedExprToAst फ़ंक्शन का इस्तेमाल करके भी किसी प्रोटोकॉल से cel.Ast को पढ़ा जा सकता है.

cel.Program को प्लान करने के बाद, Eval को कॉल करके इनपुट के हिसाब से इसका आकलन किया जा सकता है. Eval के नतीजे में नतीजा, आकलन की जानकारी, और गड़बड़ी की स्थिति शामिल होगी.

प्लानिंग जोड़ें और 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()
}

कम शब्दों में जानकारी देने के लिए, हम गड़बड़ी वाले उन मामलों को हटा देंगे जो आगे के टास्क में शामिल नहीं हैं.

कोड इस्तेमाल करें

कमांड लाइन पर, कोड को फिर से चलाएं:

go run .

आपको आगे के अभ्यासों के लिए प्लेसहोल्डर के साथ, नीचे दिया गया आउटपुट दिखेगा.

=== Exercise 1: Hello World ===

------ input ------
(interpreter.emptyActivation)

------ result ------
value: Hello, World! (types.String)

5. किसी फ़ंक्शन में वैरिएबल का इस्तेमाल करना

ज़्यादातर सीईएल ऐप्लिकेशन, ऐसे वैरिएबल का एलान करते हैं जिन्हें एक्सप्रेशन में रेफ़र किया जा सकता है. वैरिएबल के एलान से नाम और टाइप के बारे में पता चलता है. वैरिएबल का टाइप सीईएल बिल्ट-इन टाइप हो सकता है, प्रोटोकॉल बफ़र का जाना-पहचाना हो सकता है या फिर किसी प्रोटोबफ़ मैसेज का टाइप हो सकता है. बशर्ते, उसका डिस्क्रिप्टर सीईएल को भी दिया गया हो.

फ़ंक्शन जोड़ें

अपने एडिटर में, exercise2 का एलान ढूंढें और यह जानकारी जोड़ें:

// 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()
}

गड़बड़ी को फिर से चलाएं और समझें

प्रोग्राम को फिर से चलाएं:

go run .

आपको यह आउटपुट दिखेगा:

ERROR: <input>:1:1: undeclared reference to 'request' (in container '')
 | request.auth.claims.group == 'admin'
 | ^

टाइप-चेकर अनुरोध ऑब्जेक्ट के लिए एक गड़बड़ी पैदा करता है, जिसमें गड़बड़ी होने पर सोर्स स्निपेट को आसानी से शामिल कर लिया जाता है.

6. वैरिएबल का एलान करना

EnvOptions जोड़ें

एडिटर में, हम अनुरोध के ऑब्जेक्ट के लिए, google.rpc.context.AttributeContext.Request टाइप के मैसेज के तौर पर एक एलान देकर, इस गड़बड़ी को ठीक करते हैं. जैसे:

// 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()
}

गड़बड़ी को फिर से चलाएं और समझें

प्रोग्राम को फिर से चलाना:

go run .

आपको यह गड़बड़ी दिखनी चाहिए:

ERROR: <input>:1:8: [internal] unexpected failed resolution of 'google.rpc.context.AttributeContext.Request'
 | request.auth.claims.group == 'admin'
 | .......^

प्रोटोबफ़ मैसेज से जुड़े वैरिएबल का इस्तेमाल करने के लिए, टाइप-चेकर को टाइप डिस्क्रिप्टर की भी जानकारी होनी चाहिए.

अपने फ़ंक्शन में अनुरोध के लिए डिस्क्रिप्टर तय करने के लिए cel.Types का इस्तेमाल करें:

// 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()
}

फिर से चलाएं!

प्रोग्राम को फिर से चलाएं:

go run .

आपको यह जानकारी दिखेगी:

=== 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)

समीक्षा करने के लिए, हमने अभी-अभी एक गड़बड़ी के लिए एक वैरिएबल बताया, उसे एक टाइप डिस्क्रिप्टर असाइन किया, और फिर एक्सप्रेशन के आकलन में उस वैरिएबल का रेफ़रंस दिया.

7. लॉजिकल और/या

सीईएल की एक खास सुविधा, इसका कम्यूटेटिव लॉजिकल ऑपरेटर इस्तेमाल करना है. कंडिशनल ब्रांच की कोई भी साइड, गड़बड़ी या अधूरे इनपुट होने पर भी इवैलुएशन को शॉर्ट-सर्किट कर सकती है.

दूसरे शब्दों में कहें, तो सीईएल को इवैलुएशन का ऐसा ऑर्डर मिलता है जो मुमकिन होने पर, नतीजों के तौर पर दिखता है. इसके लिए, गड़बड़ियों या ऐसे डेटा को अनदेखा किया जाता है जो अन्य इवैलुएशन ऑर्डर में हो सकता है. ऐप्लिकेशन, इवैलुएशन की लागत को कम करने के लिए इस प्रॉपर्टी पर भरोसा कर सकते हैं. साथ ही, महंगे इनपुट को इकट्ठा करने से बच सकते हैं. ऐसा तब होगा, जब उनके बिना ही किसी नतीजे पर पहुंचा जा सकता हो.

हम AND/OR उदाहरण जोड़ेंगे. इसके बाद, हम इसे किसी अन्य इनपुट के साथ आज़माएंगे. इससे हम यह समझ पाएंगे कि सीईएल शॉर्ट-सर्किट का आकलन कैसे किया जाता है.

फ़ंक्शन बनाएं

अपने एडिटर में, कसरत 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"),
    ),
  )
}

इसके बाद, इस OR स्टेटमेंट को शामिल करें. यह तब सही होगा, जब उपयोगकर्ता, admin ग्रुप का सदस्य हो या उसके पास कोई खास ईमेल आइडेंटिफ़ायर हो:

// 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)
}

और आखिर में, eval केस जोड़ें जो खाली दावों के सेट वाले उपयोगकर्ता की जांच करता है:

// 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()))
}

खाली दावा सेट के साथ कोड चलाएं

प्रोग्राम को फिर से चलाने पर, आपको यह नया आउटपुट दिखेगा:

=== 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)

इवैलुएशन केस को अपडेट करना

इसके बाद, खाली दावा सेट के साथ किसी दूसरे मुख्य खाते में पास करने के लिए, आकलन के केस को अपडेट करें:

// 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()))
}

कोड को समय के साथ चलाएं

प्रोग्राम को फिर से चलाने,

go run .

आपको यह गड़बड़ी दिखनी चाहिए:

=== 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

प्रोटोबफ़ में, हम जानते हैं कि किन फ़ील्ड और टाइप की उम्मीद की जानी चाहिए. मैप और JSON वैल्यू में, हमें नहीं पता होता कि कोई कुंजी मौजूद होगी या नहीं. कुंजी के मौजूद न होने की वजह से, कोई सुरक्षित डिफ़ॉल्ट वैल्यू नहीं होती. इसलिए, सीईएल डिफ़ॉल्ट रूप से गड़बड़ी का मैसेज दिखाता है.

8. कस्टम फ़ंक्शन

सीईएल में कई पहले से मौजूद फ़ंक्शन होते हैं, लेकिन कुछ मामलों में कस्टम फ़ंक्शन काम का होता है. उदाहरण के लिए, कस्टम फ़ंक्शन का इस्तेमाल सामान्य स्थितियों के लिए उपयोगकर्ता अनुभव को बेहतर बनाने या कॉन्टेक्स्ट के हिसाब से संवेदनशील स्थिति को सामने लाने के लिए किया जा सकता है

इस वीडियो में, हम आम तौर पर इस्तेमाल होने वाली जांचों को एक साथ पैकेज करने के लिए, फ़ंक्शन को सार्वजनिक करने का तरीका जानेंगे.

किसी कस्टम फ़ंक्शन को कॉल करें

सबसे पहले, contains नाम के ओवरराइड को सेट अप करने के लिए कोड बनाएं. इससे यह तय होता है कि मैप में कोई पासकोड मौजूद है या नहीं और उसकी कोई वैल्यू तय की गई है. फ़ंक्शन की परिभाषा और फ़ंक्शन बाइंडिंग के लिए प्लेसहोल्डर छोड़ें:

// 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()
}  

कोड चलाएं और गड़बड़ी को समझें

कोड को फिर से चलाने पर, आपको यह गड़बड़ी दिखेगी:

ERROR: <input>:1:29: found no matching overload for 'contains' applied to 'map(string, dyn).(string, string)'
 | request.auth.claims.contains('group', 'admin')
 | ............................^

गड़बड़ी को ठीक करने के लिए, हमें उन एलानों की सूची में contains फ़ंक्शन जोड़ना होगा जो फ़िलहाल अनुरोध वैरिएबल के बारे में बताते हैं.

इन तीन लाइनों को जोड़कर, पैरामीटर वाले टाइप का एलान करें. यह काम उतना ही मुश्किल है जितना कि सीईएल के लिए कोई भी फ़ंक्शन ओवरलोड होगा)

// 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()
}

कस्टम फ़ंक्शन जोड़ें

इसके बाद, हम शामिल किया गया एक नया फ़ंक्शन जोड़ेंगे, जो पैरामीटर वाले टाइप का इस्तेमाल करेगा:

// 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()
}

गड़बड़ी को समझने के लिए प्रोग्राम चलाएं

व्यायाम करें. रनटाइम फ़ंक्शन मौजूद न होने के बारे में, आपको यह गड़बड़ी दिखेगी:

------ result ------
error: no such overload: contains

cel.FunctionBinding() फ़ंक्शन का इस्तेमाल करके, NewEnv एलान में फ़ंक्शन लागू करने की सुविधा दें:

// 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()
}

प्रोग्राम अब सही से काम करना चाहिए:

=== 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)

दावा मौजूद होने पर क्या होता है?

ज़्यादा क्रेडिट पाने के लिए, इनपुट पर एडमिन दावा सेट करके यह पुष्टि करें कि यह ज़्यादा लोड हुआ है. साथ ही, दावा होने पर भी 'सही' दिखाता है. आपको यह आउटपुट दिखेगा:

=== 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)

आगे बढ़ने से पहले, mapContainsKeyValue फ़ंक्शन की जांच ज़रूर कर लें:

// 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])
}

एक्सटेंशन को ज़्यादा आसानी से इस्तेमाल करने के लिए, कस्टम फ़ंक्शन के लिए हस्ताक्षर में ref.Val टाइप के आर्ग्युमेंट की ज़रूरत होती है. हालांकि, यह बात सिर्फ़ यह है कि एक्सटेंशन इस्तेमाल करने में आसानी होने से, लागू करने वाले पर बोझ बढ़ता है. इससे यह पक्का होता है कि सभी वैल्यू टाइप सही तरीके से हैंडल किए गए हैं. जब इनपुट आर्ग्युमेंट के टाइप या काउंट, फ़ंक्शन के एलान से मेल न खाएं, तो no such overload गड़बड़ी मिलेगी.

cel.FunctionBinding(), रनटाइम टाइप गार्ड जोड़ता है. इससे यह पक्का किया जाता है कि रनटाइम का कॉन्ट्रैक्ट, एनवायरमेंट में टाइप-चेक किए गए एलान से मेल खाता है.

9. JSON फ़ॉर्मैट में बनाया गया

सीईएल, ऐसे आउटपुट भी जनरेट कर सकते हैं जो बूलियन नहीं होते, जैसे कि 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")
    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()
}

कोड लागू करें

कोड को फिर से चलाने पर, आपको यह गड़बड़ी दिखेगी:

ERROR: <input>:5:11: undeclared reference to 'now' (in container '')
 |    'iat': now,
 | ..........^
... and more ...

cel.TimestampType टाइप के now वैरिएबल के लिए, cel.NewEnv() से एलान करने वाला फ़ॉर्म जोड़ें और फिर से चलाएं:

// 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()
}

कोड को फिर से चलाएं. ऐसा करने पर, कोड ट्रिगर हो जाएगा:

=== 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}}

प्रोग्राम चलता है, लेकिन आउटपुट वैल्यू out को साफ़ तौर पर JSON में बदलना होगा. इस मामले में, इंटरनल सीईएल का मतलब JSON कन्वर्टेबल है. ऐसा इसलिए, क्योंकि इससे सिर्फ़ उन टाइप के बारे में पता चलता है जो JSON पर काम कर सकते हैं या जिनके लिए एक जाना-माना 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()
}

codelab.go फ़ाइल में मौजूद valueToJSON हेल्पर फ़ंक्शन का इस्तेमाल करके, टाइप में बदलाव किए जाने के बाद, आपको नीचे दिया गया अतिरिक्त आउटपुट दिखेगा:

------ 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. प्रोटो बनाना

सीईएल, ऐप्लिकेशन में इकट्ठा किए गए किसी भी तरह के मैसेज के लिए प्रोटोबफ़ मैसेज बना सकता है. इनपुट jwt से google.rpc.context.AttributeContext.Request बनाने के लिए, फ़ंक्शन जोड़ें

// 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()
}

कोड लागू करें

कोड को फिर से चलाने पर, आपको यह गड़बड़ी दिखेगी:

ERROR: <input>:2:10: undeclared reference to 'Request' (in container '')
 |   Request{
 | .........^

कंटेनर मूल रूप से किसी नेमस्पेस या पैकेज के बराबर होता है, लेकिन यह किसी प्रोटोबफ़ मैसेज के नाम जितना छोटा भी हो सकता है. सीईएल कंटेनर, प्रोटोबफ़ और C++ जैसे नेमस्पेस रिज़ॉल्यूशन नियमों का इस्तेमाल करके, यह तय करते हैं कि किसी दिए गए वैरिएबल, फ़ंक्शन या टाइप का नाम कहां बताया गया है.

कंटेनर google.rpc.context.AttributeContext को देखते हुए, टाइप-चेकर और समीक्षक सभी वैरिएबल, टाइप, और फ़ंक्शन के लिए आइडेंटिफ़ायर के इन नामों का इस्तेमाल करेंगे:

  • google.rpc.context.AttributeContext.<id>
  • google.rpc.context.<id>
  • google.rpc.<id>
  • google.<id>
  • <id>

ऐब्सलूट नेम के लिए, वैरिएबल, टाइप या फ़ंक्शन रेफ़रंस के पहले डॉट का इस्तेमाल करें. इस उदाहरण में, .<id> एक्सप्रेशन सिर्फ़ टॉप लेवल <id> आइडेंटिफ़ायर को खोजेगा. इसके लिए, कंटेनर की जांच करना ज़रूरी नहीं है.

सीईएल एनवायरमेंट के लिए cel.Container("google.rpc.context.AttributeContext") विकल्प तय करें और फिर से चलाएं:

// 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)),
  )

 ...
}

आपको यह आउटपुट मिलेगा:

ERROR: <input>:4:16: undeclared reference to 'jwt' (in container 'google.rpc.context.AttributeContext')
 |     principal: jwt.iss + '/' + jwt.sub,
 | ...............^

... और कई अन्य गड़बड़ियां...

इसके बाद, jwt और now वैरिएबल का एलान करें और प्रोग्राम उम्मीद के मुताबिक चलना चाहिए:

=== 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}

ज़्यादा क्रेडिट पाने के लिए, टाइप को खोलने के लिए मैसेज पर out.Value() को कॉल करें. इससे आपको यह पता चलेगा कि नतीजा कैसे बदल रहा है.

11. मैक्रो

पार्स करते समय, सीईएल प्रोग्राम में बदलाव करने के लिए मैक्रो का इस्तेमाल किया जा सकता है. मैक्रो किसी कॉल सिग्नेचर को मैच करते हैं और इनपुट कॉल और उसके आर्ग्युमेंट में बदलाव करते हैं, ताकि एक नया सब-एक्सप्रेशन एएसटी जनरेट किया जा सके.

एएसटी में जटिल लॉजिक को लागू करने के लिए मैक्रो का इस्तेमाल किया जा सकता है, जिसे सीधे सीईएल में नहीं लिखा जा सकता. उदाहरण के लिए, has मैक्रो फ़ील्ड की मौजूदगी की जांच करने की सुविधा चालू करता है. समझने के लिए इस्तेमाल होने वाले मैक्रो, जैसे कि मौजूद हैं और सभी फ़ंक्शन कॉल को किसी इनपुट सूची या मैप के ऊपर बाउंड इटरेशन से बदल देते हैं. किसी भी सिद्धांत को वाक्यात्मक रूप से संभव नहीं बनाया जा सकता, लेकिन मैक्रो एक्सपैंशन के ज़रिए ऐसा किया जा सकता है.

अगला व्यायाम जोड़ें और चलाएं:

// 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()
}

आपको ये गड़बड़ियां दिखनी चाहिए:

ERROR: <input>:1:25: undeclared reference to 'c' (in container '')
 | jwt.extra_claims.exists(c, c.startsWith('group'))
 | ........................^

... और कई अन्य ...

ये गड़बड़ियां इसलिए होती हैं क्योंकि मैक्रो अभी तक चालू नहीं हुए हैं. मैक्रो चालू करने के लिए, cel.ClearMacros() हटाएं और फिर से चलाएं:

=== 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)

फ़िलहाल, यहां इस्तेमाल किए जा सकने वाले मैक्रो हैं:

मैक्रो

हस्ताक्षर

ब्यौरा

सभी

r.all(var, Cond)

जांच करें कि क्या वैल्यू, रेंज r में मौजूद सभी अलग-अलग वैल्यू के लिए सही है.

मौजूद है

r.exists(var, Cond)

जांच करें कि क्या वैल्यू, रेंज r में मौजूद किसी भी वैरिएबल के लिए सही है.

exists_one

r.exists_one(var, Cond)

जांच करें कि क्या वैल्यू, रेंज r में मौजूद सिर्फ़ एक वैरिएबल के लिए सही है.

फ़िल्टर करें

r.filter(var, Cond)

सूचियों के लिए, एक नई सूची बनाएं, जहां रेंज में मौजूद हर एलिमेंट वैरियंस स्थिति की शर्तों को पूरा करता हो. मैप के लिए, ऐसी नई सूची बनाएं जिसमें रेंज में मौजूद हर एक बटन, शर्त की शर्तों को पूरा करता हो.

मैप

r.map(var, expr)

एक नई सूची बनाएं, जहां रेंज r की हर वैरिएबल expr से बदली जाती है.

r.map(var, cond, expr)

दो-आर्ग मैप की तरह ही, लेकिन वैल्यू बदलने से पहले कंडिशनल कंडिशन फ़िल्टर के साथ.

इसमें शामिल है

है(a.b)

वैल्यू a पर b की मौजूदगी का टेस्ट : मैप के लिए, json की जांच की गई है. प्रोटो के लिए, नॉन-डिफ़ॉल्ट प्रिमिटिव वैल्यू या एक या सेट मैसेज फ़ील्ड की जांच करता है.

जब रेंज r आर्ग्युमेंट map टाइप होता है, तब मैप की var होगी. साथ ही, list टाइप की वैल्यू के लिए var, लिस्ट एलिमेंट की वैल्यू होगी. all, exists, exists_one, filter, और map मैक्रो एक AST रीराइट करते हैं, जो हर बार इनपुट के साइज़ के लिए बार-बार दोहराए जाते हैं.

इस बात का ध्यान रखा जाता है कि सीईएल प्रोग्राम ट्यूरिंग पूरे न हों. हालांकि, इनपुट के हिसाब से ये प्रोग्राम सुपर-लीनियर टाइम में इवैलुएशन करते हैं. इन मैक्रो का कम से कम इस्तेमाल करें या फिर बिलकुल न करें. आम तौर पर, समझने की क्षमता का ज़्यादा इस्तेमाल करने से इस बात का पता चलता है कि कस्टम फ़ंक्शन से बेहतर उपयोगकर्ता अनुभव और परफ़ॉर्मेंस बेहतर हो सकती है.

12. ट्यूनिंग

फ़िलहाल, कुछ सुविधाएं सिर्फ़ CEL-Go के लिए उपलब्ध हैं. हालांकि, ये इन सुविधाओं के अलावा, आने वाले समय में लागू होने वाले अन्य सीईएल प्लान के बारे में बताती हैं. नीचे दिए गए अभ्यास में एक ही एएसटी के लिए अलग-अलग कार्यक्रम की योजनाएं दिखाई गई हैं:

// 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

ऑप्टिमाइज़ेशन फ़्लैग चालू होने पर सीईएल, सूची बनाने और समय से पहले लिटरल वैल्यू मैप करने में ज़्यादा समय खर्च करेगा. साथ ही, इसे सेट की गई सदस्यता की जांच के लिए, in ऑपरेटर जैसे कुछ कॉल को ऑप्टिमाइज़ करेगा:

    // 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)

जब अलग-अलग इनपुट के हिसाब से एक ही प्रोग्राम का कई बार आकलन किया जाता है, तो ऑप्टिमाइज़ करना एक अच्छा विकल्प है. हालांकि, जब प्रोग्राम का आकलन सिर्फ़ एक बार किया जाएगा, तो ऑप्टिमाइज़ करने से सिर्फ़ ओवरहेड बढ़ते हैं.

पूरी समीक्षा

एग्ज़ॉस्टिव इवैलुएशन, एक्सप्रेशन के आकलन के व्यवहार को डीबग करने में मदद कर सकता है. इसकी वजह यह है कि इससे एक्सप्रेशन के आकलन के हर चरण में मॉनिटर की गई वैल्यू के बारे में अहम जानकारी मिलती है.

    // 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)

आपको हर एक्सप्रेशन आईडी के लिए, एक्सप्रेशन के आकलन की स्थिति की एक सूची दिखेगी:

------ 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)

एक्सप्रेशन आईडी 2, पहले में in ऑपरेटर के नतीजे से मेल खाता है. ब्रांच और एक्सप्रेशन आईडी 11, दूसरे में == ऑपरेटर के बराबर है. सामान्य आकलन के तहत, 2 की गणना करने के बाद, एक्सप्रेशन शॉर्ट सर्किट से भर जाएगा. अगर y uint नहीं है, तो राज्य एक्सप्रेशन के विफल होने के दो कारण दिखाएगा, न कि केवल एक.

13. इसमें क्या शामिल किया गया?

अगर आपको एक एक्सप्रेशन इंजन की ज़रूरत है, तो सीईएल का इस्तेमाल करें. सीईएल उन प्रोजेक्ट के लिए बिलकुल सही है जिनमें परफ़ॉर्मेंस की ज़रूरत होने पर उपयोगकर्ता कॉन्फ़िगरेशन को एक्ज़ीक्यूट करना ज़रूरी होता है.

पिछली एक्सरसाइज़ में हमें उम्मीद है कि आपको अपना डेटा, सीईएल को भेजने और आउटपुट या फ़ैसला लेने में सहज महसूस हुआ होगा.

हमें उम्मीद है कि आपको इस बात की जानकारी होगी कि आप किस तरह की कार्रवाइयां कर सकते हैं. इनमें बूलियन फ़ैसले से लेकर JSON और प्रोटोबफ़र मैसेज जनरेट करने तक, सब कुछ शामिल है.

हमें उम्मीद है कि आपको एक्सप्रेशन के साथ काम करने के तरीके और उनके काम करने के तरीके की जानकारी है. हम इसे ज़्यादा लोगों तक पहुंचाने के सामान्य तरीकों को भी समझते हैं.