Skip to content

Commit ae64581

Browse files
kriscolemanaskptbeeme1mr
authored
feat(csharp): added generator and integration tests (#97)
* feat: adds c# generator Adds a new generator for C# to create typesafe clients. This allows users to generate C# code based on feature flag definitions, streamlining integration with .NET applications. Includes necessary command-line flags, templates, and tests. Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * feat(csharp): adds C# generator integration test Adds a C# code generator integration test to ensure the generated C# code compiles correctly. This includes: - A new C# generator based on templates - Updates to the build process and documentation to support C# generation and testing - An integration test using Docker to compile the generated C# code - Fixes and adjustments to data type mappings for C# compatibility Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * chore: go fmt fixes Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * Update .github/workflows/csharp-integration.yml Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com> Signed-off-by: Kris Coleman <kris.blacksuitmedia@gmail.com> Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * Update CONTRIBUTING.md Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com> Signed-off-by: Kris Coleman <kris.blacksuitmedia@gmail.com> Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * Update internal/generators/csharp/csharp.go Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com> Signed-off-by: Kris Coleman <kris.blacksuitmedia@gmail.com> Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * chore(ci): moved the csharp integration into pr-test workflow as a separate job Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * chore: cleaned up generate code to private funcs are private Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * feat(csharp): implemented di for generated code - updated openfeature to 2.3.2 - introduced IServiceCollection and DI patterns - updated tests and expectations Signed-off-by: Kris Coleman <kriscodeman@gmail.com> * Update .github/workflows/pr-test.yml Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com> Signed-off-by: Kris Coleman <kris.blacksuitmedia@gmail.com> * Update .github/workflows/pr-test.yml Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com> Signed-off-by: Kris Coleman <kris.blacksuitmedia@gmail.com> --------- Signed-off-by: Kris Coleman <kriscodeman@gmail.com> Signed-off-by: Kris Coleman <kris.blacksuitmedia@gmail.com> Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com> Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
1 parent 1f8f43a commit ae64581

30 files changed

+1457
-117
lines changed

.github/workflows/pr-test.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,21 @@ jobs:
4343
echo "::error file=Makefile::Doc generation produced diff. Run 'make generate-docs' and commit results."
4444
git diff
4545
exit 1
46-
fi
46+
fi
47+
48+
csharp-test:
49+
name: 'C# Generator Test'
50+
runs-on: ubuntu-latest
51+
steps:
52+
- uses: actions/checkout@v4
53+
54+
- name: Set up Go
55+
uses: actions/setup-go@v5
56+
with:
57+
go-version-file: 'go.mod'
58+
59+
- name: Set up Docker
60+
uses: docker/setup-buildx-action@v2
61+
62+
- name: 'Run C# integration test'
63+
run: ./test/csharp-integration/test-compilation.sh

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ We welcome contributions for new generators to extend the functionality of the O
3232

3333
11. **Address Feedback**: Be responsive to feedback from the maintainers. Make any necessary changes and update your pull request as needed.
3434

35+
### Integration Tests
36+
37+
To verify that generated code compiles correctly, the project includes integration tests, for example, for c#:
38+
39+
```bash
40+
# Test the C# generator output
41+
make test-csharp
42+
```
43+
44+
This will:
45+
1. Build the CLI
46+
2. Generate a C# client
47+
3. Compile the C# code in a Docker container
48+
4. Validate that the code compiles correctly
49+
50+
Consider adding more integration tests for new generators.
51+
3552
## Templates
3653

3754
### Data

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ test:
55
@go test -v ./...
66
@echo "Tests passed successfully!"
77

8+
.PHONY: test-csharp
9+
test-csharp:
10+
@echo "Running C# integration test..."
11+
@./test/csharp-integration/test-compilation.sh
12+
813
generate-docs:
914
@echo "Generating documentation..."
1015
@go run ./docs/generate-commands.go

cmd/config.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {
1818
// Set the config file name and path
1919
v.SetConfigName(".openfeature")
2020
v.AddConfigPath(".")
21-
21+
2222
logger.Default.Debug("Looking for .openfeature config file in current directory")
2323

2424
// Read the config file
@@ -31,7 +31,6 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {
3131
} else {
3232
logger.Default.Debug(fmt.Sprintf("Using config file: %s", v.ConfigFileUsed()))
3333
}
34-
3534

3635
// Track which flags were set directly via command line
3736
cmdLineFlags := make(map[string]bool)
@@ -50,24 +49,24 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {
5049

5150
// Build configuration paths from most specific to least specific
5251
configPaths := []string{}
53-
52+
5453
// Check the most specific path (e.g., generate.go.package-name)
5554
if bindPrefix != "" {
56-
configPaths = append(configPaths, bindPrefix + "." + f.Name)
57-
55+
configPaths = append(configPaths, bindPrefix+"."+f.Name)
56+
5857
// Check parent paths (e.g., generate.package-name)
5958
parts := strings.Split(bindPrefix, ".")
6059
for i := len(parts) - 1; i > 0; i-- {
6160
parentPath := strings.Join(parts[:i], ".") + "." + f.Name
6261
configPaths = append(configPaths, parentPath)
6362
}
6463
}
65-
64+
6665
// Check the base path (e.g., package-name)
6766
configPaths = append(configPaths, f.Name)
68-
67+
6968
logger.Default.Debug(fmt.Sprintf("Looking for config value for flag %s in paths: %s", f.Name, strings.Join(configPaths, ", ")))
70-
69+
7170
// Try each path in order until we find a match
7271
for _, path := range configPaths {
7372
if v.IsSet(path) {
@@ -81,7 +80,7 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {
8180
}
8281
}
8382
}
84-
83+
8584
// Log the final value for the flag
8685
logger.Default.Debug(fmt.Sprintf("Final flag value: %s=%s", f.Name, f.Value.String()))
8786
})

cmd/generate.go

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/open-feature/cli/internal/config"
77
"github.com/open-feature/cli/internal/flagset"
88
"github.com/open-feature/cli/internal/generators"
9+
"github.com/open-feature/cli/internal/generators/csharp"
910
"github.com/open-feature/cli/internal/generators/golang"
1011
"github.com/open-feature/cli/internal/generators/nodejs"
1112
"github.com/open-feature/cli/internal/generators/python"
@@ -14,6 +15,32 @@ import (
1415
"github.com/spf13/cobra"
1516
)
1617

18+
func GetGenerateCmd() *cobra.Command {
19+
generateCmd := &cobra.Command{
20+
Use: "generate",
21+
Short: "Generate typesafe OpenFeature accessors.",
22+
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
23+
return initializeConfig(cmd, "generate")
24+
},
25+
RunE: func(cmd *cobra.Command, args []string) error {
26+
cmd.Println("Available generators:")
27+
return generators.DefaultManager.PrintGeneratorsTable()
28+
},
29+
}
30+
31+
// Add generate flags using the config package
32+
config.AddGenerateFlags(generateCmd)
33+
34+
// Add all registered generator commands
35+
for _, subCmd := range generators.DefaultManager.GetCommands() {
36+
generateCmd.AddCommand(subCmd)
37+
}
38+
39+
addStabilityInfo(generateCmd)
40+
41+
return generateCmd
42+
}
43+
1744
// addStabilityInfo adds stability information to the command's help template before "Usage:"
1845
func addStabilityInfo(cmd *cobra.Command) {
1946
// Only modify commands that have a stability annotation
@@ -37,7 +64,7 @@ func addStabilityInfo(cmd *cobra.Command) {
3764
}
3865
}
3966

40-
func GetGenerateNodeJSCmd() *cobra.Command {
67+
func getGenerateNodeJSCmd() *cobra.Command {
4168
nodeJSCmd := &cobra.Command{
4269
Use: "nodejs",
4370
Short: "Generate typesafe Node.js client.",
@@ -69,9 +96,9 @@ func GetGenerateNodeJSCmd() *cobra.Command {
6996
if err != nil {
7097
return err
7198
}
72-
99+
73100
logger.Default.GenerationComplete("Node.js")
74-
101+
75102
return nil
76103
},
77104
}
@@ -81,7 +108,7 @@ func GetGenerateNodeJSCmd() *cobra.Command {
81108
return nodeJSCmd
82109
}
83110

84-
func GetGenerateReactCmd() *cobra.Command {
111+
func getGenerateReactCmd() *cobra.Command {
85112
reactCmd := &cobra.Command{
86113
Use: "react",
87114
Short: "Generate typesafe React Hooks.",
@@ -95,7 +122,7 @@ func GetGenerateReactCmd() *cobra.Command {
95122
RunE: func(cmd *cobra.Command, args []string) error {
96123
manifestPath := config.GetManifestPath(cmd)
97124
outputPath := config.GetOutputPath(cmd)
98-
125+
99126
logger.Default.GenerationStarted("React")
100127

101128
params := generators.Params[react.Params]{
@@ -113,9 +140,9 @@ func GetGenerateReactCmd() *cobra.Command {
113140
if err != nil {
114141
return err
115142
}
116-
143+
117144
logger.Default.GenerationComplete("React")
118-
145+
119146
return nil
120147
},
121148
}
@@ -125,7 +152,57 @@ func GetGenerateReactCmd() *cobra.Command {
125152
return reactCmd
126153
}
127154

128-
func GetGenerateGoCmd() *cobra.Command {
155+
func getGenerateCSharpCmd() *cobra.Command {
156+
csharpCmd := &cobra.Command{
157+
Use: "csharp",
158+
Short: "Generate typesafe C# client.",
159+
Long: `Generate typesafe C# client compatible with the OpenFeature .NET SDK.`,
160+
Annotations: map[string]string{
161+
"stability": string(generators.Alpha),
162+
},
163+
PreRunE: func(cmd *cobra.Command, args []string) error {
164+
return initializeConfig(cmd, "generate.csharp")
165+
},
166+
RunE: func(cmd *cobra.Command, args []string) error {
167+
namespace := config.GetCSharpNamespace(cmd)
168+
manifestPath := config.GetManifestPath(cmd)
169+
outputPath := config.GetOutputPath(cmd)
170+
171+
logger.Default.GenerationStarted("C#")
172+
173+
params := generators.Params[csharp.Params]{
174+
OutputPath: outputPath,
175+
Custom: csharp.Params{
176+
Namespace: namespace,
177+
},
178+
}
179+
flagset, err := flagset.Load(manifestPath)
180+
if err != nil {
181+
return err
182+
}
183+
184+
generator := csharp.NewGenerator(flagset)
185+
logger.Default.Debug("Executing C# generator")
186+
err = generator.Generate(&params)
187+
if err != nil {
188+
return err
189+
}
190+
191+
logger.Default.GenerationComplete("C#")
192+
193+
return nil
194+
},
195+
}
196+
197+
// Add C#-specific flags
198+
config.AddCSharpGenerateFlags(csharpCmd)
199+
200+
addStabilityInfo(csharpCmd)
201+
202+
return csharpCmd
203+
}
204+
205+
func getGenerateGoCmd() *cobra.Command {
129206
goCmd := &cobra.Command{
130207
Use: "go",
131208
Short: "Generate typesafe accessors for OpenFeature.",
@@ -140,7 +217,7 @@ func GetGenerateGoCmd() *cobra.Command {
140217
goPackageName := config.GetGoPackageName(cmd)
141218
manifestPath := config.GetManifestPath(cmd)
142219
outputPath := config.GetOutputPath(cmd)
143-
220+
144221
logger.Default.GenerationStarted("Go")
145222

146223
params := generators.Params[golang.Params]{
@@ -161,9 +238,9 @@ func GetGenerateGoCmd() *cobra.Command {
161238
if err != nil {
162239
return err
163240
}
164-
241+
165242
logger.Default.GenerationComplete("Go")
166-
243+
167244
return nil
168245
},
169246
}
@@ -219,34 +296,9 @@ func getGeneratePythonCmd() *cobra.Command {
219296

220297
func init() {
221298
// Register generators with the manager
222-
generators.DefaultManager.Register(GetGenerateReactCmd)
223-
generators.DefaultManager.Register(GetGenerateGoCmd)
224-
generators.DefaultManager.Register(GetGenerateNodeJSCmd)
299+
generators.DefaultManager.Register(getGenerateReactCmd)
300+
generators.DefaultManager.Register(getGenerateGoCmd)
301+
generators.DefaultManager.Register(getGenerateNodeJSCmd)
225302
generators.DefaultManager.Register(getGeneratePythonCmd)
226-
}
227-
228-
func GetGenerateCmd() *cobra.Command {
229-
generateCmd := &cobra.Command{
230-
Use: "generate",
231-
Short: "Generate typesafe OpenFeature accessors.",
232-
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
233-
return initializeConfig(cmd, "generate")
234-
},
235-
RunE: func(cmd *cobra.Command, args []string) error {
236-
cmd.Println("Available generators:")
237-
return generators.DefaultManager.PrintGeneratorsTable()
238-
},
239-
}
240-
241-
// Add generate flags using the config package
242-
config.AddGenerateFlags(generateCmd)
243-
244-
// Add all registered generator commands
245-
for _, subCmd := range generators.DefaultManager.GetCommands() {
246-
generateCmd.AddCommand(subCmd)
247-
}
248-
249-
addStabilityInfo(generateCmd)
250-
251-
return generateCmd
303+
generators.DefaultManager.Register(getGenerateCSharpCmd)
252304
}

0 commit comments

Comments
 (0)