From ed6bc42e80e751f54a0804719193c4c5063291bb Mon Sep 17 00:00:00 2001 From: namit goel <53664454+namitgoel@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:45:21 +0530 Subject: [PATCH] feat: adding lua cac and experimentation client wrapper --- clients/lua/Readme.md | 33 ++++++ clients/lua/cacclient/CacClient.lua | 106 +++++++++++++++++ .../lua/expclient/ExperimentationClient.lua | 107 ++++++++++++++++++ clients/lua/main.lua | 14 +++ 4 files changed, 260 insertions(+) create mode 100644 clients/lua/Readme.md create mode 100644 clients/lua/cacclient/CacClient.lua create mode 100644 clients/lua/expclient/ExperimentationClient.lua create mode 100644 clients/lua/main.lua diff --git a/clients/lua/Readme.md b/clients/lua/Readme.md new file mode 100644 index 00000000..b4c9c8a3 --- /dev/null +++ b/clients/lua/Readme.md @@ -0,0 +1,33 @@ +Set directory path that contains superposition object files in SUPERPOSITION_LIB_PATH env variable; + +## [ CAC Client ](./cacclient/CacClient.lua) + +1. This exports a class that exposes functions that internally call rust functions. +2. For Different platform it read different superposition object files. + * For Mac -> libcac_client.dylib + * For Windows -> libcac_client.so + * For Linux -> libcac_client.dll +3. This run CAC CLient in two thread one is main thread another is worker thread. +4. Polling updates for config are done on different thread. ([ref](./cacclient/CacClient.lua#78)). + + +## [ Experimentation Client ](./expclient/ExperimentationClient.lua) + +1. This exports a class that exposes functions that internally call rust functions. +2. For Different platform it read different superposition object files. + * For Mac -> libexperimentation_client.dylib + * For Windows -> libexperimentation_client.so + * For Linux -> libexperimentation_client.dll +3. This run Experimentation CLient in two thread one is main thread another is worker thread. +4. Polling updates for experiments are done on different thread. ([ref](./expclient/ExperimentationClient.lua#80)). + + +## [ Test ](./main.lua) + +1. To test this sample project follow below steps. + * Run superposition client. + * Run **luajit main.lua** . +2. By Default this sample code uses [dev](./main.lua#4) tenant. +3. By Default this sample code assumes superposition is running on [8080](./main.lua#6) port. +3. By Default this sample code polls superposition every [1 second](./main.lua#L5) port. +4. This sample code creates both [CAC CLient](./main.lua#12) and [Experimentation Client](./main.lua#8) with above default values. \ No newline at end of file diff --git a/clients/lua/cacclient/CacClient.lua b/clients/lua/cacclient/CacClient.lua new file mode 100644 index 00000000..d61fbf0c --- /dev/null +++ b/clients/lua/cacclient/CacClient.lua @@ -0,0 +1,106 @@ +local ffi = require("ffi") +local platform = string.lower(jit.os) + +local lib_path = os.getenv("SUPERPOSITION_LIB_PATH") +if not lib_path then + error("Environment variable SUPERPOSITION_LIB_PATH is not set") +end + +if platform == "osx" then + lib_path = lib_path .. "/libcac_client.dylib" +elseif platform == "linux" then + lib_path = lib_path .. "/libcac_client.so" +elseif platform == "windows" then + lib_path = lib_path .. "/libcac_client.dll" +else + error("Unsupported platform: " .. platform) +end + + +local lib_path = "/Users/namit.goel/Desktop/repos/namit_superposition/superposition/target/debug/libcac_client.dylib" + +ffi.cdef[[ + int cac_new_client(const char* tenant_name, int polling_frequency, const char* cac_host_name); + const char* cac_get_client(const char* tenant_name); + void cac_start_polling_update(const char* tenant_name); + void cac_free_client(const char* client_ptr); + const char* cac_last_error_message(); + int cac_last_error_length(); + const char* cac_get_config(const char* client_ptr, const char* filter_query, const char* filter_prefix); + void cac_free_string(const char* string); + const char* cac_get_last_modified(const char* client_ptr); + const char* cac_get_resolved_config(const char* client_ptr, const char* query, const char* filter_keys, const char* merge_strategy); + const char* cac_get_default_config(const char* client_ptr, const char* filter_keys); +]] + +local rust_lib = ffi.load(lib_path) + +local CacClient = {} +CacClient.__index = CacClient + +function CacClient:new(tenant_name, polling_frequency, cac_host_name) + assert(tenant_name and #tenant_name > 0, "tenantName cannot be null or empty") + assert(cac_host_name and #cac_host_name > 0, "cacHostName cannot be null or empty") + + local self = setmetatable({}, CacClient) + self.tenant = tenant_name + self.polling_frequency = polling_frequency + self.cac_host_name = cac_host_name + return self +end + +function CacClient:get_cac_last_error_message() + local error_message = rust_lib.cac_last_error_message() + if error_message == nil then + return "No Error" + end + return ffi.string(error_message) +end + +function CacClient:get_cac_last_error_length() + return rust_lib.cac_last_error_length() +end + +function CacClient:get_cac_client() + return ffi.string(rust_lib.cac_get_client(self.tenant)) +end + +function CacClient:create_new_cac_client() + local resp = rust_lib.cac_new_client(self.tenant, self.polling_frequency, self.cac_host_name) + if resp == 1 then + local error_message = self:get_cac_last_error_message() + print("Some Error Occur while creating new client ", error_message) + end + return resp +end + +function CacClient:start_cac_polling_update() + rust_lib.cac_start_polling_update(self.tenant) +end + +function CacClient:get_cac_config(filter_query, filter_prefix) + local client_ptr = self:get_cac_client() + return ffi.string(rust_lib.cac_get_config(client_ptr, filter_query, filter_prefix)) +end + +function CacClient:free_cac_client(client_ptr) + rust_lib.cac_free_client(client_ptr) +end + +function CacClient:free_cac_string(string) + rust_lib.cac_free_string(string) +end + +function CacClient:get_last_modified() + return ffi.string(rust_lib.cac_get_last_modified(self:get_cac_client())) +end + +function CacClient:get_resolved_config(query, filter_keys, merge_strategy) + return ffi.string(rust_lib.cac_get_resolved_config(self:get_cac_client(), query, filter_keys, merge_strategy)) +end + +function CacClient:get_default_config(filter_keys) + return ffi.string(rust_lib.cac_get_default_config(self:get_cac_client(), filter_keys)) +end + +return CacClient \ No newline at end of file diff --git a/clients/lua/expclient/ExperimentationClient.lua b/clients/lua/expclient/ExperimentationClient.lua new file mode 100644 index 00000000..1ad43b0c --- /dev/null +++ b/clients/lua/expclient/ExperimentationClient.lua @@ -0,0 +1,107 @@ +local ffi = require("ffi") +local platform = string.lower(jit.os) + +local lib_path = os.getenv("SUPERPOSITION_LIB_PATH") +if not lib_path then + error("Environment variable SUPERPOSITION_LIB_PATH is not set") +end + +if platform == "osx" then + lib_path = lib_path .. "/libexperimentation_client.dylib" +elseif platform == "linux" then + lib_path = lib_path .. "/libexperimentation_client.so" +elseif platform == "windows" then + lib_path = lib_path .. "/libexperimentation_client.dll" +else + error("Unsupported platform: " .. platform) +end + +ffi.cdef[[ + int expt_new_client(const char* tenant_name, int polling_frequency, const char* cac_host_name); + void expt_start_polling_update(const char* tenant_name); + const char* expt_get_client(const char* tenant_name); + const char* expt_get_applicable_variant(const char* client_ptr, const char* context, int toss); + const char* expt_get_satisfied_experiments(const char* client_ptr, const char* context, const char* filter_prefix); + const char* expt_get_filtered_satisfied_experiments(const char* client_ptr, const char* context, const char* filter_prefix); + const char* expt_get_running_experiments(const char* client_ptr); + void expt_free_string(const char* string); + const char* expt_last_error_message(); + int expt_last_error_length(); + void expt_free_client(const char* client_ptr); +]] + +local rust_lib = ffi.load(lib_path) + +local ExperimentationClient = {} +ExperimentationClient.__index = ExperimentationClient + +function ExperimentationClient:new(tenant_name, polling_frequency, cac_host_name) + assert(tenant_name and #tenant_name > 0, "tenantName cannot be null or empty") + assert(cac_host_name and #cac_host_name > 0, "cacHostName cannot be null or empty") + + local self = setmetatable({}, ExperimentationClient) + self.tenant = tenant_name + self.polling_frequency = polling_frequency + self.cac_host_name = cac_host_name + return self +end + +function ExperimentationClient:get_experimentation_last_error_message() + local error_message = rust_lib.expt_last_error_message(); + if error_message == nil then + return "No Error" + end + return ffi.string(rust_lib.expt_last_error_message()) +end + +function ExperimentationClient:create_new_experimentation_client() + local resp_code = rust_lib.expt_new_client(self.tenant, self.polling_frequency, self.cac_host_name) + if resp_code == 1 then + local error_message = self:get_experimentation_last_error_message() + print("Some error occurred while creating new experimentation client:", error_message) + error("Client Creation Error") + end + return resp_code +end + +function ExperimentationClient:get_experimentation_client() + return ffi.string(rust_lib.expt_get_client(self.tenant)) +end + +function ExperimentationClient:get_running_experiments() + return ffi.string(rust_lib.expt_get_running_experiments(self:get_experimentation_client())) +end + +function ExperimentationClient:free_string(string) + rust_lib.expt_free_string(string) +end + +function ExperimentationClient:start_experimentation_polling_update() + rust_lib.expt_start_polling_update(self.tenant) +end + +function ExperimentationClient:get_experimentation_last_error_length() + return rust_lib.expt_last_error_length() +end + +function ExperimentationClient:free_experimentation_client() + rust_lib.expt_free_client(self:get_experimentation_client()) +end + +function ExperimentationClient:get_filtered_satisfied_experiments(context, filter_prefix) + local resp = rust_lib.expt_get_filtered_satisfied_experiments(self:get_experimentation_client(), context, filter_prefix) + if resp == nil then + return "" + end + return ffi.string(resp) +end + +function ExperimentationClient:get_applicable_variant(context, toss) + return ffi.string(rust_lib.expt_get_applicable_variant(self:get_experimentation_client(), context, toss)) +end + +function ExperimentationClient:get_satisfied_experiments(context, filter_prefix) + return ffi.string(rust_lib.expt_get_satisfied_experiments(self:get_experimentation_client(), context, filter_prefix)) +end + +return ExperimentationClient \ No newline at end of file diff --git a/clients/lua/main.lua b/clients/lua/main.lua new file mode 100644 index 00000000..55c8af28 --- /dev/null +++ b/clients/lua/main.lua @@ -0,0 +1,14 @@ +local CacClient = require("cacclient.CacClient") +local ExperimentationClient = require("expclient.ExperimentationClient") + +local tenant_name = "dev" +local polling_frequency = 1 +local cac_host_name = "http://localhost:8080" + +local exp_client = ExperimentationClient:new(tenant_name, polling_frequency, cac_host_name) +local response = exp_client:create_new_experimentation_client() +print(exp_client:get_satisfied_experiments("{}","")) + +local cac_client = CacClient:new(tenant_name, polling_frequency, cac_host_name) +local response = cac_client:create_new_cac_client() +print(cac_client:get_last_modified()) \ No newline at end of file