Skip to content

Commit

Permalink
feat: list flags + single namespace (#151)
Browse files Browse the repository at this point in the history
* feat(wip): remove passing array of namespace everywhere

* chore: fmt

* feat(wip): return flags list

* chore: add basic test

* chore: rm namespace from requests

* chore: simplify

* chore: fix ruby client/test

* chore: java client

* chore: fmt java

* chore: try to add list_flags to node

* chore: try to ignore unknown flag type property

* chore: try to fix node test

* cleanup memory after use (#154)

* chore: python cleanup

* chore: fix python; test

* chore: go list flags

* chore: fix rust test

* chore: PR suggestions

---------

Co-authored-by: Roman Dmytrenko <[email protected]>
  • Loading branch information
markphelps and erka authored Mar 1, 2024
1 parent 2e89d09 commit cf09c85
Show file tree
Hide file tree
Showing 20 changed files with 542 additions and 405 deletions.
53 changes: 35 additions & 18 deletions flipt-client-go/evaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,12 @@ func NewClient(opts ...clientOption) (*Client, error) {
return nil, err
}

ns := []*C.char{C.CString(client.namespace)}

// Free the memory of the C Strings that were created to initialize the engine.
defer func() {
for _, n := range ns {
C.free(unsafe.Pointer(n))
}
}()

nsPtr := (**C.char)(unsafe.Pointer(&ns[0]))

eng := C.initialize_engine(nsPtr, C.CString(string(b)))
cn := C.CString(client.namespace)
defer C.free(unsafe.Pointer(cn))
co := C.CString(string(b))
defer C.free(unsafe.Pointer(co))

eng := C.initialize_engine(cn, co)
client.engine = eng

return client, nil
Expand Down Expand Up @@ -107,7 +100,7 @@ func WithJWTAuthentication(token string) clientOption {
}
}

// EvaluateVariant makes an evaluation on a variant flag.
// EvaluateVariant performs evaluation for a variant flag.
func (e *Client) EvaluateVariant(_ context.Context, flagKey, entityID string, evalContext map[string]string) (*VariantResult, error) {
ereq, err := json.Marshal(evaluationRequest{
NamespaceKey: e.namespace,
Expand All @@ -119,7 +112,10 @@ func (e *Client) EvaluateVariant(_ context.Context, flagKey, entityID string, ev
return nil, err
}

variant := C.evaluate_variant(e.engine, C.CString(string(ereq)))
cr := C.CString(string(ereq))
defer C.free(unsafe.Pointer(cr))

variant := C.evaluate_variant(e.engine, cr)
defer C.free(unsafe.Pointer(variant))

b := C.GoBytes(unsafe.Pointer(variant), (C.int)(C.strlen(variant)))
Expand All @@ -133,7 +129,7 @@ func (e *Client) EvaluateVariant(_ context.Context, flagKey, entityID string, ev
return vr, nil
}

// EvaluateBoolean makes an evaluation on a boolean flag.
// EvaluateBoolean performs evaluation for a boolean flag.
func (e *Client) EvaluateBoolean(_ context.Context, flagKey, entityID string, evalContext map[string]string) (*BooleanResult, error) {
ereq, err := json.Marshal(evaluationRequest{
NamespaceKey: e.namespace,
Expand All @@ -145,7 +141,10 @@ func (e *Client) EvaluateBoolean(_ context.Context, flagKey, entityID string, ev
return nil, err
}

boolean := C.evaluate_boolean(e.engine, C.CString(string(ereq)))
cr := C.CString(string(ereq))
defer C.free(unsafe.Pointer(cr))

boolean := C.evaluate_boolean(e.engine, cr)
defer C.free(unsafe.Pointer(boolean))

b := C.GoBytes(unsafe.Pointer(boolean), (C.int)(C.strlen(boolean)))
Expand All @@ -159,7 +158,7 @@ func (e *Client) EvaluateBoolean(_ context.Context, flagKey, entityID string, ev
return br, nil
}

// EvaluateBatch makes an evaluation on a batch of flags.
// EvaluateBatch performs evaluation for a batch of flags.
func (e *Client) EvaluateBatch(_ context.Context, requests []*EvaluationRequest) (*BatchResult, error) {
evaluationRequests := make([]*evaluationRequest, 0, len(requests))

Expand All @@ -177,7 +176,10 @@ func (e *Client) EvaluateBatch(_ context.Context, requests []*EvaluationRequest)
return nil, err
}

batch := C.evaluate_batch(e.engine, C.CString(string(requestsBytes)))
cr := C.CString(string(requestsBytes))
defer C.free(unsafe.Pointer(cr))

batch := C.evaluate_batch(e.engine, cr)
defer C.free(unsafe.Pointer(batch))

b := C.GoBytes(unsafe.Pointer(batch), (C.int)(C.strlen(batch)))
Expand All @@ -191,6 +193,21 @@ func (e *Client) EvaluateBatch(_ context.Context, requests []*EvaluationRequest)
return br, nil
}

func (e *Client) ListFlags(_ context.Context) (*ListFlagsResult, error) {
flags := C.list_flags(e.engine)
defer C.free(unsafe.Pointer(flags))

b := C.GoBytes(unsafe.Pointer(flags), (C.int)(C.strlen(flags)))

var fl *ListFlagsResult

if err := json.Unmarshal(b, &fl); err != nil {
return nil, err
}

return fl, nil
}

// Close cleans up the allocated engine as it was initialized in the constructor.
func (e *Client) Close() error {
// Destroy the engine to clean up allocated memory on dynamic library side.
Expand Down
12 changes: 12 additions & 0 deletions flipt-client-go/evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ func TestBatch(t *testing.T) {
assert.Equal(t, "NOT_FOUND_ERROR_EVALUATION_REASON", errorResponse.ErrorEvaluationResponse.Reason)
}

func TestListFlags(t *testing.T) {
evaluationClient, err := flipt.NewClient(flipt.WithURL(fliptUrl), flipt.WithClientTokenAuthentication(authToken))
require.NoError(t, err)

response, err := evaluationClient.ListFlags(context.TODO())
require.NoError(t, err)

assert.NotEmpty(t, response)
assert.Equal(t, "success", response.Status)
assert.Equal(t, 2, len(*response.Result))
}

func TestVariantFailure(t *testing.T) {
evaluationClient, err := flipt.NewClient(flipt.WithURL(fliptUrl), flipt.WithClientTokenAuthentication(authToken))
require.NoError(t, err)
Expand Down
8 changes: 8 additions & 0 deletions flipt-client-go/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ type EngineOpts[T any] struct {
Reference string `json:"reference,omitempty"`
}

type Flag struct {
Key string `json:"key"`
Enabled bool `json:"enabled"`
Type string `json:"type"`
}

type VariantEvaluationResponse struct {
Match bool `json:"match"`
SegmentKeys []string `json:"segment_keys"`
Expand Down Expand Up @@ -76,3 +82,5 @@ type VariantResult Result[VariantEvaluationResponse]
type BooleanResult Result[BooleanEvaluationResponse]

type BatchResult Result[BatchEvaluationResponse]

type ListFlagsResult Result[[]Flag]
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,36 @@
public class FliptEvaluationClient {
private final Pointer engine;

private final String namespace;

private final ObjectMapper objectMapper;

public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary) Native.load("fliptengine", CLibrary.class);

Pointer initialize_engine(String[] namespaces, String opts);
Pointer initialize_engine(String namespace, String opts);

String evaluate_boolean(Pointer engine, String evaluation_request);

String evaluate_variant(Pointer engine, String evaluation_request);

String evaluate_batch(Pointer engine, String batch_evaluation_request);

String list_flags(Pointer engine);

void destroy_engine(Pointer engine);
}

private FliptEvaluationClient(String namespace, EngineOpts engineOpts)
throws JsonProcessingException {

String[] namespaces = {namespace};

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());

String engineOptions = objectMapper.writeValueAsString(engineOpts);

Pointer engine = CLibrary.INSTANCE.initialize_engine(namespaces, engineOptions);
Pointer engine = CLibrary.INSTANCE.initialize_engine(namespace, engineOptions);

this.objectMapper = objectMapper;
this.engine = engine;
this.namespace = namespace;
}

public static FliptEvaluationClientBuilder builder() {
Expand Down Expand Up @@ -101,28 +98,20 @@ public FliptEvaluationClient build() throws JsonProcessingException {
}
}

private static class EvalRequest {
private final String namespaceKey;
private static class InternalEvaluationRequest {

private final String flagKey;

private final String entityId;

private final Map<String, String> context;

public EvalRequest(
String namespaceKey, String flagKey, String entityId, Map<String, String> context) {
this.namespaceKey = namespaceKey;
public InternalEvaluationRequest(String flagKey, String entityId, Map<String, String> context) {
this.flagKey = flagKey;
this.entityId = entityId;
this.context = context;
}

@JsonProperty("namespace_key")
public String getNamespaceKey() {
return namespaceKey;
}

@JsonProperty("flag_key")
public String getFlagKey() {
return flagKey;
Expand All @@ -141,7 +130,8 @@ public Map<String, String> getContext() {

public Result<VariantEvaluationResponse> evaluateVariant(
String flagKey, String entityId, Map<String, String> context) throws JsonProcessingException {
EvalRequest evaluationRequest = new EvalRequest(this.namespace, flagKey, entityId, context);
InternalEvaluationRequest evaluationRequest =
new InternalEvaluationRequest(flagKey, entityId, context);

String evaluationRequestSerialized = this.objectMapper.writeValueAsString(evaluationRequest);
String value = CLibrary.INSTANCE.evaluate_variant(this.engine, evaluationRequestSerialized);
Expand All @@ -154,7 +144,8 @@ public Result<VariantEvaluationResponse> evaluateVariant(

public Result<BooleanEvaluationResponse> evaluateBoolean(
String flagKey, String entityId, Map<String, String> context) throws JsonProcessingException {
EvalRequest evaluationRequest = new EvalRequest(this.namespace, flagKey, entityId, context);
InternalEvaluationRequest evaluationRequest =
new InternalEvaluationRequest(flagKey, entityId, context);

String evaluationRequestSerialized = this.objectMapper.writeValueAsString(evaluationRequest);
String value = CLibrary.INSTANCE.evaluate_boolean(this.engine, evaluationRequestSerialized);
Expand All @@ -166,13 +157,13 @@ public Result<BooleanEvaluationResponse> evaluateBoolean(

public Result<BatchEvaluationResponse> evaluateBatch(EvaluationRequest[] batchEvaluationRequests)
throws JsonProcessingException {
ArrayList<EvalRequest> evaluationRequests = new ArrayList<>(batchEvaluationRequests.length);
ArrayList<InternalEvaluationRequest> evaluationRequests =
new ArrayList<>(batchEvaluationRequests.length);

for (int i = 0; i < batchEvaluationRequests.length; i++) {
evaluationRequests.add(
i,
new EvalRequest(
this.namespace,
new InternalEvaluationRequest(
batchEvaluationRequests[i].getFlagKey(),
batchEvaluationRequests[i].getEntityId(),
batchEvaluationRequests[i].getContext()));
Expand All @@ -188,6 +179,15 @@ public Result<BatchEvaluationResponse> evaluateBatch(EvaluationRequest[] batchEv
return this.objectMapper.readValue(value, typeRef);
}

public Result<ArrayList<Flag>> listFlags() throws JsonProcessingException {
String value = CLibrary.INSTANCE.list_flags(this.engine);

TypeReference<Result<ArrayList<Flag>>> typeRef =
new TypeReference<Result<ArrayList<Flag>>>() {};

return this.objectMapper.readValue(value, typeRef);
}

public void close() {
CLibrary.INSTANCE.destroy_engine(this.engine);
}
Expand Down
34 changes: 34 additions & 0 deletions flipt-client-java/src/main/java/io/flipt/client/models/Flag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.flipt.client.models;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Flag {
private final String key;
private final boolean enabled;
private final String type;

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Flag(
@JsonProperty("key") String key,
@JsonProperty("enabled") boolean enabled,
@JsonProperty("type") String type) {
this.key = key;
this.enabled = enabled;
this.type = type;
}

public String getKey() {
return key;
}

public boolean isEnabled() {
return enabled;
}

public String getType() {
return type;
}
}
13 changes: 13 additions & 0 deletions flipt-client-java/src/test/java/TestFliptEvaluationClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io.flipt.client.FliptEvaluationClient;
import io.flipt.client.models.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
Expand Down Expand Up @@ -105,6 +106,18 @@ void testEvaluateBatch() throws Exception {
Assertions.assertEquals("NOT_FOUND_ERROR_EVALUATION_REASON", errorResponse.getReason());
}

@Test
void testListFlags() throws Exception {
Result<ArrayList<Flag>> result = fliptClient.listFlags();

Assertions.assertEquals("success", result.getStatus());
Assertions.assertFalse(result.getErrorMessage().isPresent());
Assertions.assertTrue(result.getResult().isPresent());

ArrayList<Flag> flags = result.getResult().get();
Assertions.assertEquals(2, flags.size());
}

@AfterAll
static void tearDownAll() throws Exception {
if (fliptClient != null) fliptClient.close();
Expand Down
35 changes: 27 additions & 8 deletions flipt-client-node/src/evaluation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ test('variant', () => {
expect(variant.error_message).toBeNull();
expect(variant.status).toEqual('success');
expect(variant.result).toBeDefined();
expect(variant.result.flag_key).toEqual('flag1');
expect(variant.result.match).toEqual(true);
expect(variant.result.reason).toEqual('MATCH_EVALUATION_REASON');
expect(variant.result.segment_keys).toContain('segment1');
expect(variant.result.variant_key).toContain('variant1');
expect(variant.result?.flag_key).toEqual('flag1');
expect(variant.result?.match).toEqual(true);
expect(variant.result?.reason).toEqual('MATCH_EVALUATION_REASON');
expect(variant.result?.segment_keys).toContain('segment1');
expect(variant.result?.variant_key).toContain('variant1');
} finally {
if (client) client.close();
}
Expand All @@ -55,9 +55,9 @@ test('boolean', () => {
expect(boolean.error_message).toBeNull();
expect(boolean.status).toEqual('success');
expect(boolean.result).toBeDefined();
expect(boolean.result.flag_key).toEqual('flag_boolean');
expect(boolean.result.enabled).toEqual(true);
expect(boolean.result.reason).toEqual('MATCH_EVALUATION_REASON');
expect(boolean.result?.flag_key).toEqual('flag_boolean');
expect(boolean.result?.enabled).toEqual(true);
expect(boolean.result?.reason).toEqual('MATCH_EVALUATION_REASON');
} finally {
if (client) client.close();
}
Expand Down Expand Up @@ -182,3 +182,22 @@ test('boolean failure', () => {
if (client) client.close();
}
});

test('list flags', () => {
const client = new FliptEvaluationClient('default', {
url: fliptUrl,
authentication: {
client_token: authToken
}
});

try {
const flags = client.listFlags();
expect(flags.error_message).toBeNull();
expect(flags.status).toEqual('success');
expect(flags.result).toBeDefined();
expect(flags.result?.length).toEqual(2);
} finally {
if (client) client.close();
}
});
Loading

0 comments on commit cf09c85

Please sign in to comment.