diff --git a/clients/go/Readme.md b/clients/go/Readme.md new file mode 100644 index 00000000..77950c23 --- /dev/null +++ b/clients/go/Readme.md @@ -0,0 +1,40 @@ +Set directory path that contains superposition object files in SUPERPOSITION_LIB_PATH env variable; + +Set directory path that contains C header file SUPERPOSITION_INCLUDE_PATH env variable; + + export CGO_CFLAGS="-I${SUPERPOSITION_INCLUDE_PATH}" (Set this flag in order to know go where to pick C header file from). + +## [ CAC Client ](./cacclient/main.go) + +* #### export CGO_LDFLAGS="-L${SUPERPOSITION_LIB_PATH} -lcac_client" (Set this flag in order to know go where to pick superposition object files from). +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/main.go#50)). + + +## [ Experimentation Client ](./expclient/main.go) + +* #### export CGO_LDFLAGS="-L${SUPERPOSITION_LIB_PATH} -lexperimentation_client" (Set this flag in order to know go where to pick superposition object files from). +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/main.go#55)). + + +## [ Test ](./main.go) + +* #### export CGO_LDFLAGS="-L${SUPERPOSITION_LIB_PATH} -lcac_client -lexperimentation_client" (Set this to run sample project) +1. To test this sample project follow below steps. + * Run superposition client. + * Run **go run main.go** . +2. By Default this sample code uses [dev](./main.go#L11) tenant. +3. By Default this sample code assumes superposition is running on [8080](./main.go#L13) port. +3. By Default this sample code polls superposition every [1 second](./main.go#L12) port. +4. This sample code creates both [CAC CLient](./main.go#15) and [Experimentation Client](./main.go#20) with above default values. \ No newline at end of file diff --git a/clients/go/cacclient/main.go b/clients/go/cacclient/main.go new file mode 100644 index 00000000..c793f8a1 --- /dev/null +++ b/clients/go/cacclient/main.go @@ -0,0 +1,158 @@ +package cacclient + +/* +#include "libcac_client.h" +*/ +import "C" +import ( + "encoding/json" + "fmt" + "strings" + "unsafe" +) + +// CacClient struct +type CacClient struct { + tenant string + pollingFrequency int + cacHostName string + delimiter string +} + +// NewCacClient creates a new CacClient +func NewCacClient(tenantName string, pollingFrequency int, cacHostName string) *CacClient { + return &CacClient{tenant: tenantName, pollingFrequency: pollingFrequency, cacHostName: cacHostName, delimiter: ","} +} + +// GetCacLastErrorMessage gets the last error message from the C library +func (c *CacClient) GetCacLastErrorMessage() string { + return C.GoString(C.cac_last_error_message()) +} + +// GetCacLastErrorLength gets the length of the last error message +func (c *CacClient) GetCacLastErrorLength() int { + return int(C.cac_last_error_length()) +} + +// GetCacClient gets the client pointer from the C library +func (c *CacClient) GetCacClient() *C.Arc_Client { + return C.cac_get_client(C.CString(c.tenant)) +} + +// CreateNewCacClient creates a new client in the C library +func (c *CacClient) CreateNewCacClient() int { + resp := C.cac_new_client(C.CString(c.tenant), C.ulong(c.pollingFrequency), C.CString(c.cacHostName)) + if resp == 1 { + errorMessage := c.GetCacLastErrorMessage() + fmt.Printf("Some Error Occur while creating new client: %s\n", errorMessage) + } + return int(resp) +} + +// StartCacPollingUpdate starts polling updates in a separate goroutine +func (c *CacClient) StartCacPollingUpdate() { + tenant := C.CString(c.tenant) + go C.cac_start_polling_update(tenant) +} + +// GetCacConfig gets the configuration from the C library +func (c *CacClient) GetCacConfig(filterQuery *map[string]string, filterPrefix *[]string) string { + var strFilterPrefix, strFilterQuery *string + + if filterPrefix != nil { + val := strings.Join(*filterPrefix, c.delimiter) + strFilterPrefix = &val + } + + if filterQuery != nil { + byteVal, err := json.Marshal(filterQuery) + if err != nil { + fmt.Println("Failed to covert json to string") + return "" + } + strVal := string(byteVal) + strFilterQuery = &strVal + } + clientPtr := c.GetCacClient() + var fp, fq *C.char + + if strFilterPrefix != nil { + fp = C.CString(*strFilterPrefix) + } else { + fp = nil + } + + if strFilterQuery != nil { + fq = C.CString(*strFilterQuery) + } else { + fq = nil + } + return C.GoString(C.cac_get_config(clientPtr, fq, fp)) +} + +// FreeCacClient frees the client in the C library +func (c *CacClient) FreeCacClient(clientPtr string) { + ptr := c.GetCacClient() + C.cac_free_client(ptr) +} + +// FreeCacString frees a string in the C library +func (c *CacClient) FreeCacString(s string) { + cs := C.CString(s) + defer C.free(unsafe.Pointer(cs)) + C.cac_free_string(cs) +} + +// GetLastModified gets the last modified timestamp from the C library +func (c *CacClient) GetLastModified() string { + clientPtr := c.GetCacClient() + return C.GoString(C.cac_get_last_modified(clientPtr)) +} + +// GetResolvedConfig gets the resolved configuration from the C library +func (c *CacClient) GetResolvedConfig(query map[string]string, filterKeys *[]string, mergeStrategy string) string { + + var strfilterKeys *string + + if filterKeys != nil { + val := strings.Join(*filterKeys, c.delimiter) + strfilterKeys = &val + } + + var fk *C.char + if strfilterKeys != nil { + fk = C.CString(*strfilterKeys) + } else { + fk = nil + } + clientPtr := c.GetCacClient() + strQuery, err := json.Marshal(query) + if err != nil { + fmt.Println("Failed to covert json to string") + return "" + } + q := C.CString(string(strQuery)) + ms := C.CString(mergeStrategy) + + return C.GoString(C.cac_get_resolved_config(clientPtr, q, fk, ms)) +} + +// GetDefaultConfig gets the default configuration from the C library +func (c *CacClient) GetDefaultConfig(filterKeys *[]string) string { + clientPtr := c.GetCacClient() + var strfilterKeys *string + + if filterKeys != nil { + val := strings.Join(*filterKeys, c.delimiter) + strfilterKeys = &val + } + + var fk *C.char + if strfilterKeys != nil { + fk = C.CString(*strfilterKeys) + } else { + fk = nil + } + + return C.GoString(C.cac_get_default_config(clientPtr, fk)) +} diff --git a/clients/go/expclient/main.go b/clients/go/expclient/main.go new file mode 100644 index 00000000..5db2a001 --- /dev/null +++ b/clients/go/expclient/main.go @@ -0,0 +1,124 @@ +package expclient + +/* +#include "libexperimentation_client.h" +*/ +import "C" +import ( + "encoding/json" + "fmt" + "strings" +) + +// Define ExperimentationClient structure +type ExperimentationClient struct { + tenant string + pollingFrequency int + cacHostName string + delimiter string +} + +// Constructor +func NewExperimentationClient(tenant string, pollingFrequency int, hostName string) *ExperimentationClient { + return &ExperimentationClient{ + tenant: tenant, + pollingFrequency: pollingFrequency, + cacHostName: hostName, + delimiter: ",", + } +} + +// Methods +func (ec *ExperimentationClient) CreateNewExperimentationClient() int { + respCode := C.expt_new_client(C.CString(ec.tenant), C.ulong(ec.pollingFrequency), C.CString(ec.cacHostName)) + if respCode == 1 { + errorMessage := C.GoString(C.expt_last_error_message()) + fmt.Printf("Error occurred while creating new experimentation client: %s\n", errorMessage) + return int(respCode) + } + return int(respCode) +} + +func (ec *ExperimentationClient) GetExperimentationClient() *C.Arc_Client { + return C.expt_get_client(C.CString(ec.tenant)) +} + +func (ec *ExperimentationClient) GetRunningExperiments() string { + clientPtr := ec.GetExperimentationClient() + return C.GoString(C.expt_get_running_experiments(clientPtr)) +} + +func (ec *ExperimentationClient) FreeString(str string) { + C.expt_free_string(C.CString(str)) +} + +func (ec *ExperimentationClient) StartExperimentationPollingUpdate() { + go C.expt_start_polling_update(C.CString(ec.tenant)) +} + +func (ec *ExperimentationClient) GetExperimentationLastErrorLength() int { + return int(C.expt_last_error_length()) +} + +func (ec *ExperimentationClient) FreeExperimentationClient() { + clientPtr := ec.GetExperimentationClient() + C.expt_free_client(clientPtr) +} + +func (ec *ExperimentationClient) GetFilteredSatisfiedExperiments(context map[string]string, filterPrefix *[]string) string { + clientPtr := ec.GetExperimentationClient() + strContext, err := json.Marshal(context) + if err != nil { + fmt.Println("Failed to covert json to string") + return "" + } + var strFilterPrefix *string + + if filterPrefix != nil { + val := strings.Join(*filterPrefix, ec.delimiter) + strFilterPrefix = &val + } + + var fk *C.char + if strFilterPrefix != nil { + fk = C.CString(*strFilterPrefix) + } else { + fk = nil + } + + return C.GoString(C.expt_get_filtered_satisfied_experiments(clientPtr, C.CString(string(strContext)), fk)) +} + +func (ec *ExperimentationClient) GetApplicableVariant(context map[string]string, toss int) string { + clientPtr := ec.GetExperimentationClient() + strContext, err := json.Marshal(context) + if err != nil { + fmt.Println("Failed to covert json to string") + return "" + } + return C.GoString(C.expt_get_applicable_variant(clientPtr, C.CString(string(strContext)), C.short(toss))) +} + +func (ec *ExperimentationClient) GetSatisfiedExperiments(context map[string]string, filterPrefix *[]string) string { + clientPtr := ec.GetExperimentationClient() + strContext, err := json.Marshal(context) + var strFilterPrefix *string + + if filterPrefix != nil { + val := strings.Join(*filterPrefix, ec.delimiter) + strFilterPrefix = &val + } + + var fk *C.char + if strFilterPrefix != nil { + fk = C.CString(*strFilterPrefix) + } else { + fk = nil + } + + if err != nil { + fmt.Println("Failed to covert json to string") + return "" + } + return C.GoString(C.expt_get_satisfied_experiments(clientPtr, C.CString(string(strContext)), fk)) +} diff --git a/clients/go/go.mod b/clients/go/go.mod new file mode 100644 index 00000000..552e14e4 --- /dev/null +++ b/clients/go/go.mod @@ -0,0 +1,3 @@ +module goclients + +go 1.22.5 diff --git a/clients/go/main.go b/clients/go/main.go new file mode 100644 index 00000000..057fd7de --- /dev/null +++ b/clients/go/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "goclients/cacclient" + "goclients/expclient" +) + +func main() { + // Example usage + tenant := "dev" + pollingFrequency := 1 + cachostName := "http://localhost:8080" + + client := cacclient.NewCacClient(tenant, pollingFrequency, cachostName) + client.CreateNewCacClient() + fmt.Println("Default Configs", client.GetCacConfig(nil, nil)) + + expClient := expclient.NewExperimentationClient(tenant, pollingFrequency, cachostName) + expClient.CreateNewExperimentationClient() + fmt.Println("Running experiments:", expClient.GetRunningExperiments()) +}