Skip to content

Commit f4342d8

Browse files
ctawiahcursoragent
andcommitted
fix: align ldctx anonymous and multi-kind key with other SDKs (AIC-2695)
- Always expose ldctx.anonymous as true/false rather than only when true. - Expose the canonical fully-qualified key at the top level of a multi-kind ldctx so {{ldctx.key}} resolves, matching the .NET (and other) SDKs. Adds InterpolatorTest cases for both. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 04326f0 commit f4342d8

2 files changed

Lines changed: 27 additions & 5 deletions

File tree

lib/sdk/server-ai/src/main/java/com/launchdarkly/sdk/server/ai/internal/Interpolator.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ private String render(String template, Map<String, Object> variables) {
8787
/**
8888
* Encodes the evaluation context directly into the nested map structure exposed to templates as
8989
* {@code ldctx}, without round-tripping through JSON serialization. A single-kind context becomes
90-
* a map of its attributes; a multi-kind context becomes {@code {"kind":"multi", <kind>: {...}}}
91-
* with one nested map per individual context.
90+
* a map of its attributes; a multi-kind context becomes
91+
* {@code {"kind":"multi", "key":<fully-qualified key>, <kind>: {...}}} with one nested map per
92+
* individual context.
9293
*/
9394
private static Map<String, Object> contextToMap(LDContext context) {
9495
if (context == null || !context.isValid()) {
@@ -97,6 +98,9 @@ private static Map<String, Object> contextToMap(LDContext context) {
9798
if (context.isMultiple()) {
9899
Map<String, Object> map = new HashMap<>();
99100
map.put("kind", "multi");
101+
// Expose the canonical multi-kind key at the top level (matching other SDKs) so
102+
// {{ldctx.key}} resolves for multi-kind contexts.
103+
map.put("key", context.getFullyQualifiedKey());
100104
int count = context.getIndividualContextCount();
101105
for (int i = 0; i < count; i++) {
102106
LDContext individual = context.getIndividualContext(i);
@@ -120,9 +124,8 @@ private static Map<String, Object> singleContextToMap(LDContext context, boolean
120124
if (context.getName() != null) {
121125
map.put("name", context.getName());
122126
}
123-
if (context.isAnonymous()) {
124-
map.put("anonymous", true);
125-
}
127+
// Always expose anonymous as true/false (matching other SDKs) rather than only when true.
128+
map.put("anonymous", context.isAnonymous());
126129
// Custom attribute values can be arbitrary JSON; convert each LDValue to a plain Java value
127130
// (depth-capped) so nested objects/arrays remain addressable from templates.
128131
for (String attribute : context.getCustomAttributeNames()) {

lib/sdk/server-ai/src/test/java/com/launchdarkly/sdk/server/ai/internal/InterpolatorTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ public void multiKindNestedContextsOmitKind() {
9696
interpolator.interpolate("[{{ldctx.user.kind}}]", null, multi), is("[]"));
9797
}
9898

99+
@Test
100+
public void anonymousIsAlwaysExposedAsBoolean() {
101+
LDContext anon = LDContext.builder("k").anonymous(true).build();
102+
assertThat(interpolator.interpolate("{{ldctx.anonymous}}", null, anon), is("true"));
103+
// Matches other SDKs: anonymous is emitted even when false, rather than rendering empty.
104+
LDContext named = LDContext.builder("k").build();
105+
assertThat(interpolator.interpolate("{{ldctx.anonymous}}", null, named), is("false"));
106+
}
107+
108+
@Test
109+
public void multiKindExposesFullyQualifiedKeyAtTopLevel() {
110+
LDContext multi = LDContext.createMulti(
111+
LDContext.builder("user-key").build(),
112+
LDContext.builder(com.launchdarkly.sdk.ContextKind.of("org"), "org-key").build());
113+
// {{ldctx.key}} on a multi-kind context resolves to the canonical fully-qualified key.
114+
assertThat(
115+
interpolator.interpolate("{{ldctx.key}}", null, multi), is(multi.getFullyQualifiedKey()));
116+
}
117+
99118
@Test
100119
public void ldctxOverridesUserSuppliedValue() {
101120
Map<String, Object> userLdctx = new HashMap<>();

0 commit comments

Comments
 (0)