১. ভূমিকা
CEL কী?
CEL হলো একটি নন-টিউরিং কমপ্লিট এক্সপ্রেশন ল্যাঙ্গুয়েজ, যা দ্রুত, পোর্টেবল এবং নিরাপদে কার্যকর করার জন্য ডিজাইন করা হয়েছে। CEL এককভাবে অথবা কোনো বৃহত্তর পণ্যের সাথে সংযুক্ত করে ব্যবহার করা যায়।
CEL এমন একটি ভাষা হিসেবে ডিজাইন করা হয়েছে যেখানে ব্যবহারকারীর কোড নিরাপদে চালানো যায়। যদিও কোনো ব্যবহারকারীর পাইথন কোডে নির্বিচারে eval() কল করা বিপজ্জনক, আপনি ব্যবহারকারীর CEL কোড নিরাপদে চালাতে পারেন। এবং যেহেতু CEL এমন আচরণ প্রতিরোধ করে যা এর পারফরম্যান্স কমিয়ে দিতে পারে, তাই এটি ন্যানোসেকেন্ড থেকে মাইক্রোসেকেন্ডের মধ্যে নিরাপদে ইভ্যালুয়েট হয়; এটি পারফরম্যান্স-নির্ভর অ্যাপ্লিকেশনগুলির জন্য আদর্শ।
CEL এক্সপ্রেশন মূল্যায়ন করে, যা এক লাইনের ফাংশন বা ল্যাম্বডা এক্সপ্রেশনের অনুরূপ। যদিও CEL সাধারণত বুলিয়ান সিদ্ধান্তের জন্য ব্যবহৃত হয়, এটি JSON বা প্রোটোবাফ মেসেজের মতো আরও জটিল অবজেক্ট তৈরি করতেও ব্যবহার করা যেতে পারে।
আপনার প্রকল্পের জন্য CEL কি উপযুক্ত?
যেহেতু CEL ন্যানোসেকেন্ড থেকে মাইক্রোসেকেন্ডের মধ্যে AST থেকে একটি এক্সপ্রেশন মূল্যায়ন করে, তাই CEL-এর আদর্শ ব্যবহার হলো পারফরম্যান্স-ক্রিটিক্যাল পাথযুক্ত অ্যাপ্লিকেশনগুলিতে। ক্রিটিক্যাল পাথে CEL কোডকে AST-তে কম্পাইল করা উচিত নয়; আদর্শ অ্যাপ্লিকেশন হলো সেগুলো, যেখানে কনফিগারেশনটি প্রায়শই এক্সিকিউট করা হয় এবং তুলনামূলকভাবে কম পরিবর্তন করা হয়।
উদাহরণস্বরূপ, কোনো সার্ভিসে প্রতিটি HTTP অনুরোধের সাথে একটি নিরাপত্তা নীতি কার্যকর করা CEL-এর জন্য একটি আদর্শ ব্যবহার ক্ষেত্র, কারণ নিরাপত্তা নীতিটি খুব কমই পরিবর্তিত হয় এবং প্রতিক্রিয়ার সময়ের উপর CEL-এর প্রভাব নগণ্য থাকে। এই ক্ষেত্রে, অনুরোধটি অনুমোদিত হবে কি না, তা বোঝাতে CEL একটি বুলিয়ান মান ফেরত দেয়, তবে এটি আরও জটিল কোনো বার্তাও ফেরত দিতে পারত।
এই কোডল্যাবে কী কী অন্তর্ভুক্ত রয়েছে?
এই কোডল্যাবের প্রথম ধাপে CEL ব্যবহারের পেছনের উদ্দেশ্য এবং এর মূল ধারণাগুলো আলোচনা করা হয়েছে। বাকি অংশে সাধারণ ব্যবহারের ক্ষেত্রগুলো নিয়ে কোডিং অনুশীলনী রয়েছে। ভাষা, শব্দার্থ এবং বৈশিষ্ট্যগুলো সম্পর্কে আরও বিস্তারিত জানতে GitHub-এ থাকা CEL ল্যাঙ্গুয়েজ ডেফিনিশন এবং CEL Go ডক্স দেখুন।
এই কোডল্যাবটি সেইসব ডেভেলপারদের জন্য তৈরি করা হয়েছে, যারা CEL সমর্থিত সার্ভিসগুলো ব্যবহার করার জন্য CEL শিখতে চান। এই কোডল্যাবে আপনার নিজের প্রোজেক্টে কীভাবে CEL ইন্টিগ্রেট করবেন, তা শেখানো হয়নি।
আপনি যা শিখবেন
- CEL থেকে মূল ধারণা
- হ্যালো, ওয়ার্ল্ড: একটি স্ট্রিং মূল্যায়ন করতে CEL ব্যবহার
- ভেরিয়েবল তৈরি করা
- লজিক্যাল AND/OR অপারেশনে CEL-এর শর্ট-সার্কিটিং বোঝা
- JSON তৈরি করতে CEL কীভাবে ব্যবহার করবেন
- প্রোটোবাফার তৈরি করতে CEL কীভাবে ব্যবহার করবেন
- ম্যাক্রো তৈরি করা
- আপনার CEL এক্সপ্রেশন টিউন করার উপায়
আপনার যা যা লাগবে
পূর্বশর্ত
এই কোডল্যাবটি প্রোটোকল বাফার এবং গো ল্যাং সম্পর্কে প্রাথমিক ধারণার উপর ভিত্তি করে তৈরি করা হয়েছে।
আপনি যদি প্রোটোকল বাফার সম্পর্কে পরিচিত না হন, তাহলে প্রথম অনুশীলনটি আপনাকে CEL কীভাবে কাজ করে সে সম্পর্কে একটি ধারণা দেবে, কিন্তু যেহেতু আরও উন্নত উদাহরণগুলিতে CEL-এর ইনপুট হিসাবে প্রোটোকল বাফার ব্যবহার করা হয়েছে, তাই সেগুলি বোঝা আরও কঠিন হতে পারে। প্রথমে এই টিউটোরিয়ালগুলির মধ্যে একটি সম্পন্ন করার কথা বিবেচনা করুন। উল্লেখ্য যে, CEL ব্যবহার করার জন্য প্রোটোকল বাফার আবশ্যক নয়, কিন্তু এই কোডল্যাবে এটি ব্যাপকভাবে ব্যবহৃত হয়েছে।
go ইনস্টল করা আছে কিনা তা পরীক্ষা করতে, নিম্নলিখিত কমান্ডটি চালান:
go --help
২. মূল ধারণা
অ্যাপ্লিকেশন
CEL একটি সাধারণ উদ্দেশ্যমূলক টুল, এবং এটি RPC রাউটিং থেকে শুরু করে নিরাপত্তা নীতি নির্ধারণ পর্যন্ত বিভিন্ন ক্ষেত্রে ব্যবহৃত হয়ে আসছে। CEL সম্প্রসারণযোগ্য, অ্যাপ্লিকেশন-নিরপেক্ষ, এবং একবার কম্পাইল করে বহুবার মূল্যায়ন করার কর্মপ্রবাহের জন্য বিশেষভাবে তৈরি।
অনেক পরিষেবা এবং অ্যাপ্লিকেশন ডিক্লারেটিভ কনফিগারেশন মূল্যায়ন করে। উদাহরণস্বরূপ, রোল-বেসড অ্যাক্সেস কন্ট্রোল (RBAC) হলো একটি ডিক্লারেটিভ কনফিগারেশন যা একটি রোল এবং একদল ব্যবহারকারীর উপর ভিত্তি করে অ্যাক্সেসের সিদ্ধান্ত তৈরি করে। যদি ডিক্লারেটিভ কনফিগারেশন ৮০% ব্যবহারের ক্ষেত্র হয়, তবে বাকি ২০% পূরণ করার জন্য CEL একটি দরকারি টুল, যখন ব্যবহারকারীদের আরও বেশি প্রকাশক্ষমতার প্রয়োজন হয়।
সংকলন
একটি এক্সপ্রেশনকে একটি এনভায়রনমেন্টের সাপেক্ষে কম্পাইল করা হয়। কম্পাইলেশন ধাপটি প্রোটোবাফ আকারে একটি অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি (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 টাইপ-চেকার প্রোটো-ভিত্তিক ডিক্লারেশনগুলো ব্যবহার করে।
একটি এক্সপ্রেশন পার্স করার তিনটি পর্যায়
একটি এক্সপ্রেশন প্রসেস করার তিনটি পর্যায় রয়েছে: পার্স, চেক এবং ইভ্যালুয়েট। CEL-এর জন্য সবচেয়ে প্রচলিত প্যাটার্নটি হলো, কন্ট্রোল প্লেন কনফিগারেশনের সময় এক্সপ্রেশনগুলো পার্স ও চেক করে এবং AST সংরক্ষণ করে।

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

একটি ANTLR লেক্সার / পার্সার গ্রামার ব্যবহার করে CEL-কে মানুষের পাঠযোগ্য এক্সপ্রেশন থেকে একটি অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি-তে পার্স করা হয়। পার্সিং পর্যায়টি একটি প্রোটো-ভিত্তিক অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি তৈরি করে, যেখানে AST-এর প্রতিটি Expr নোডে একটি পূর্ণসংখ্যা আইডি থাকে যা পার্সিং এবং চেকিংয়ের সময় তৈরি হওয়া মেটাডেটাকে ইনডেক্স করতে ব্যবহৃত হয়। পার্সিংয়ের সময় তৈরি হওয়া syntax.proto ফাইলটি এক্সপ্রেশনটির স্ট্রিং আকারে টাইপ করা বিষয়বস্তুর অ্যাবস্ট্রাক্ট উপস্থাপনাকে বিশ্বস্তভাবে তুলে ধরে।
কোনো এক্সপ্রেশন পার্স করার পর, সেটির মধ্যে থাকা সমস্ত ভেরিয়েবল ও ফাংশন আইডেন্টিফায়ার ডিক্লেয়ার করা হয়েছে এবং সঠিকভাবে ব্যবহৃত হচ্ছে কি না, তা নিশ্চিত করার জন্য সেটিকে এনভায়রনমেন্টের সাথে মিলিয়ে দেখা হতে পারে। টাইপ-চেকার একটি checked.proto ফাইল তৈরি করে, যাতে টাইপ, ভেরিয়েবল এবং ফাংশন রেজোলিউশন মেটাডেটা অন্তর্ভুক্ত থাকে, যা ইভ্যালুয়েশন দক্ষতা ব্যাপকভাবে উন্নত করতে পারে।
সিইএল মূল্যায়নকারীর ৩টি জিনিস প্রয়োজন:
- যেকোনো কাস্টম এক্সটেনশনের জন্য ফাংশন বাইন্ডিং
- পরিবর্তনশীল বাইন্ডিং
- মূল্যায়ন করার জন্য একটি AST
ফাংশন এবং ভেরিয়েবল বাইন্ডিংগুলো অবশ্যই AST কম্পাইল করার সময় যা ব্যবহার করা হয়েছিল তার সাথে মিলতে হবে। এই ইনপুটগুলোর যেকোনোটি একাধিক ইভ্যালুয়েশনের ক্ষেত্রে পুনরায় ব্যবহার করা যেতে পারে; যেমন, একটি AST-কে একাধিক ভেরিয়েবল বাইন্ডিং সেটের মাধ্যমে ইভ্যালুয়েট করা, অথবা একই ভেরিয়েবল একাধিক AST-এর ক্ষেত্রে ব্যবহার করা, কিংবা একটি প্রসেসের জীবনকাল জুড়ে ফাংশন বাইন্ডিংগুলো ব্যবহার করা (যা একটি সাধারণ ঘটনা)।
৩. সেট আপ করুন
এই কোডল্যাবের কোডটি 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 ===
CEL প্যাকেজগুলো কোথায় আছে?
আপনার এডিটরে codelab/codelab.go খুলুন। আপনি main ফাংশনটি দেখতে পাবেন, যা এই কোডল্যাবের অনুশীলনীগুলো সম্পাদনের কাজ করে। এর পরে তিনটি হেল্পার ফাংশনের ব্লক রয়েছে। হেল্পারগুলোর প্রথম সেটটি CEL ইভ্যালুয়েশনের বিভিন্ন ধাপে সহায়তা করে:
-
Compileফাংশন: একটি এনভায়রনমেন্টের সাপেক্ষে ইনপুট এক্সপ্রেশন পার্স ও চেক করে। -
Evalফাংশন: একটি ইনপুটের সাপেক্ষে কম্পাইল করা প্রোগ্রামের মূল্যায়ন করে। -
Reportফাংশন: মূল্যায়নের ফলাফল সুন্দরভাবে প্রিন্ট করে।
এছাড়াও, বিভিন্ন অনুশীলনের জন্য ইনপুট তৈরিতে সহায়তা করার জন্য request এবং auth হেল্পার প্রদান করা হয়েছে।
অনুশীলনীতে প্যাকেজগুলোকে তাদের সংক্ষিপ্ত নাম দিয়ে উল্লেখ করা হবে। আপনি যদি বিস্তারিত জানতে চান, তাহলে google/cel-go রিপো-এর মধ্যে প্যাকেজ থেকে সোর্স লোকেশনের ম্যাপিং নিচে দেওয়া হলো:
প্যাকেজ | উৎস অবস্থান | বর্ণনা |
সেল | শীর্ষ-স্তরের ইন্টারফেস | |
রেফারেন্স | রেফারেন্স ইন্টারফেস | |
প্রকার | রানটাইম টাইপ মান |
৪. হ্যালো, ওয়ার্ল্ড!
সকল প্রোগ্রামিং ভাষার ঐতিহ্য অনুসরণ করে, আমরা "Hello World!" তৈরি এবং মূল্যায়ন করার মাধ্যমে শুরু করব।
পরিবেশ কনফিগার করুন
আপনার এডিটরে, 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() এর মান nil না হয়, তাহলে সিনট্যাক্স বা সিম্যান্টিক্সে কোনো ত্রুটি থাকে এবং প্রোগ্রামটি আর অগ্রসর হতে পারে না। যখন এক্সপ্রেশনটি সুগঠিত হয়, তখন এই কলগুলোর ফলাফল হিসেবে একটি এক্সিকিউটেবল 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)
৫. ফাংশনে ভেরিয়েবল ব্যবহার করুন
বেশিরভাগ 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'
| ^
টাইপ-চেকারটি রিকোয়েস্ট অবজেক্টটির জন্য একটি এরর তৈরি করে, যেখানে সুবিধাজনকভাবে সেই সোর্স কোড স্নিপেটটিও অন্তর্ভুক্ত থাকে যেখানে এররটি ঘটে।
৬. ভেরিয়েবলগুলো ঘোষণা করুন।
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'
| .......^
প্রোটোবাফ মেসেজ নির্দেশ করে এমন ভেরিয়েবল ব্যবহার করার জন্য, টাইপ-চেকারকে টাইপ ডেসক্রিপ্টরটিও জানতে হয়।
আপনার ফাংশনে request-এর ডেসক্রিপ্টর নির্ধারণ করতে 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)
পুনরালোচনা করলে দেখা যায়, আমরা এইমাত্র একটি ত্রুটির জন্য একটি ভেরিয়েবল ঘোষণা করেছি, সেটিকে একটি টাইপ ডেসক্রিপ্টর দিয়েছি এবং তারপরে এক্সপ্রেশন ইভ্যালুয়েশনে ভেরিয়েবলটিকে রেফারেন্স করেছি।
৭. যৌক্তিক AND/OR
CEL-এর অন্যতম অনন্য বৈশিষ্ট্য হলো এর বিনিময়যোগ্য লজিক্যাল অপারেটরের ব্যবহার। একটি কন্ডিশনাল ব্রাঞ্চের উভয় পাশই ইভ্যালুয়েশনকে শর্ট-সার্কিট করতে পারে, এমনকি ত্রুটি বা আংশিক ইনপুটের ক্ষেত্রেও।
অন্য কথায়, CEL এমন একটি মূল্যায়ন ক্রম খুঁজে বের করে যা যখনই সম্ভব একটি ফলাফল দেয়, এবং অন্যান্য মূল্যায়ন ক্রমে ঘটতে পারে এমন ত্রুটি বা এমনকি অনুপস্থিত ডেটাকেও উপেক্ষা করে। অ্যাপ্লিকেশনগুলি মূল্যায়নের খরচ কমানোর জন্য এই বৈশিষ্ট্যের উপর নির্ভর করতে পারে, এবং যখন ব্যয়বহুল ইনপুট ছাড়াই ফলাফল পাওয়া সম্ভব হয়, তখন সেগুলি সংগ্রহ করা স্থগিত রাখতে পারে।
আমরা একটি AND/OR উদাহরণ যোগ করব এবং তারপরে CEL কীভাবে মূল্যায়নকে শর্ট-সার্কিট করে তা বোঝার জন্য বিভিন্ন ইনপুট দিয়ে এটি চেষ্টা করব।
ফাংশনটি তৈরি করুন
আপনার এডিটরে, অনুশীলনী ৩-এ নিম্নলিখিত বিষয়বস্তু যোগ করুন:
// 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 স্টেটমেন্টটি যুক্ত করুন যা true রিটার্ন করবে যদি ব্যবহারকারী 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
প্রোটোবাফে আমরা জানি কী কী ফিল্ড এবং টাইপ আশা করা যায়। ম্যাপ এবং জেসন ভ্যালুতে, কোনো কী উপস্থিত থাকবে কি না তা আমরা জানি না। যেহেতু কোনো কী অনুপস্থিত থাকলে তার জন্য কোনো নিরাপদ ডিফল্ট ভ্যালু নেই, তাই CEL ডিফল্টভাবে এরর দেখায়।
৮. কাস্টম ফাংশন
যদিও 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 ফাংশনটি যোগ করতে হবে, যেখানে বর্তমানে `request` ভেরিয়েবলটি ডিক্লেয়ার করা আছে।
নিম্নলিখিত ৩টি লাইন যোগ করে একটি প্যারামিটারাইজড টাইপ ঘোষণা করুন। (CEL-এর জন্য যেকোনো ফাংশন ওভারলোড এর চেয়ে বেশি জটিল হবে না)
// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
fmt.Println("=== Exercise 4: Customization ===\n")
// Determine whether an optional claim is set to the proper value. The custom
// map.contains(key, value) function is used as an alternative to:
// key in map && map[key] == value
// Useful components of the type-signature for 'contains'.
typeParamA := cel.TypeParamType("A")
typeParamB := cel.TypeParamType("B")
mapAB := cel.MapType(typeParamA, typeParamB)
env, _ := cel.NewEnv(
cel.Types(&rpcpb.AttributeContext_Request{}),
// Declare the request.
cel.Variable("request",
cel.ObjectType("google.rpc.context.AttributeContext.Request"),
),
// Add the custom function declaration and binding here with cel.Function()
)
ast := compile(env,
`request.auth.claims.contains('group', 'admin')`,
cel.BoolType)
// Construct the program plan.
// Output: false
program, _ := env.Program(ast)
emptyClaims := map[string]string{}
eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
fmt.Println()
}
কাস্টম ফাংশন যোগ করুন
এরপরে, আমরা একটি নতুন 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 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)
দাবিটি বিদ্যমান থাকলে কী হয়?
অতিরিক্ত ক্রেডিটের জন্য, ইনপুটে অ্যাডমিন ক্লেইমটি সেট করে যাচাই করুন যে ক্লেইমটি বিদ্যমান থাকলে 'contains' ওভারলোডটিও 'true' রিটার্ন করে। আপনি নিম্নলিখিত আউটপুটটি দেখতে পাবেন:
=== 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() একটি রানটাইম টাইপ গার্ড যোগ করে, যা নিশ্চিত করে যে রানটাইম কন্ট্রাক্টটি এনভায়রনমেন্টের টাইপ-চেক করা ডিক্লারেশনের সাথে মেলে।
৯. 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"
}
১০. প্রোটো তৈরি করা
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{
| .........^
কন্টেইনার মূলত একটি নেমস্পেস বা প্যাকেজের সমতুল্য, তবে এটি এমনকি একটি প্রোটোবাফ মেসেজ নামের মতো সূক্ষ্মও হতে পারে। কোনো নির্দিষ্ট ভেরিয়েবল, ফাংশন বা টাইপের নাম কোথায় ডিক্লেয়ার করা হয়েছে তা নির্ধারণ করার জন্য CEL কন্টেইনারগুলো প্রোটোবাফ এবং C++ এর মতোই একই নেমস্পেস রেজোলিউশন নিয়ম ব্যবহার করে।
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() কল করে টাইপটি আনর্যাপ করে দেখুন ফলাফল কীভাবে পরিবর্তিত হয়।
১১. ম্যাক্রো
পার্স করার সময়ে CEL প্রোগ্রামকে পরিবর্তন করার জন্য ম্যাক্রো ব্যবহার করা যায়। ম্যাক্রো একটি কল সিগনেচারের সাথে মিল রেখে ইনপুট কল এবং এর আর্গুমেন্টগুলোকে পরিবর্তন করে একটি নতুন সাবএক্সপ্রেশন AST তৈরি করে।
AST-তে এমন জটিল লজিক প্রয়োগ করার জন্য ম্যাক্রো ব্যবহার করা যেতে পারে যা সরাসরি CEL-এ লেখা যায় না। উদাহরণস্বরূপ, ` has ম্যাক্রো ফিল্ডের উপস্থিতি পরীক্ষা করতে সক্ষম করে। `exists` এবং `all`-এর মতো কম্প্রিহেনশন ম্যাক্রোগুলো একটি ফাংশন কলকে ইনপুট লিস্ট বা ম্যাপের উপর বাউন্ডেড ইটারেশন দ্বারা প্রতিস্থাপন করে। এই ধারণাগুলোর কোনোটিই সিনট্যাকটিক স্তরে সম্ভব নয়, কিন্তু ম্যাক্রো এক্সপ্যানশনের মাধ্যমে এগুলো সম্ভব।
পরবর্তী অনুশীলনটি যোগ করুন এবং চালান:
// 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 পরিসরের অন্তর্গত যেকোনো ভেরিয়েবলের জন্য cond সত্য হয় কিনা তা পরীক্ষা করুন। |
এক বিদ্যমান | r.exists_one(var, cond) | r পরিসরের অন্তর্গত ভেরিয়েবলগুলোর মধ্যে শুধুমাত্র একটির জন্য cond সত্য হয় কিনা তা পরীক্ষা করুন। |
ফিল্টার | r.filter(var, cond) | তালিকার জন্য, একটি নতুন তালিকা তৈরি করুন যেখানে r পরিসরের অন্তর্গত var উপাদানটি cond শর্তটি পূরণ করে। ম্যাপের জন্য, একটি নতুন তালিকা তৈরি করুন যেখানে r পরিসরের অন্তর্গত var কী-টি cond শর্তটি পূরণ করে। |
মানচিত্র | r.map(var, expr) | একটি নতুন তালিকা তৈরি করুন যেখানে r পরিসরের প্রতিটি ভেরিয়েবল expr দ্বারা রূপান্তরিত হয়। |
r.map(var, cond, expr) | দুই-আর্গুমেন্টের ম্যাপের মতোই, তবে মানটি রূপান্তরিত হওয়ার আগে একটি শর্তসাপেক্ষ ফিল্টার ব্যবহার করা হয়। | |
আছে | হাস(আব) | 'a' মানের উপর 'b'-এর উপস্থিতি পরীক্ষা: ম্যাপের ক্ষেত্রে, JSON সংজ্ঞা পরীক্ষা করে। প্রোটোর ক্ষেত্রে, এটি নন-ডিফল্ট প্রিমিটিভ মান, 'a' অথবা একটি সেট মেসেজ ফিল্ড পরীক্ষা করে। |
যখন রেঞ্জ r আর্গুমেন্টটি একটি map টাইপের হয়, তখন var হবে ম্যাপের কী, এবং list টাইপের ভ্যালুগুলোর জন্য var হবে লিস্টের এলিমেন্টের ভ্যালু। all , exists , exists_one , filter , এবং map ম্যাক্রোগুলো একটি AST রিরাইট সম্পাদন করে, যা ইনপুটের সাইজ দ্বারা সীমাবদ্ধ একটি for-each ইটারেশন চালায়।
বাউন্ডেড কম্প্রিহেনশনগুলো নিশ্চিত করে যে CEL প্রোগ্রামগুলো টিউরিং-কমপ্লিট হবে না, কিন্তু এগুলো ইনপুটের সাপেক্ষে সুপার-লিনিয়ার সময়ে ইভ্যালুয়েট হয়। এই ম্যাক্রোগুলো খুব কম ব্যবহার করুন অথবা একেবারেই ব্যবহার করবেন না। কম্প্রিহেনশনের ব্যাপক ব্যবহার সাধারণত একটি ভালো ইঙ্গিত দেয় যে একটি কাস্টম ফাংশন আরও ভালো ইউজার এক্সপেরিয়েন্স এবং উন্নত পারফরম্যান্স প্রদান করবে।
১২. টিউনিং
বর্তমানে এমন কিছু বৈশিষ্ট্য রয়েছে যা শুধুমাত্র 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 আগে থেকেই লিস্ট এবং ম্যাপ লিটারেল তৈরি করতে অতিরিক্ত সময় ব্যয় করবে এবং '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)
যখন একই প্রোগ্রামকে বিভিন্ন ইনপুটের সাপেক্ষে বহুবার মূল্যায়ন করা হয়, তখন অপ্টিমাইজ করা একটি ভালো উপায়। তবে, যখন প্রোগ্রামটি কেবল একবারই মূল্যায়ন করা হবে, তখন অপ্টিমাইজ করলে তা কেবল বাড়তি বোঝাই সৃষ্টি করবে।
পুঙ্খানুপুঙ্খ মূল্যায়ন
এক্সপ্রেশন ইভ্যালুয়েশনের আচরণ ডিবাগ করার জন্য এক্সহস্টিভ ইভ্যাল (Exhaustive Eval) কার্যকর হতে পারে, কারণ এটি এক্সপ্রেশন ইভ্যালুয়েশনের প্রতিটি ধাপে পর্যবেক্ষণকৃত মান সম্পর্কে ধারণা দেয়।
// 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)
এক্সপ্রেশন আইডি ২ প্রথম ব্রাঞ্চের 'in' অপারেটরের ফলাফলের সাথে এবং এক্সপ্রেশন আইডি ১১ দ্বিতীয় ব্রাঞ্চের '==' অপারেটরের সাথে সম্পর্কিত। স্বাভাবিক ইভ্যালুয়েশনের অধীনে, ২ গণনা করার পরেই এক্সপ্রেশনটি শর্ট-সার্কিট হয়ে যেত। যদি 'y' একটি 'uint' না হতো, তাহলে স্টেটটি এক্সপ্রেশনটি ব্যর্থ হওয়ার একটির পরিবর্তে দুটি কারণ দেখাতো।
১৩. কী কী বিষয় আলোচনা করা হয়েছিল?
আপনার যদি একটি এক্সপ্রেশন ইঞ্জিনের প্রয়োজন হয়, তবে CEL ব্যবহার করার কথা বিবেচনা করতে পারেন। যেসব প্রোজেক্টে ব্যবহারকারীর কনফিগারেশন কার্যকর করার প্রয়োজন হয় এবং যেখানে পারফরম্যান্স অত্যন্ত গুরুত্বপূর্ণ, সেগুলোর জন্য CEL একটি আদর্শ মাধ্যম।
পূর্ববর্তী অনুশীলনগুলোর মাধ্যমে, আমরা আশা করি আপনারা CEL-এ আপনাদের ডেটা প্রবেশ করানো এবং আউটপুট বা সিদ্ধান্তটি ফেরত পাওয়ার বিষয়ে স্বচ্ছন্দ হয়েছেন।
আমরা আশা করি, বুলিয়ান সিদ্ধান্ত নেওয়া থেকে শুরু করে JSON এবং প্রোটোবাফার মেসেজ তৈরি করা পর্যন্ত আপনি কী ধরনের অপারেশন করতে পারেন, সে সম্পর্কে আপনার ধারণা আছে।
আমরা আশা করি, এক্সপ্রেশনগুলো কীভাবে ব্যবহার করতে হয় এবং সেগুলো কী কাজ করে, সে সম্পর্কে আপনার একটি ধারণা আছে। এবং এটিকে সম্প্রসারিত করার সাধারণ উপায়গুলোও আমরা বুঝি।