diff --git a/internal/cmd/testdata/success_angular.golden b/internal/cmd/testdata/success_angular.golden
index 221a752..cb2063d 100644
--- a/internal/cmd/testdata/success_angular.golden
+++ b/internal/cmd/testdata/success_angular.golden
@@ -101,6 +101,14 @@ export type FlagKey = (typeof FlagKeys)[keyof typeof FlagKeys];
export class GeneratedFeatureFlagService {
private readonly flagService = inject(FeatureFlagService);
+ /**
+ * The underlying FeatureFlagService for ad-hoc flag evaluations
+ * beyond what's defined in the manifest.
+ */
+ get client(): FeatureFlagService {
+ return this.flagService;
+ }
+
/**
* Get evaluation details for the `discountPercentage` flag.
diff --git a/internal/cmd/testdata/success_csharp.golden b/internal/cmd/testdata/success_csharp.golden
index a77ce09..a321400 100644
--- a/internal/cmd/testdata/success_csharp.golden
+++ b/internal/cmd/testdata/success_csharp.golden
@@ -75,6 +75,11 @@ namespace TestNamespace
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
+
+ ///
+ /// The underlying OpenFeature client for ad-hoc flag evaluations.
+ ///
+ public IFeatureClient Client => _client;
///
/// Discount percentage applied to purchases.
///
diff --git a/internal/cmd/testdata/success_go.golden b/internal/cmd/testdata/success_go.golden
index 64861ab..92da0ba 100644
--- a/internal/cmd/testdata/success_go.golden
+++ b/internal/cmd/testdata/success_go.golden
@@ -24,7 +24,9 @@ type (
evaluationDetails[T any] func(context.Context, openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[T], error)
)
-var client = openfeature.NewDefaultClient()
+// Client is the underlying OpenFeature client used for flag evaluations.
+// It can be used directly for ad-hoc flag evaluations beyond what is defined in the manifest.
+var Client = openfeature.NewDefaultClient()
// DiscountPercentage returns the value of the "discountPercentage" feature flag.
// Discount percentage applied to purchases.
@@ -41,10 +43,10 @@ var DiscountPercentage = struct {
}{
Stringer: stringer("discountPercentage"),
Value: func(ctx context.Context, evalCtx openfeature.EvaluationContext) float64 {
- return client.Float(ctx, "discountPercentage", 0.15, evalCtx)
+ return Client.Float(ctx, "discountPercentage", 0.15, evalCtx)
},
ValueWithDetails: func(ctx context.Context, evalCtx openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[float64], error) {
- return client.FloatValueDetails(ctx, "discountPercentage", 0.15, evalCtx)
+ return Client.FloatValueDetails(ctx, "discountPercentage", 0.15, evalCtx)
},
}
@@ -63,10 +65,10 @@ var EnableFeatureA = struct {
}{
Stringer: stringer("enableFeatureA"),
Value: func(ctx context.Context, evalCtx openfeature.EvaluationContext) bool {
- return client.Boolean(ctx, "enableFeatureA", false, evalCtx)
+ return Client.Boolean(ctx, "enableFeatureA", false, evalCtx)
},
ValueWithDetails: func(ctx context.Context, evalCtx openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[bool], error) {
- return client.BooleanValueDetails(ctx, "enableFeatureA", false, evalCtx)
+ return Client.BooleanValueDetails(ctx, "enableFeatureA", false, evalCtx)
},
}
@@ -85,10 +87,10 @@ var GreetingMessage = struct {
}{
Stringer: stringer("greetingMessage"),
Value: func(ctx context.Context, evalCtx openfeature.EvaluationContext) string {
- return client.String(ctx, "greetingMessage", "Hello there!", evalCtx)
+ return Client.String(ctx, "greetingMessage", "Hello there!", evalCtx)
},
ValueWithDetails: func(ctx context.Context, evalCtx openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[string], error) {
- return client.StringValueDetails(ctx, "greetingMessage", "Hello there!", evalCtx)
+ return Client.StringValueDetails(ctx, "greetingMessage", "Hello there!", evalCtx)
},
}
@@ -107,10 +109,10 @@ var ThemeCustomization = struct {
}{
Stringer: stringer("themeCustomization"),
Value: func(ctx context.Context, evalCtx openfeature.EvaluationContext) any {
- return client.Object(ctx, "themeCustomization", map[string]any{"primaryColor": "#007bff", "secondaryColor": "#6c757d"}, evalCtx)
+ return Client.Object(ctx, "themeCustomization", map[string]any{"primaryColor": "#007bff", "secondaryColor": "#6c757d"}, evalCtx)
},
ValueWithDetails: func(ctx context.Context, evalCtx openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[any], error) {
- return client.ObjectValueDetails(ctx, "themeCustomization", map[string]any{"primaryColor": "#007bff", "secondaryColor": "#6c757d"}, evalCtx)
+ return Client.ObjectValueDetails(ctx, "themeCustomization", map[string]any{"primaryColor": "#007bff", "secondaryColor": "#6c757d"}, evalCtx)
},
}
@@ -129,9 +131,9 @@ var UsernameMaxLength = struct {
}{
Stringer: stringer("usernameMaxLength"),
Value: func(ctx context.Context, evalCtx openfeature.EvaluationContext) int64 {
- return client.Int(ctx, "usernameMaxLength", 50, evalCtx)
+ return Client.Int(ctx, "usernameMaxLength", 50, evalCtx)
},
ValueWithDetails: func(ctx context.Context, evalCtx openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[int64], error) {
- return client.IntValueDetails(ctx, "usernameMaxLength", 50, evalCtx)
+ return Client.IntValueDetails(ctx, "usernameMaxLength", 50, evalCtx)
},
}
diff --git a/internal/cmd/testdata/success_java.golden b/internal/cmd/testdata/success_java.golden
index de28527..8749455 100644
--- a/internal/cmd/testdata/success_java.golden
+++ b/internal/cmd/testdata/success_java.golden
@@ -125,6 +125,12 @@ public final class OpenFeature {
* Returns the evaluation details containing the flag value and metadata
*/
FlagEvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx);
+
+ /**
+ * Returns the underlying OpenFeature client for ad-hoc flag evaluations.
+ * @return The OpenFeature Client instance
+ */
+ Client getOpenFeatureClient();
}
private static final class OpenFeatureGeneratedClient implements GeneratedClient {
@@ -180,6 +186,11 @@ public final class OpenFeature {
public FlagEvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx) {
return client.getIntegerDetails("usernameMaxLength", 50, ctx);
}
+
+ @Override
+ public Client getOpenFeatureClient() {
+ return client;
+ }
}
public static GeneratedClient getClient() {
diff --git a/internal/cmd/testdata/success_nodejs.golden b/internal/cmd/testdata/success_nodejs.golden
index f1f7d4a..6107ca4 100644
--- a/internal/cmd/testdata/success_nodejs.golden
+++ b/internal/cmd/testdata/success_nodejs.golden
@@ -6,6 +6,7 @@ import {
JsonValue,
} from "@openfeature/server-sdk";
import type {
+ Client,
EvaluationContext,
EvaluationDetails,
FlagEvaluationOptions,
@@ -26,6 +27,8 @@ export const FlagKeys = {
} as const;
export interface GeneratedClient {
+ /** The underlying OpenFeature client for ad-hoc flag evaluations. */
+ readonly client: Client;
/**
* Discount percentage applied to purchases.
*
@@ -206,6 +209,7 @@ export function getGeneratedClient(domainOrContext?: string | EvaluationContext,
const client = domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context)
return {
+ client,
discountPercentage: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise => {
return client.getNumberValue("discountPercentage", 0.15, context, options);
},
diff --git a/internal/cmd/testdata/success_react.golden b/internal/cmd/testdata/success_react.golden
index 17a219a..444040f 100644
--- a/internal/cmd/testdata/success_react.golden
+++ b/internal/cmd/testdata/success_react.golden
@@ -6,6 +6,7 @@ import {
type FlagQuery,
useFlag,
useSuspenseFlag,
+ useOpenFeatureClient,
JsonValue
} from "@openfeature/react-sdk";
@@ -158,3 +159,9 @@ export const useUsernameMaxLength = (options?: ReactFlagEvaluationOptions): Flag
export const useSuspenseUsernameMaxLength = (options?: ReactFlagEvaluationNoSuspenseOptions): FlagQuery => {
return useSuspenseFlag("usernameMaxLength", 50, options);
};
+
+/**
+ * Re-exported hook for accessing the underlying OpenFeature client
+ * for ad-hoc flag evaluations beyond what's defined in the manifest.
+ */
+export { useOpenFeatureClient };
diff --git a/internal/generators/README.md b/internal/generators/README.md
index a349197..cd2459d 100644
--- a/internal/generators/README.md
+++ b/internal/generators/README.md
@@ -55,4 +55,26 @@ To add a new generator, follow these steps:
6. Write tests for your generator to ensure it works as expected.
7. Update the documentation to include information about your new generator.
-We appreciate your contributions and look forward to seeing your new generators!
\ No newline at end of file
+We appreciate your contributions and look forward to seeing your new generators!
+
+## Reserved Keywords
+
+Each generator reserves certain symbol names that it exports in the generated output. If a flag key transforms to a reserved name, that flag will be **excluded** from the generated output and a warning will be printed.
+
+| Generator | Reserved names | Transform applied |
+|-----------|------------------------|------------------------------|
+| Go | `Client` | `ToPascal` |
+| Node.js | `client` | `ToCamel` |
+| React | `useOpenFeatureClient` | `use` + `ToPascal` |
+
+A generator only reserves a name when a flag key could actually transform into a symbol it already emits. For example, a flag key `"client"` in a Go manifest transforms to `Client` (via `ToPascal`), colliding with the exported `var Client` that the Go generator places in every generated file. Likewise, a flag key `"openFeatureClient"` for the React generator produces the hook `useOpenFeatureClient`, colliding with the re-exported hook of the same name. The flag is skipped and a warning is emitted:
+
+```
+Flag "client" transforms to "Client" which is a reserved symbol in the Go generator. This flag will be excluded from the generated output.
+```
+
+To avoid this, rename any flags whose transformed name matches a reserved symbol.
+
+Generators whose exposed client symbol cannot collide with a flag-generated member do not reserve a name. For instance, C# exposes a `Client` property while flags generate `Async`/`DetailsAsync` methods, and Java exposes a no-arg `getOpenFeatureClient()` that a flag method would only ever overload โ so neither needs an entry above.
+
+When adding a new generator, use the shared `generators.FilterReservedFlags` helper in the generator's `Generate()` method, and add a row here only if a flag key can genuinely transform into a symbol the generator exports.
\ No newline at end of file
diff --git a/internal/generators/angular/angular.tmpl b/internal/generators/angular/angular.tmpl
index 37e1a07..b2ac0ee 100644
--- a/internal/generators/angular/angular.tmpl
+++ b/internal/generators/angular/angular.tmpl
@@ -79,6 +79,14 @@ export type FlagKey = (typeof FlagKeys)[keyof typeof FlagKeys];
export class GeneratedFeatureFlagService {
private readonly flagService = inject(FeatureFlagService);
+ /**
+ * The underlying FeatureFlagService for ad-hoc flag evaluations
+ * beyond what's defined in the manifest.
+ */
+ get client(): FeatureFlagService {
+ return this.flagService;
+ }
+
{{ range .Flagset.Flags }}
/**
* Get evaluation details for the `{{ .Key }}` flag.
diff --git a/internal/generators/csharp/csharp.tmpl b/internal/generators/csharp/csharp.tmpl
index f09bf64..1e492c5 100644
--- a/internal/generators/csharp/csharp.tmpl
+++ b/internal/generators/csharp/csharp.tmpl
@@ -70,6 +70,11 @@ namespace {{ if .Params.Custom.Namespace }}{{ .Params.Custom.Namespace }}{{ else
_client = client ?? throw new ArgumentNullException(nameof(client));
}
+ ///
+ /// The underlying OpenFeature client for ad-hoc flag evaluations.
+ ///
+ public IFeatureClient Client => _client;
+
{{- range .Flagset.Flags }}
///
/// {{ if .Description }}{{ .Description }}{{ else }}Feature flag{{ end }}
diff --git a/internal/generators/golang/golang.go b/internal/generators/golang/golang.go
index c45d0a8..c5306d8 100644
--- a/internal/generators/golang/golang.go
+++ b/internal/generators/golang/golang.go
@@ -10,6 +10,7 @@ import (
"strings"
"text/template"
+ "github.com/iancoleman/strcase"
"github.com/open-feature/cli/internal/flagset"
"github.com/open-feature/cli/internal/generators"
"golang.org/x/tools/imports"
@@ -128,7 +129,16 @@ func formatNestedValue(value any) string {
}
}
+// reservedNames are symbols exported by the Go generator itself. Flag keys
+// that transform (via ToPascal) to one of these names will be excluded from
+// the generated output and a warning will be emitted.
+var reservedNames = map[string]bool{
+ "Client": true,
+}
+
func (g *GolangGenerator) Generate(params *generators.Params[Params]) error {
+ g.Flagset = generators.FilterReservedFlags(g.Flagset, "Go", reservedNames, strcase.ToCamel)
+
funcs := template.FuncMap{
"SupportImports": supportImports,
"OpenFeatureType": openFeatureType,
diff --git a/internal/generators/golang/golang.tmpl b/internal/generators/golang/golang.tmpl
index c8898bc..48653ce 100644
--- a/internal/generators/golang/golang.tmpl
+++ b/internal/generators/golang/golang.tmpl
@@ -23,7 +23,9 @@ type (
evaluationDetails[T any] func(context.Context, openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[T], error)
)
-var client = openfeature.NewDefaultClient()
+// Client is the underlying OpenFeature client used for flag evaluations.
+// It can be used directly for ad-hoc flag evaluations beyond what is defined in the manifest.
+var Client = openfeature.NewDefaultClient()
{{- range .Flagset.Flags }}
// {{ .Key | ToPascal }} returns the value of the "{{ .Key }}" feature flag.
@@ -41,10 +43,10 @@ var {{ .Key | ToPascal }} = struct {
}{
Stringer: stringer({{ .Key | Quote }}),
Value: func(ctx context.Context, evalCtx openfeature.EvaluationContext) {{- if eq (.Type | OpenFeatureType) "Object"}}any{{- else}}{{ .Type | TypeString }}{{- end}} {
- return client.{{ .Type | OpenFeatureType }}(ctx, {{ .Key | Quote }}, {{ if eq (.Type | OpenFeatureType) "Object" }}{{.DefaultValue | ToMapLiteral }}{{- else }}{{ .DefaultValue | QuoteString }}{{- end}}, evalCtx)
+ return Client.{{ .Type | OpenFeatureType }}(ctx, {{ .Key | Quote }}, {{ if eq (.Type | OpenFeatureType) "Object" }}{{.DefaultValue | ToMapLiteral }}{{- else }}{{ .DefaultValue | QuoteString }}{{- end}}, evalCtx)
},
ValueWithDetails: func(ctx context.Context, evalCtx openfeature.EvaluationContext) (openfeature.GenericEvaluationDetails[{{- if eq (.Type | OpenFeatureType) "Object"}}any{{- else}}{{ .Type | TypeString }}{{- end}}], error){
- return client.{{ .Type | OpenFeatureType }}ValueDetails(ctx, {{ .Key | Quote }}, {{ if eq (.Type | OpenFeatureType) "Object" }}{{.DefaultValue | ToMapLiteral }}{{- else }}{{ .DefaultValue | QuoteString }}{{- end}}, evalCtx)
+ return Client.{{ .Type | OpenFeatureType }}ValueDetails(ctx, {{ .Key | Quote }}, {{ if eq (.Type | OpenFeatureType) "Object" }}{{.DefaultValue | ToMapLiteral }}{{- else }}{{ .DefaultValue | QuoteString }}{{- end}}, evalCtx)
},
}
{{- end}}
diff --git a/internal/generators/java/java.tmpl b/internal/generators/java/java.tmpl
index 3e3002c..4b02457 100644
--- a/internal/generators/java/java.tmpl
+++ b/internal/generators/java/java.tmpl
@@ -44,6 +44,12 @@ public final class OpenFeature {
FlagEvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx);
{{- end }}
+
+ /**
+ * Returns the underlying OpenFeature client for ad-hoc flag evaluations.
+ * @return The OpenFeature Client instance
+ */
+ Client getOpenFeatureClient();
}
private static final class OpenFeatureGeneratedClient implements GeneratedClient {
@@ -65,6 +71,11 @@ public final class OpenFeature {
}
{{- end }}
+
+ @Override
+ public Client getOpenFeatureClient() {
+ return client;
+ }
}
public static GeneratedClient getClient() {
diff --git a/internal/generators/nodejs/nodejs.go b/internal/generators/nodejs/nodejs.go
index dc6469a..a06b0d3 100644
--- a/internal/generators/nodejs/nodejs.go
+++ b/internal/generators/nodejs/nodejs.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"text/template"
+ "github.com/iancoleman/strcase"
"github.com/open-feature/cli/internal/flagset"
"github.com/open-feature/cli/internal/generators"
)
@@ -43,7 +44,16 @@ func toJSONString(value any) string {
return string(bytes)
}
+// reservedNames are symbols exported by the Node.js generator itself. Flag
+// keys that transform (via ToCamel) to one of these names will be excluded
+// from the generated output and a warning will be emitted.
+var reservedNames = map[string]bool{
+ "client": true,
+}
+
func (g *NodejsGenerator) Generate(params *generators.Params[Params]) error {
+ g.Flagset = generators.FilterReservedFlags(g.Flagset, "Node.js", reservedNames, strcase.ToLowerCamel)
+
funcs := template.FuncMap{
"OpenFeatureType": openFeatureType,
"ToJSONString": toJSONString,
diff --git a/internal/generators/nodejs/nodejs.tmpl b/internal/generators/nodejs/nodejs.tmpl
index dd4d3c7..5c4ef83 100644
--- a/internal/generators/nodejs/nodejs.tmpl
+++ b/internal/generators/nodejs/nodejs.tmpl
@@ -6,6 +6,7 @@ import {
JsonValue,
} from "@openfeature/server-sdk";
import type {
+ Client,
EvaluationContext,
EvaluationDetails,
FlagEvaluationOptions,
@@ -20,6 +21,8 @@ export const FlagKeys = {
} as const;
export interface GeneratedClient {
+ /** The underlying OpenFeature client for ad-hoc flag evaluations. */
+ readonly client: Client;
{{- range .Flagset.Flags }}
/**
* {{ if .Description }}{{ .Description }}{{ else }}Feature flag{{ end }}
@@ -82,6 +85,7 @@ export function getGeneratedClient(domainOrContext?: string | EvaluationContext,
const client = domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context)
return {
+ client,
{{- range .Flagset.Flags }}
{{ .Key | ToCamel }}: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise<{{ if eq (.Type | OpenFeatureType) "object" }}JsonValue{{ else }}{{ .Type | OpenFeatureType }}{{ end }}> => {
return client.get{{ .Type | OpenFeatureType | ToPascal }}Value({{ .Key | Quote }}, {{ if eq (.Type | OpenFeatureType) "object"}}{{ .DefaultValue | ToJSONString }}{{ else }}{{ .DefaultValue | QuoteString }}{{ end }}, context, options);
diff --git a/internal/generators/react/react.go b/internal/generators/react/react.go
index f3dcb9c..6d09380 100644
--- a/internal/generators/react/react.go
+++ b/internal/generators/react/react.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"text/template"
+ "github.com/iancoleman/strcase"
"github.com/open-feature/cli/internal/flagset"
"github.com/open-feature/cli/internal/generators"
)
@@ -43,7 +44,18 @@ func toJSONString(value any) string {
return string(bytes)
}
+// reservedNames are symbols exported by the React generator itself. Flag keys
+// whose generated hook name (use + ToPascal) matches one of these will be
+// excluded from the generated output and a warning will be emitted.
+var reservedNames = map[string]bool{
+ "useOpenFeatureClient": true,
+}
+
func (g *ReactGenerator) Generate(params *generators.Params[Params]) error {
+ g.Flagset = generators.FilterReservedFlags(g.Flagset, "React", reservedNames, func(key string) string {
+ return "use" + strcase.ToCamel(key)
+ })
+
funcs := template.FuncMap{
"OpenFeatureType": openFeatureType,
"ToJSONString": toJSONString,
diff --git a/internal/generators/react/react.tmpl b/internal/generators/react/react.tmpl
index 95401a0..6b6aaf5 100644
--- a/internal/generators/react/react.tmpl
+++ b/internal/generators/react/react.tmpl
@@ -6,6 +6,7 @@ import {
type FlagQuery,
useFlag,
useSuspenseFlag,
+ useOpenFeatureClient,
JsonValue
} from "@openfeature/react-sdk";
@@ -44,4 +45,9 @@ export const use{{ .Key | ToPascal }} = (options?: ReactFlagEvaluationOptions):
export const useSuspense{{ .Key | ToPascal }} = (options?: ReactFlagEvaluationNoSuspenseOptions): FlagQuery<{{ if eq (.Type | OpenFeatureType) "object" }}JsonValue{{ else }}{{ .Type | OpenFeatureType }}{{ end }}> => {
return useSuspenseFlag({{ .Key | Quote }}, {{ if eq (.Type | OpenFeatureType) "object"}}{{ .DefaultValue | ToJSONString }}{{ else }}{{ .DefaultValue | QuoteString }}{{ end }}, options);
};
-{{ end}}
\ No newline at end of file
+{{ end}}
+/**
+ * Re-exported hook for accessing the underlying OpenFeature client
+ * for ad-hoc flag evaluations beyond what's defined in the manifest.
+ */
+export { useOpenFeatureClient };
diff --git a/internal/generators/reserved.go b/internal/generators/reserved.go
new file mode 100644
index 0000000..f6bbe5a
--- /dev/null
+++ b/internal/generators/reserved.go
@@ -0,0 +1,34 @@
+package generators
+
+import (
+ "fmt"
+
+ "github.com/open-feature/cli/internal/flagset"
+ "github.com/open-feature/cli/internal/logger"
+)
+
+// FilterReservedFlags returns a copy of fs with any flag removed whose key,
+// once transformed by the generator, collides with a symbol the generator
+// reserves for itself (such as the exposed underlying OpenFeature client). A
+// warning is emitted for each excluded flag so users understand why it is
+// missing from the generated output. The reserved symbol always wins the
+// collision.
+//
+// transform maps a flag key to the symbol name the generator would emit for it
+// (e.g. ToPascal for Go). reserved holds the symbol names the generator
+// reserves. generatorName is used only in the warning message.
+func FilterReservedFlags(fs *flagset.Flagset, generatorName string, reserved map[string]bool, transform func(string) string) *flagset.Flagset {
+ filtered := &flagset.Flagset{}
+ for _, flag := range fs.Flags {
+ symbol := transform(flag.Key)
+ if reserved[symbol] {
+ logger.Default.Warning(fmt.Sprintf(
+ "Flag %q transforms to %q which is a reserved symbol in the %s generator. This flag will be excluded from the generated output.",
+ flag.Key, symbol, generatorName,
+ ))
+ continue
+ }
+ filtered.Flags = append(filtered.Flags, flag)
+ }
+ return filtered
+}
diff --git a/internal/generators/reserved_test.go b/internal/generators/reserved_test.go
new file mode 100644
index 0000000..cc8b8b4
--- /dev/null
+++ b/internal/generators/reserved_test.go
@@ -0,0 +1,80 @@
+package generators
+
+import (
+ "testing"
+
+ "github.com/iancoleman/strcase"
+ "github.com/open-feature/cli/internal/flagset"
+)
+
+func keys(fs *flagset.Flagset) []string {
+ out := make([]string, 0, len(fs.Flags))
+ for _, f := range fs.Flags {
+ out = append(out, f.Key)
+ }
+ return out
+}
+
+func TestFilterReservedFlags(t *testing.T) {
+ tests := []struct {
+ name string
+ generator string
+ reserved map[string]bool
+ transform func(string) string
+ input []string
+ want []string
+ }{
+ {
+ name: "drops colliding flag, keeps the rest",
+ generator: "Go",
+ reserved: map[string]bool{"Client": true},
+ transform: strcase.ToCamel,
+ input: []string{"client", "discountPercentage"},
+ want: []string{"discountPercentage"},
+ },
+ {
+ name: "no collision keeps every flag",
+ generator: "Go",
+ reserved: map[string]bool{"Client": true},
+ transform: strcase.ToCamel,
+ input: []string{"discountPercentage", "enableFeatureA"},
+ want: []string{"discountPercentage", "enableFeatureA"},
+ },
+ {
+ name: "react hook name collision",
+ generator: "React",
+ reserved: map[string]bool{"useOpenFeatureClient": true},
+ transform: func(key string) string { return "use" + strcase.ToCamel(key) },
+ input: []string{"openFeatureClient", "greetingMessage"},
+ want: []string{"greetingMessage"},
+ },
+ {
+ name: "node lower-camel collision",
+ generator: "Node.js",
+ reserved: map[string]bool{"client": true},
+ transform: strcase.ToLowerCamel,
+ input: []string{"client", "greetingMessage"},
+ want: []string{"greetingMessage"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fs := &flagset.Flagset{}
+ for _, k := range tt.input {
+ fs.Flags = append(fs.Flags, flagset.Flag{Key: k, Type: flagset.StringType})
+ }
+
+ got := keys(FilterReservedFlags(fs, tt.generator, tt.reserved, tt.transform))
+
+ if len(got) != len(tt.want) {
+ t.Fatalf("got %v, want %v", got, tt.want)
+ }
+ for i := range got {
+ if got[i] != tt.want[i] {
+ t.Fatalf("got %v, want %v", got, tt.want)
+ }
+ }
+ })
+ }
+}
diff --git a/test/csharp-integration/Program.cs b/test/csharp-integration/Program.cs
index 3c3230b..3c86f9f 100644
--- a/test/csharp-integration/Program.cs
+++ b/test/csharp-integration/Program.cs
@@ -25,6 +25,9 @@ static void Main(string[] args)
// Test client retrieval from DI
var client = serviceProvider.GetRequiredService();
+ // Verify the underlying client is accessible
+ var underlyingClient = client.Client;
+
// Also test the traditional factory method
var clientFromFactory = GeneratedClient.CreateClient();
diff --git a/test/go-integration/test.go b/test/go-integration/test.go
index 0a8ebea..455361a 100644
--- a/test/go-integration/test.go
+++ b/test/go-integration/test.go
@@ -105,6 +105,9 @@ func run() error {
}
fmt.Printf("themeCustomization: %v\n", themeCustomization)
+ // Verify the underlying client is accessible for ad-hoc evaluations
+ _ = generated.Client
+
// Test the String() method functionality for all flags
fmt.Printf("enableFeatureA flag key: %s\n", generated.EnableFeatureA.String())
fmt.Printf("discountPercentage flag key: %s\n", generated.DiscountPercentage.String())
diff --git a/test/nodejs-integration/test.ts b/test/nodejs-integration/test.ts
index 735b57a..31c628e 100644
--- a/test/nodejs-integration/test.ts
+++ b/test/nodejs-integration/test.ts
@@ -61,6 +61,12 @@ async function main() {
const { getGeneratedClient } = await import(clientPath);
const client = getGeneratedClient();
+ // Verify the underlying client is accessible
+ if (!client.client) {
+ throw new Error('Underlying OpenFeature client not exposed');
+ }
+ console.log('โ
Underlying client accessible');
+
console.log('๐งช Testing flags...');
// Test each flag