Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ go.work.sum
dist

# openfeature cli config
.openfeature.yaml
.openfeature.yaml

.idea/
56 changes: 55 additions & 1 deletion cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/open-feature/cli/internal/generators"
"github.com/open-feature/cli/internal/generators/csharp"
"github.com/open-feature/cli/internal/generators/golang"
"github.com/open-feature/cli/internal/generators/java"
"github.com/open-feature/cli/internal/generators/nestjs"
"github.com/open-feature/cli/internal/generators/nodejs"
"github.com/open-feature/cli/internal/generators/python"
Expand Down Expand Up @@ -257,6 +258,58 @@ func getGenerateCSharpCmd() *cobra.Command {
return csharpCmd
}

func getGenerateJavaCmd() *cobra.Command {
javaCmd := &cobra.Command{
Use: "java",
Short: "Generate typesafe Java client.",
Long: `Generate typesafe Java client compatible with the OpenFeature Java SDK.`,
Annotations: map[string]string{
"stability": string(generators.Alpha),
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return initializeConfig(cmd, "generate.java")
},
RunE: func(cmd *cobra.Command, args []string) error {
manifestPath := config.GetManifestPath(cmd)
javaPackageName := config.GetJavaPackageName(cmd)
outputPath := config.GetOutputPath(cmd)

logger.Default.GenerationStarted("Java")

params := generators.Params[java.Params]{
OutputPath: outputPath,
Custom: java.Params{
JavaPackage: javaPackageName,
},
}

flagset, err := flagset.Load(manifestPath)
if err != nil {
return err
}

generator := java.NewGenerator(flagset)
logger.Default.Debug("Executing Java generator")
err = generator.Generate(&params)
if err != nil {
return err
}

logger.Default.GenerationComplete("Java")

return nil

},
}

// Add Java specific flags
config.AddJavaGenerateFlags(javaCmd)

addStabilityInfo(javaCmd)

return javaCmd
}

func getGenerateGoCmd() *cobra.Command {
goCmd := &cobra.Command{
Use: "go",
Expand Down Expand Up @@ -355,6 +408,7 @@ func init() {
generators.DefaultManager.Register(getGenerateGoCmd)
generators.DefaultManager.Register(getGenerateNodeJSCmd)
generators.DefaultManager.Register(getGeneratePythonCmd)
generators.DefaultManager.Register(getGenerateCSharpCmd)
generators.DefaultManager.Register(getGenerateCSharpCmd)
generators.DefaultManager.Register(GetGenerateNestJsCmd)
generators.DefaultManager.Register(getGenerateJavaCmd)
}
27 changes: 24 additions & 3 deletions cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type generateTestCase struct {
outputGolden string // path to the golden output file
outputPath string // output directory (optional, defaults to "output")
outputFile string // output file name
packageName string // optional, used for Go (package-name) and C# (namespace)
packageName string // optional, used for Go (package-name), Java (package-name) and C# (namespace)
}

func TestGenerate(t *testing.T) {
Expand Down Expand Up @@ -70,6 +70,14 @@ func TestGenerate(t *testing.T) {
outputFile: "OpenFeature.g.cs",
packageName: "TestNamespace", // Using packageName field for namespace
},
{
name: "Java generation success",
command: "java",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_java.golden",
outputFile: "OpenFeature.java",
packageName: "com.example.openfeature",
},
// Add more test cases here as needed
}

Expand Down Expand Up @@ -107,6 +115,8 @@ func TestGenerate(t *testing.T) {
args = append(args, "--namespace", tc.packageName)
} else if tc.command == "go" {
args = append(args, "--package-name", tc.packageName)
} else if tc.command == "java" {
args = append(args, "--package-name", tc.packageName)
}
}

Expand Down Expand Up @@ -146,6 +156,17 @@ func readOsFileAndWriteToMemMap(t *testing.T, inputPath string, memPath string,
}
}

// normalizeLines trims trailing whitespace and carriage returns from each line.
// This helps ensure consistent comparison by ignoring formatting differences like indentation or line endings.
func normalizeLines(input []string) []string {
normalized := make([]string, len(input))
for i, line := range input {
// Trim right whitespace and convert \r\n or \r to \n
normalized[i] = strings.TrimRight(line, " \t\r")
}
return normalized
}

func compareOutput(t *testing.T, testFile, memoryOutputPath string, fs afero.Fs) {
want, err := os.ReadFile(testFile)
if err != nil {
Expand All @@ -158,8 +179,8 @@ func compareOutput(t *testing.T, testFile, memoryOutputPath string, fs afero.Fs)
}

// Convert to string arrays by splitting on newlines
wantLines := strings.Split(string(want), "\n")
gotLines := strings.Split(string(got), "\n")
wantLines := normalizeLines(strings.Split(string(want), "\n"))
gotLines := normalizeLines(strings.Split(string(got), "\n"))

if diff := cmp.Diff(wantLines, gotLines); diff != "" {
t.Errorf("output mismatch (-want +got):\n%s", diff)
Expand Down
154 changes: 154 additions & 0 deletions cmd/testdata/success_java.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// AUTOMATICALLY GENERATED BY OPENFEATURE CLI, DO NOT EDIT.
package com.example.openfeature;

import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.OpenFeatureAPI;

public final class Generated {

private Generated() {} // prevent instantiation

public interface GeneratedClient {

/**
* Discount percentage applied to purchases.
* Details:
* - Flag key: discountPercentage
* - Type: Double
* - Default value: 0.15
* Returns the flag value
*/
Double discountPercentage(EvaluationContext ctx);

/**
* Discount percentage applied to purchases.
* Details:
* - Flag key: discountPercentage
* - Type: Double
* - Default value: 0.15
* Returns the evaluation details containing the flag value and metadata
*/
FlagEvaluationDetails<Double> discountPercentageDetails(EvaluationContext ctx);

/**
* Controls whether Feature A is enabled.
* Details:
* - Flag key: enableFeatureA
* - Type: Boolean
* - Default value: false
* Returns the flag value
*/
Boolean enableFeatureA(EvaluationContext ctx);

/**
* Controls whether Feature A is enabled.
* Details:
* - Flag key: enableFeatureA
* - Type: Boolean
* - Default value: false
* Returns the evaluation details containing the flag value and metadata
*/
FlagEvaluationDetails<Boolean> enableFeatureADetails(EvaluationContext ctx);

/**
* The message to use for greeting users.
* Details:
* - Flag key: greetingMessage
* - Type: String
* - Default value: Hello there!
* Returns the flag value
*/
String greetingMessage(EvaluationContext ctx);

/**
* The message to use for greeting users.
* Details:
* - Flag key: greetingMessage
* - Type: String
* - Default value: Hello there!
* Returns the evaluation details containing the flag value and metadata
*/
FlagEvaluationDetails<String> greetingMessageDetails(EvaluationContext ctx);

/**
* Maximum allowed length for usernames.
* Details:
* - Flag key: usernameMaxLength
* - Type: Integer
* - Default value: 50
* Returns the flag value
*/
Integer usernameMaxLength(EvaluationContext ctx);

/**
* Maximum allowed length for usernames.
* Details:
* - Flag key: usernameMaxLength
* - Type: Integer
* - Default value: 50
* Returns the evaluation details containing the flag value and metadata
*/
FlagEvaluationDetails<Integer> usernameMaxLengthDetails(EvaluationContext ctx);

}

private static final class OpenFeatureGeneratedClient implements GeneratedClient {
private final Client client;

private OpenFeatureGeneratedClient(Client client) {
this.client = client;
}


@Override
public Double discountPercentage(EvaluationContext ctx) {
return client.getDoubleValue("discountPercentage", 0.15, ctx);
}

@Override
public FlagEvaluationDetails<Double> discountPercentageDetails(EvaluationContext ctx) {
return client.getDoubleDetails("discountPercentage", 0.15, ctx);
}

@Override
public Boolean enableFeatureA(EvaluationContext ctx) {
return client.getBooleanValue("enableFeatureA", false, ctx);
}

@Override
public FlagEvaluationDetails<Boolean> enableFeatureADetails(EvaluationContext ctx) {
return client.getBooleanDetails("enableFeatureA", false, ctx);
}

@Override
public String greetingMessage(EvaluationContext ctx) {
return client.getStringValue("greetingMessage", "Hello there!", ctx);
}

@Override
public FlagEvaluationDetails<String> greetingMessageDetails(EvaluationContext ctx) {
return client.getStringDetails("greetingMessage", "Hello there!", ctx);
}

@Override
public Integer usernameMaxLength(EvaluationContext ctx) {
return client.getIntegerValue("usernameMaxLength", 50, ctx);
}

@Override
public FlagEvaluationDetails<Integer> usernameMaxLengthDetails(EvaluationContext ctx) {
return client.getIntegerDetails("usernameMaxLength", 50, ctx);
}

}

public static GeneratedClient getClient() {
return new OpenFeatureGeneratedClient(OpenFeatureAPI.getInstance().getClient());
}

public static GeneratedClient getClient(String domain) {
return new OpenFeatureGeneratedClient(OpenFeatureAPI.getInstance().getClient(domain));
}
}
1 change: 1 addition & 0 deletions docs/commands/openfeature_generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ openfeature generate [flags]
* [openfeature](openfeature.md) - CLI for OpenFeature.
* [openfeature generate csharp](openfeature_generate_csharp.md) - Generate typesafe C# client.
* [openfeature generate go](openfeature_generate_go.md) - Generate typesafe accessors for OpenFeature.
* [openfeature generate java](openfeature_generate_java.md) - Generate typesafe Java client.
* [openfeature generate nestjs](openfeature_generate_nestjs.md) - Generate typesafe NestJS decorators.
* [openfeature generate nodejs](openfeature_generate_nodejs.md) - Generate typesafe Node.js client.
* [openfeature generate python](openfeature_generate_python.md) - Generate typesafe Python client.
Expand Down
37 changes: 37 additions & 0 deletions docs/commands/openfeature_generate_java.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!-- markdownlint-disable-file -->
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
## openfeature generate java

Generate typesafe Java client.


> **Stability**: alpha

### Synopsis

Generate typesafe Java client compatible with the OpenFeature Java SDK.

```
openfeature generate java [flags]
```

### Options

```
-h, --help help for java
--package-name string Name of the generated Java package (default "com.example.openfeature")
```

### Options inherited from parent commands

```
--debug Enable debug logging
-m, --manifest string Path to the flag manifest (default "flags.json")
--no-input Disable interactive prompts
-o, --output string Path to where the generated files should be saved
```

### SEE ALSO

* [openfeature generate](openfeature_generate.md) - Generate typesafe OpenFeature accessors.

13 changes: 13 additions & 0 deletions internal/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
GoPackageFlagName = "package-name"
CSharpNamespaceName = "namespace"
OverrideFlagName = "override"
JavaPackageFlagName = "package-name"
)

// Default values for flags
Expand All @@ -21,6 +22,7 @@ const (
DefaultOutputPath = ""
DefaultGoPackageName = "openfeature"
DefaultCSharpNamespace = "OpenFeature"
DefaultJavaPackageName = "com.example.openfeature"
)

// AddRootFlags adds the common flags to the given command
Expand All @@ -45,6 +47,11 @@ func AddCSharpGenerateFlags(cmd *cobra.Command) {
cmd.Flags().String(CSharpNamespaceName, DefaultCSharpNamespace, "Namespace for the generated C# code")
}

// AddJavaGenerateFlags adds the Java generator specific flags to the given command
func AddJavaGenerateFlags(cmd *cobra.Command) {
cmd.Flags().String(JavaPackageFlagName, DefaultJavaPackageName, "Name of the generated Java package")
}

// AddInitFlags adds the init command specific flags
func AddInitFlags(cmd *cobra.Command) {
cmd.Flags().Bool(OverrideFlagName, false, "Override an existing configuration")
Expand Down Expand Up @@ -74,6 +81,12 @@ func GetCSharpNamespace(cmd *cobra.Command) string {
return namespace
}

// GetJavaPackageName gets the Java package name from the given command
func GetJavaPackageName(cmd *cobra.Command) string {
javaPackageName, _ := cmd.Flags().GetString(JavaPackageFlagName)
return javaPackageName
}

// GetNoInput gets the no-input flag from the given command
func GetNoInput(cmd *cobra.Command) bool {
noInput, _ := cmd.Flags().GetBool(NoInputFlagName)
Expand Down
Loading