CEL-Go Codelab: দ্রুত, নিরাপদ, এমবেডেড এক্সপ্রেশন

1. ভূমিকা

CEL কি?

CEL হল একটি নন-টুরিং সম্পূর্ণ এক্সপ্রেশন ভাষা যা দ্রুত, বহনযোগ্য এবং কার্যকর করার জন্য নিরাপদ। CEL নিজেই ব্যবহার করা যেতে পারে, বা একটি বড় পণ্যের মধ্যে এমবেড করা যেতে পারে।

CEL একটি ভাষা হিসাবে ডিজাইন করা হয়েছিল যেখানে ব্যবহারকারী কোড চালানো নিরাপদ। যদিও ব্যবহারকারীর পাইথন কোডে অন্ধভাবে eval() কল করা বিপজ্জনক, আপনি নিরাপদে একজন ব্যবহারকারীর CEL কোড চালাতে পারেন। এবং যেহেতু CEL এমন আচরণকে বাধা দেয় যা এটিকে কম পারফরম্যান্স করে তোলে, এটি ন্যানোসেকেন্ড থেকে মাইক্রোসেকেন্ডের ক্রম অনুসারে নিরাপদে মূল্যায়ন করে; এটি কর্মক্ষমতা-সমালোচনামূলক অ্যাপ্লিকেশনের জন্য আদর্শ।

CEL এক্সপ্রেশন মূল্যায়ন করে, যা একক লাইন ফাংশন বা ল্যাম্বডা এক্সপ্রেশনের অনুরূপ। যদিও CEL সাধারণত বুলিয়ান সিদ্ধান্তের জন্য ব্যবহৃত হয়, এটি JSON বা protobuf বার্তাগুলির মতো আরও জটিল বস্তু তৈরি করতেও ব্যবহার করা যেতে পারে।

CEL আপনার প্রকল্পের জন্য সঠিক?

যেহেতু CEL ন্যানোসেকেন্ডে AST থেকে মাইক্রোসেকেন্ডে একটি অভিব্যক্তিকে মূল্যায়ন করে, CEL-এর ক্ষেত্রে আদর্শ ব্যবহার হল কর্মক্ষমতা-সমালোচনামূলক পাথ সহ অ্যাপ্লিকেশন। AST-তে CEL কোডের সংকলন সমালোচনামূলক পথে করা উচিত নয়; আদর্শ অ্যাপ্লিকেশন হল সেইগুলি যেখানে কনফিগারেশন প্রায়শই কার্যকর করা হয় এবং তুলনামূলকভাবে কদাচিৎ পরিবর্তন করা হয়।

উদাহরণস্বরূপ, একটি পরিষেবাতে প্রতিটি HTTP অনুরোধের সাথে একটি সুরক্ষা নীতি কার্যকর করা CEL-এর জন্য একটি আদর্শ ব্যবহারের ক্ষেত্রে কারণ নিরাপত্তা নীতি খুব কমই পরিবর্তিত হয় এবং CEL-এর প্রতিক্রিয়া সময়ের উপর একটি নগণ্য প্রভাব পড়বে। এই ক্ষেত্রে, CEL একটি বুলিয়ান ফেরত দেয়, যদি অনুরোধটি অনুমোদিত হওয়া উচিত বা না করা উচিত, তবে এটি আরও জটিল বার্তা ফেরত দিতে পারে।

এই কোডল্যাবে কি কভার করা হয়েছে?

এই কোডল্যাবের প্রথম ধাপটি CEL এবং এর মূল ধারণাগুলি ব্যবহার করার অনুপ্রেরণার মধ্য দিয়ে চলে। বাকিগুলি কোডিং অনুশীলনের জন্য নিবেদিত যা সাধারণ ব্যবহারের ক্ষেত্রে কভার করে। ভাষা, শব্দার্থবিদ্যা, এবং বৈশিষ্ট্যগুলি আরও গভীরভাবে দেখার জন্য GitHub এবং CEL Go Docs-CEL ভাষার সংজ্ঞা দেখুন।

এই কোডল্যাবটি এমন ডেভেলপারদের লক্ষ্য করে যারা ইতিমধ্যেই CEL সমর্থন করে এমন পরিষেবাগুলি ব্যবহার করার জন্য CEL শিখতে চান। এই কোডল্যাবটি কীভাবে আপনার নিজের প্রকল্পে CEL সংহত করতে হয় তা কভার করে না।

আপনি কি শিখবেন

  • CEL থেকে মূল ধারণা
  • হ্যালো, ওয়ার্ল্ড: একটি স্ট্রিং মূল্যায়ন করতে CEL ব্যবহার করে
  • ভেরিয়েবল তৈরি করা
  • লজিক্যাল AND/OR অপারেশনে CEL এর শর্ট সার্কিটিং বোঝা
  • JSON তৈরি করতে কীভাবে CEL ব্যবহার করবেন
  • প্রোটোবাফার তৈরি করতে কীভাবে সিইএল ব্যবহার করবেন
  • ম্যাক্রো তৈরি করা
  • আপনার CEL এক্সপ্রেশন টিউন করার উপায়

আপনি কি প্রয়োজন হবে

পূর্বশর্ত

এই কোডল্যাবটি প্রোটোকল বাফার এবং গো ল্যাং- এর মৌলিক বোঝার উপর ভিত্তি করে তৈরি করে।

আপনি যদি প্রোটোকল বাফারগুলির সাথে পরিচিত না হন তবে প্রথম অনুশীলনটি আপনাকে CEL কীভাবে কাজ করে তার একটি ধারণা দেবে, কিন্তু যেহেতু আরও উন্নত উদাহরণগুলি CEL-এ ইনপুট হিসাবে প্রোটোকল বাফার ব্যবহার করে, সেগুলি বোঝা কঠিন হতে পারে। প্রথমে এই টিউটোরিয়ালগুলির মধ্যে একটির মাধ্যমে কাজ করার কথা বিবেচনা করুন। উল্লেখ্য যে সিইএল ব্যবহার করার জন্য প্রোটোকল বাফারগুলির প্রয়োজন নেই, তবে এই কোডল্যাবে তারা ব্যাপকভাবে ব্যবহৃত হয়।

আপনি পরীক্ষা করে দেখতে পারেন যে গো ইনস্টল করা আছে:

go --help

2. মূল ধারণা

অ্যাপ্লিকেশন

CEL সাধারণ উদ্দেশ্য, এবং RPC রাউটিং থেকে নিরাপত্তা নীতি নির্ধারণ পর্যন্ত বিভিন্ন অ্যাপ্লিকেশনের জন্য ব্যবহার করা হয়েছে। CEL এক্সটেনসিবল, অ্যাপ্লিকেশন অজ্ঞেয়বাদী, এবং কম্পাইল-একবার, মূল্যায়ন-অনেক কর্মপ্রবাহের জন্য অপ্টিমাইজ করা হয়েছে।

অনেক পরিষেবা এবং অ্যাপ্লিকেশন ঘোষণামূলক কনফিগারেশন মূল্যায়ন করে। উদাহরণস্বরূপ, ভূমিকা-ভিত্তিক অ্যাক্সেস কন্ট্রোল (RBAC) হল একটি ঘোষণামূলক কনফিগারেশন যা একটি ভূমিকা এবং ব্যবহারকারীদের একটি সেট দিয়ে একটি অ্যাক্সেস সিদ্ধান্ত তৈরি করে। যদি ঘোষণামূলক কনফিগারেশনগুলি 80% ব্যবহারের ক্ষেত্রে হয়, তাহলে ব্যবহারকারীদের আরও অভিব্যক্তিপূর্ণ শক্তির প্রয়োজন হলে CEL বাকি 20%কে রাউন্ড আউট করার জন্য একটি দরকারী টুল।

সংকলন

একটি পরিবেশের বিরুদ্ধে একটি অভিব্যক্তি সংকলিত হয়। সংকলন ধাপটি প্রোটোবাফ আকারে একটি বিমূর্ত সিনট্যাক্স ট্রি (AST) তৈরি করে। সংকলিত অভিব্যক্তিগুলি সাধারণত যত দ্রুত সম্ভব মূল্যায়ন রাখতে ভবিষ্যতে ব্যবহারের জন্য সংরক্ষণ করা হয়। একটি একক সংকলিত অভিব্যক্তি অনেকগুলি বিভিন্ন ইনপুট দিয়ে মূল্যায়ন করা যেতে পারে।

অভিব্যক্তি

ব্যবহারকারীরা অভিব্যক্তি সংজ্ঞায়িত করে; পরিষেবা এবং অ্যাপ্লিকেশনগুলি পরিবেশকে সংজ্ঞায়িত করে যেখানে এটি চলে। একটি ফাংশন স্বাক্ষর ইনপুট ঘোষণা করে এবং CEL এক্সপ্রেশনের বাইরে লেখা হয়। CEL-তে উপলব্ধ ফাংশনগুলির লাইব্রেরি স্বয়ংক্রিয়ভাবে আমদানি করা হয়।

নিম্নলিখিত উদাহরণে অভিব্যক্তিটি একটি অনুরোধ অবজেক্ট নেয় এবং অনুরোধটিতে একটি দাবির টোকেন অন্তর্ভুক্ত থাকে। অভিব্যক্তিটি একটি বুলিয়ান প্রদান করে যা নির্দেশ করে যে দাবি টোকেন এখনও বৈধ কিনা।

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

পরিবেশ

পরিবেশগুলি পরিষেবা দ্বারা সংজ্ঞায়িত করা হয় । CEL এম্বেড করা পরিষেবা এবং অ্যাপ্লিকেশনগুলি অভিব্যক্তি পরিবেশ ঘোষণা করে। পরিবেশ হল ভেরিয়েবল এবং ফাংশনের সংগ্রহ যা এক্সপ্রেশনে ব্যবহার করা যেতে পারে।

প্রোটো-ভিত্তিক ঘোষণাগুলি সিইএল টাইপ-চেকার দ্বারা ব্যবহার করা হয় যাতে একটি অভিব্যক্তির মধ্যে সমস্ত শনাক্তকারী এবং ফাংশন রেফারেন্স সঠিকভাবে ঘোষণা করা হয় এবং ব্যবহার করা হয়।

একটি অভিব্যক্তি পার্স করার তিনটি ধাপ

একটি অভিব্যক্তি প্রক্রিয়াকরণের তিনটি পর্যায় রয়েছে: পার্স, চেক এবং মূল্যায়ন। CEL-এর জন্য সবচেয়ে সাধারণ প্যাটার্ন হল একটি কন্ট্রোল প্লেনের জন্য কনফিগার করার সময় এক্সপ্রেশনগুলি পার্স এবং চেক করা এবং AST সংরক্ষণ করা।

c71fc08068759f81.png

রানটাইমে, ডেটা প্লেন পুনরুদ্ধার করে এবং বারবার AST মূল্যায়ন করে। CEL রানটাইম দক্ষতার জন্য অপ্টিমাইজ করা হয়েছে, কিন্তু পার্সিং এবং চেকিং লেটেন্সি ক্রিটিক্যাল কোড পাথগুলিতে করা উচিত নয়।

49ab7d8517143b66.png

CEL একটি ANTLR লেক্সার/পার্সার ব্যাকরণ ব্যবহার করে একটি মানব-পাঠযোগ্য অভিব্যক্তি থেকে একটি বিমূর্ত সিনট্যাক্স ট্রিতে পার্স করা হয়েছে। পার্স ফেজটি একটি প্রোটো-ভিত্তিক বিমূর্ত সিনট্যাক্স ট্রি নির্গত করে যেখানে AST-এর প্রতিটি Expr নোডে একটি পূর্ণসংখ্যা আইডি থাকে যা পার্সিং এবং চেক করার সময় উত্পন্ন মেটাডেটাতে সূচক করতে ব্যবহৃত হয়। পার্সিং এর সময় যে syntax.proto তৈরি হয় তা বিশ্বস্ততার সাথে এক্সপ্রেশনের স্ট্রিং ফর্মে যা টাইপ করা হয়েছিল তার বিমূর্ত উপস্থাপনা করে।

একবার একটি এক্সপ্রেশন পার্স করা হয়ে গেলে, এক্সপ্রেশনের সমস্ত ভেরিয়েবল এবং ফাংশন আইডেন্টিফায়ার ঘোষণা করা হয়েছে এবং সঠিকভাবে ব্যবহার করা হচ্ছে তা নিশ্চিত করতে পরিবেশের বিরুদ্ধে পরীক্ষা করা যেতে পারে। টাইপ-চেকার একটি checked.proto তৈরি করে যাতে টাইপ, ভেরিয়েবল এবং ফাংশন রেজোলিউশন মেটাডেটা অন্তর্ভুক্ত থাকে যা মূল্যায়নের দক্ষতাকে ব্যাপকভাবে উন্নত করতে পারে।

CEL মূল্যায়নকারীর 3টি জিনিস প্রয়োজন:

  • যেকোনো কাস্টম এক্সটেনশনের জন্য ফাংশন বাইন্ডিং
  • পরিবর্তনশীল বাঁধাই
  • মূল্যায়ন করার জন্য একটি AST

ফাংশন এবং ভেরিয়েবল বাইন্ডিংগুলি AST কম্পাইল করতে যা ব্যবহার করা হয়েছিল তার সাথে মেলে। এই ইনপুটগুলির যেকোনও একাধিক মূল্যায়ন জুড়ে পুনরায় ব্যবহার করা যেতে পারে, যেমন একটি AST-কে বহু সেট ভেরিয়েবল বাইন্ডিং জুড়ে মূল্যায়ন করা হয়, বা একই ভেরিয়েবলগুলি অনেক AST-এর বিরুদ্ধে ব্যবহৃত হয়, বা একটি প্রক্রিয়ার জীবনকাল জুড়ে ব্যবহৃত ফাংশন বাইন্ডিংগুলি (একটি সাধারণ ক্ষেত্রে) )

3. সেট আপ করুন

এই কোডল্যাবের কোডটি cel-go রেপোর codelab ফোল্ডারে থাকে। সমাধানটি একই রেপোর codelab/solution ফোল্ডারে উপলব্ধ।

রেপোতে ক্লোন এবং সিডি করুন:

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

CEL প্যাকেজগুলো কোথায়?

আপনার সম্পাদকে, codelab/codelab.go খুলুন। আপনার প্রধান ফাংশনটি দেখতে হবে যা এই কোডল্যাবে অনুশীলনগুলি সম্পাদন করে, তারপরে তিনটি ব্লক হেল্পার ফাংশন দ্বারা পরিচালিত হয়। সাহায্যকারীদের প্রথম সেট CEL মূল্যায়নের পর্যায়গুলিতে সহায়তা করে:

  • Compile ফাংশন: পার্স এবং চেক এবং একটি পরিবেশের বিরুদ্ধে ইনপুট এক্সপ্রেশন
  • Eval ফাংশন: একটি ইনপুটের বিপরীতে একটি সংকলিত প্রোগ্রামের মূল্যায়ন করে
  • Report ফাংশন: মূল্যায়নের ফলাফল প্রিটি-প্রিন্ট করে

অতিরিক্তভাবে, বিভিন্ন অনুশীলনের জন্য ইনপুট নির্মাণে সহায়তা করার জন্য request এবং auth সহায়ক সরবরাহ করা হয়েছে।

ব্যায়াম তাদের সংক্ষিপ্ত প্যাকেজ নাম দ্বারা প্যাকেজ উল্লেখ করা হবে. google/cel-go রেপোর মধ্যে প্যাকেজ থেকে উৎসের অবস্থানে ম্যাপিং নীচে রয়েছে যদি আপনি বিশদ বিবরণে খনন করতে চান:

প্যাকেজ

উৎস অবস্থান

বর্ণনা

সেল

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
}

CEL অ্যাপ্লিকেশনগুলি একটি পরিবেশের বিরুদ্ধে একটি অভিব্যক্তি মূল্যায়ন করে। env, err := cel.NewEnv() স্ট্যান্ডার্ড পরিবেশ কনফিগার করে।

কলে cel.EnvOption অপশন প্রদান করে পরিবেশ কাস্টমাইজ করা যায়। এই বিকল্পগুলি ম্যাক্রো অক্ষম করতে, কাস্টম ভেরিয়েবল এবং ফাংশন ঘোষণা করতে সক্ষম হয়।

স্ট্যান্ডার্ড CEL পরিবেশ ভাষা স্পেসিফিকেশনের মধ্যে সংজ্ঞায়িত সমস্ত প্রকার, অপারেটর, ফাংশন এবং ম্যাক্রো সমর্থন করে।

পার্স এবং এক্সপ্রেশন চেক

এনভায়রনমেন্ট কনফিগার করা হয়ে গেলে, এক্সপ্রেশনগুলি পার্স এবং চেক করা যেতে পারে। আপনার ফাংশনে নিম্নলিখিত যোগ করুন:

// 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. একটি ফাংশনে ভেরিয়েবল ব্যবহার করুন

বেশিরভাগ CEL অ্যাপ্লিকেশনগুলি ভেরিয়েবল ঘোষণা করবে যা অভিব্যক্তির মধ্যে উল্লেখ করা যেতে পারে। ভেরিয়েবলের ঘোষণা একটি নাম এবং একটি প্রকার নির্দিষ্ট করে। একটি ভেরিয়েবলের ধরন হয় একটি CEL বিল্টইন টাইপ , একটি প্রোটোকল বাফার সুপরিচিত টাইপ, অথবা যেকোনো প্রোটোবাফ মেসেজ টাইপ হতে পারে যতক্ষণ না এটির বর্ণনাকারীও CEL-কে প্রদান করা হয়।

ফাংশন যোগ করুন

আপনার সম্পাদকে, 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

CEL-এর আরও অনন্য বৈশিষ্ট্যগুলির মধ্যে একটি হল এর কম্যুটেটিভ লজিক্যাল অপারেটরগুলির ব্যবহার। শর্তসাপেক্ষ শাখার উভয় দিক মূল্যায়নকে শর্ট-সার্কিট করতে পারে, এমনকি ত্রুটি বা আংশিক ইনপুটের মুখেও।

অন্য কথায়, CEL একটি মূল্যায়ন আদেশ খুঁজে পায় যা যখনই সম্ভব একটি ফলাফল দেয়, ত্রুটিগুলি উপেক্ষা করে বা এমনকি ডেটা হারিয়ে যা অন্যান্য মূল্যায়ন আদেশে ঘটতে পারে। অ্যাপ্লিকেশানগুলি মূল্যায়নের খরচ কমাতে এই সম্পত্তির উপর নির্ভর করতে পারে, ব্যয়বহুল ইনপুট সংগ্রহকে স্থগিত করে যখন সেগুলি ছাড়াই ফলাফল পাওয়া যায়।

আমরা একটি AND/OR উদাহরণ যোগ করব, এবং তারপর CEL শর্ট-সার্কিট মূল্যায়ন কীভাবে হয় তা বোঝার জন্য বিভিন্ন ইনপুট দিয়ে চেষ্টা করুন।

ফাংশন তৈরি করুন

আপনার সম্পাদকে, অনুশীলন 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 মানগুলিতে, একটি কী উপস্থিত হবে কিনা তা আমরা জানি না। যেহেতু একটি অনুপস্থিত কী এর জন্য একটি নিরাপদ ডিফল্ট মান নেই, তাই CEL ডিফল্ট ত্রুটির জন্য।

8. কাস্টম ফাংশন

যদিও CEL-তে অনেকগুলি অন্তর্নির্মিত ফাংশন অন্তর্ভুক্ত রয়েছে, এমন কিছু ঘটনা রয়েছে যেখানে একটি কাস্টম ফাংশন দরকারী। উদাহরণস্বরূপ, কাস্টম ফাংশনগুলি সাধারণ অবস্থার জন্য ব্যবহারকারীর অভিজ্ঞতা উন্নত করতে বা প্রসঙ্গ-সংবেদনশীল অবস্থা প্রকাশ করতে ব্যবহার করা যেতে পারে

এই অনুশীলনে, আমরা সাধারণভাবে ব্যবহৃত চেকগুলিকে একত্রে প্যাকেজ করার জন্য কীভাবে একটি ফাংশন প্রকাশ করা যায় তা অন্বেষণ করব।

একটি কাস্টম ফাংশন কল করুন

প্রথমে, একটি ওভাররাইড সেট আপ করার জন্য কোড তৈরি করুন যার 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 ফাংশন যোগ করতে হবে যা বর্তমানে অনুরোধ পরিবর্তনশীল ঘোষণা করে।

নিম্নলিখিত 3 লাইন যোগ করে একটি প্যারামিটারাইজড টাইপ ঘোষণা করুন। (এটি সিইএল-এর জন্য যেকোনো ফাংশন ওভারলোডের মতো জটিল)

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

CEL নন-বুলিয়ান আউটপুটও তৈরি করতে পারে, যেমন 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.NewEnv() -তে cel.TimestampType এর now ভেরিয়েবলের জন্য একটি ঘোষণা যোগ করুন এবং আবার চালান:

// 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 রূপান্তর করা প্রয়োজন. এই ক্ষেত্রে অভ্যন্তরীণ CEL উপস্থাপনা হল 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. বিল্ডিং প্রোটোস

CEL অ্যাপ্লিকেশনে সংকলিত যেকোনো বার্তার জন্য প্রোটোবাফ বার্তা তৈরি করতে পারে। একটি ইনপুট 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{
 | .........^

ধারকটি মূলত একটি নামস্থান বা প্যাকেজের সমতুল্য, তবে এটি একটি প্রোটোবাফ বার্তা নামের মতো দানাদারও হতে পারে। প্রদত্ত ভেরিয়েবল, ফাংশন বা টাইপ নাম কোথায় ঘোষণা করা হয়েছে তা নির্ধারণের জন্য সিইএল কন্টেইনারগুলি প্রোটোবুফ এবং সি++ হিসাবে একই নেমস্পেস রেজোলিউশন নিয়ম ব্যবহার করে।

google.rpc.context.AttributeContext কন্টেইনার দেওয়া টাইপ-চেকার এবং মূল্যায়নকারী সমস্ত ভেরিয়েবল, প্রকার এবং ফাংশনের জন্য নিম্নলিখিত শনাক্তকারী নামগুলি চেষ্টা করবে:

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

পরম নামের জন্য, একটি অগ্রণী বিন্দু সহ ভেরিয়েবল, টাইপ বা ফাংশন রেফারেন্স উপসর্গ করুন। উদাহরণে, অভিব্যক্তি .<id> শুধুমাত্র শীর্ষ-স্তরের <id> সনাক্তকারীর জন্য অনুসন্ধান করবে কন্টেইনারের মধ্যে প্রথমে পরীক্ষা না করে।

CEL পরিবেশে 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. ম্যাক্রো

পার্স সময়ে CEL প্রোগ্রাম ম্যানিপুলেট করতে ম্যাক্রো ব্যবহার করা যেতে পারে। ম্যাক্রো একটি কল স্বাক্ষরের সাথে মেলে এবং একটি নতুন সাব এক্সপ্রেশন AST তৈরি করার জন্য ইনপুট কল এবং এর আর্গুমেন্টগুলি ম্যানিপুলেট করে৷

ম্যাক্রোগুলি AST-তে জটিল যুক্তি প্রয়োগ করতে ব্যবহার করা যেতে পারে যা সরাসরি CEL তে লেখা যায় না। উদাহরণস্বরূপ, 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 রেঞ্জের সমস্ত var-এর জন্য cond সঠিক মূল্যায়ন করে কিনা তা পরীক্ষা করুন।

বিদ্যমান

r.exists(var, cond)

r পরিসরের যেকোনো var-এর জন্য cond সঠিক মূল্যায়ন করে কিনা তা পরীক্ষা করুন।

বিদ্যমান_একটি

r.exists_one(var, cond)

r পরিসরে শুধুমাত্র একটি var-এর জন্য cond মূল্যায়ন সত্য কিনা তা পরীক্ষা করুন।

ফিল্টার

r.filter(var, cond)

তালিকার জন্য, একটি নতুন তালিকা তৈরি করুন যেখানে প্রতিটি উপাদান var পরিসীমা r শর্তের শর্তকে সন্তুষ্ট করে। মানচিত্রের জন্য, একটি নতুন তালিকা তৈরি করুন যেখানে প্রতিটি কী var পরিসীমা r শর্তের শর্তকে সন্তুষ্ট করে।

মানচিত্র

r.map(var, expr)

একটি নতুন তালিকা তৈরি করুন যেখানে r পরিসরের প্রতিটি var expr দ্বারা রূপান্তরিত হয়।

r.map(var, cond, expr)

দুই-আর্গ মানচিত্রের মতো কিন্তু মান রূপান্তরিত হওয়ার আগে শর্তসাপেক্ষ কনড ফিল্টার সহ।

আছে

আছে(ab)

মানের উপর b এর উপস্থিতি পরীক্ষা a : মানচিত্রের জন্য, json পরীক্ষা সংজ্ঞা। প্রোটোর জন্য, নন-ডিফল্ট আদিম মান বা একটি বা একটি সেট বার্তা ক্ষেত্র পরীক্ষা করে।

যখন রেঞ্জ r আর্গুমেন্ট একটি map ধরন হয়, তখন var হবে মানচিত্র কী, এবং list প্রকারের মানগুলির জন্য var হবে তালিকা উপাদানের মান। all , exists , exists_one , filter , এবং map ম্যাক্রোগুলি একটি AST পুনর্লিখন সম্পাদন করে যা প্রতিটি পুনরাবৃত্তির জন্য সম্পাদন করে যা ইনপুটের আকার দ্বারা আবদ্ধ থাকে৷

আবদ্ধ বোধগম্যতা নিশ্চিত করে যে CEL প্রোগ্রামগুলি টিউরিং-সম্পূর্ণ হবে না, তবে তারা ইনপুটের ক্ষেত্রে সুপার-লিনিয়ার সময়ে মূল্যায়ন করে। এই ম্যাক্রোগুলি খুব কম ব্যবহার করুন বা একেবারেই না। বোঝার ভারী ব্যবহার সাধারণত একটি ভাল সূচক যে একটি কাস্টম ফাংশন একটি ভাল ব্যবহারকারীর অভিজ্ঞতা এবং আরও ভাল কর্মক্ষমতা প্রদান করবে।

12. টিউনিং

এই মুহুর্তে CEL-Go-এর জন্য একচেটিয়া বৈশিষ্ট্যগুলির একটি মুষ্টিমেয়, কিন্তু যা অন্যান্য CEL বাস্তবায়নের জন্য ভবিষ্যতের পরিকল্পনার ইঙ্গিত দেয়। নিম্নলিখিত অনুশীলন একই AST এর জন্য বিভিন্ন প্রোগ্রাম পরিকল্পনা প্রদর্শন করে:

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

অপ্টিমাইজ করুন

অপ্টিমাইজেশান ফ্ল্যাগ চালু হলে, CEL সময়ের আগে তালিকা তৈরি করতে এবং লিটারেল ম্যাপ করতে অতিরিক্ত সময় ব্যয় করবে এবং নির্দিষ্ট কলগুলিকে অপ্টিমাইজ করবে যেমন অপারেটর একটি সত্যিকারের সেট সদস্যতা পরীক্ষা হতে:

    // 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 প্রথমটিতে অপারেটরের ফলাফলের সাথে মিলে যায়। শাখা, এবং এক্সপ্রেশন আইডি 11 দ্বিতীয়টিতে == অপারেটরের সাথে মিলে যায়। স্বাভাবিক মূল্যায়নের অধীনে, 2 গণনা করার পরে অভিব্যক্তিটি শর্ট-সার্কিট হয়ে যাবে। যদি y uint না থাকত, তবে রাষ্ট্র দুটি কারণ দেখাত কেন অভিব্যক্তি ব্যর্থ হত এবং শুধু একটি নয়।

13. কি আচ্ছাদিত ছিল?

আপনার যদি এক্সপ্রেশন ইঞ্জিনের প্রয়োজন হয়, CEL ব্যবহার করার কথা বিবেচনা করুন। CEL সেই প্রকল্পগুলির জন্য আদর্শ যেখানে ব্যবহারকারীর কনফিগারেশন কার্যকর করতে হবে যেখানে কর্মক্ষমতা গুরুত্বপূর্ণ।

পূর্ববর্তী অনুশীলনে, আমরা আশা করি আপনি CEL-এ আপনার ডেটা পাস করতে এবং আউটপুট বা সিদ্ধান্ত ফিরে পেতে স্বাচ্ছন্দ্য বোধ করেছেন।

আমরা আশা করি আপনি যে ধরনের ক্রিয়াকলাপগুলি করতে পারেন, বুলিয়ান সিদ্ধান্ত থেকে শুরু করে JSON এবং Protobuffer বার্তাগুলি তৈরি করার বিষয়ে আপনার ধারণা আছে৷

আমরা আশা করি অভিব্যক্তিগুলির সাথে কীভাবে কাজ করতে হয় এবং তারা কী করছে সে সম্পর্কে আপনার ধারণা আছে৷ এবং আমরা এটি প্রসারিত করার সাধারণ উপায় বুঝতে পারি।