Skip to content

Commit 793be1f

Browse files
committed
feat(generate): generate pkl testing
1 parent be65811 commit 793be1f

File tree

7 files changed

+843
-1
lines changed

7 files changed

+843
-1
lines changed

cmd/generate/generate.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package generate
2+
3+
import "github.com/spf13/cobra"
4+
5+
var GenerateCmd = &cobra.Command{
6+
Use: "generate",
7+
Short: "Generate code",
8+
Long: "Generate code to facilitate testing, modeling and working with OpenFGA.",
9+
}
10+
11+
func init() {
12+
GenerateCmd.AddCommand(pklCmd)
13+
}

cmd/generate/pkl.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package generate
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/openfga/cli/internal/authorizationmodel"
7+
"github.com/openfga/cli/internal/cmdutils"
8+
"github.com/openfga/cli/internal/generate"
9+
"github.com/openfga/cli/internal/output"
10+
openfga "github.com/openfga/go-sdk"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var pklCmd = &cobra.Command{
15+
Use: "pkl",
16+
Short: "Generate pkl test utilities",
17+
Long: "Generate pkl test utilities based on the given model",
18+
Example: `fga generate pkl --file=model.json
19+
fga generate --file=fga.mod
20+
fga generate '{"type_definitions":[{"type":"user"},{"type":"document","relations":{"can_view":{"this":{}}},"metadata":{"relations":{"can_view":{"directly_related_user_types":[{"type":"user"}]}}}}],"schema_version":"1.1"}' --format=json
21+
fga generate --file=fga.mod --out=testing
22+
fga generate --file=fga.mod --out=testing --config='{"user": {"base_type_name": "Awesome"}}'`, //nolint:lll
23+
Args: cobra.MaximumNArgs(1),
24+
RunE: func(cmd *cobra.Command, args []string) error {
25+
clientConfig := cmdutils.GetClientConfig(cmd)
26+
27+
_, err := clientConfig.GetFgaClient()
28+
if err != nil {
29+
return fmt.Errorf("failed to initialize FGA Client due to %w", err)
30+
}
31+
32+
var inputModel string
33+
if err := authorizationmodel.ReadFromInputFileOrArg(
34+
cmd,
35+
args,
36+
"file",
37+
false,
38+
&inputModel,
39+
openfga.PtrString(""),
40+
&writeInputFormat); err != nil {
41+
return err //nolint:wrapcheck
42+
}
43+
44+
authModel := authorizationmodel.AuthzModel{}
45+
46+
err = authModel.ReadModelFromString(inputModel, writeInputFormat)
47+
if err != nil {
48+
return err //nolint:wrapcheck
49+
}
50+
51+
out, err := cmd.Flags().GetString("out")
52+
if err != nil {
53+
return fmt.Errorf("failed to parse output directory due to %w", err)
54+
}
55+
config, err := cmd.Flags().GetString("config")
56+
if err != nil {
57+
return fmt.Errorf("failed to parse config due to %w", err)
58+
}
59+
cn := make(map[string]generate.PklConventionConfig)
60+
err = json.Unmarshal([]byte(config), &cn)
61+
if err != nil {
62+
return fmt.Errorf("failed to parse config content due to %w", err)
63+
}
64+
65+
g := &generate.PklGenerator{
66+
Model: authModel.TypeDefinitions,
67+
Convention: &generate.PklConvention{Config: cn},
68+
}
69+
files, err := g.Generate()
70+
err = files.SaveAll(out)
71+
if err != nil {
72+
return fmt.Errorf("failed to save generated files due to %w", err)
73+
}
74+
return output.Display(fmt.Sprintf("generated files in directory %v successfully", out))
75+
},
76+
}
77+
78+
var writeInputFormat = authorizationmodel.ModelFormatDefault
79+
80+
func init() {
81+
pklCmd.Flags().String("out", "testing", "Output of testing directory")
82+
pklCmd.Flags().String("config", "{}", "Generator configurations")
83+
pklCmd.Flags().String("file", "", "File Name. The file should have the model in the JSON or DSL format")
84+
pklCmd.Flags().Var(&writeInputFormat, "format", `Authorization model input format. Can be "fga", "json", or "modular"`) //nolint:lll
85+
}

cmd/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/spf13/cobra"
2626
"github.com/spf13/viper"
2727

28+
"github.com/openfga/cli/cmd/generate"
2829
"github.com/openfga/cli/cmd/model"
2930
"github.com/openfga/cli/cmd/query"
3031
"github.com/openfga/cli/cmd/store"
@@ -81,6 +82,7 @@ func init() {
8182
rootCmd.AddCommand(model.ModelCmd)
8283
rootCmd.AddCommand(tuple.TupleCmd)
8384
rootCmd.AddCommand(query.QueryCmd)
85+
rootCmd.AddCommand(generate.GenerateCmd)
8486
}
8587

8688
// initConfig reads in config file and ENV variables if set.

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.22.3
55
require (
66
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
77
github.com/hashicorp/go-multierror v1.1.1
8+
github.com/iancoleman/strcase v0.3.0
89
github.com/mattn/go-isatty v0.0.20
910
github.com/muesli/mango-cobra v1.2.0
1011
github.com/muesli/roff v0.1.0
@@ -19,6 +20,7 @@ require (
1920
github.com/spf13/viper v1.19.0
2021
github.com/stretchr/testify v1.9.0
2122
go.uber.org/mock v0.4.0
23+
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10
2224
google.golang.org/protobuf v1.34.1
2325
gopkg.in/yaml.v3 v3.0.1
2426
)
@@ -70,7 +72,6 @@ require (
7072
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
7173
go.uber.org/multierr v1.11.0 // indirect
7274
go.uber.org/zap v1.27.0 // indirect
73-
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect
7475
golang.org/x/net v0.25.0 // indirect
7576
golang.org/x/sync v0.7.0 // indirect
7677
golang.org/x/sys v0.20.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT
107107
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
108108
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
109109
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
110+
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
111+
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
110112
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
111113
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
112114
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=

internal/generate/generate.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package generate
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path"
8+
)
9+
10+
type GeneratedFile struct {
11+
Path string
12+
Content string
13+
OverrideIfExists bool
14+
}
15+
16+
type GeneratedFiles struct {
17+
Files []GeneratedFile
18+
}
19+
20+
func (f *GeneratedFiles) SaveAll(out string) error {
21+
for _, file := range f.Files {
22+
if err := file.Save(out); err != nil {
23+
return err
24+
}
25+
}
26+
return nil
27+
}
28+
29+
func (f *GeneratedFile) Save(out string) error {
30+
if f.OverrideIfExists {
31+
if err := f.writeFile(out, f.Path, f.Content); err != nil {
32+
return err
33+
}
34+
} else {
35+
if err := f.writeFileIfNotExists(out, f.Path, f.Content); err != nil {
36+
return err
37+
}
38+
}
39+
return nil
40+
}
41+
42+
func (f *GeneratedFile) writeFileIfNotExists(out, filePath, content string) error {
43+
pwd, err := os.Getwd()
44+
if err != nil {
45+
return fmt.Errorf("failed to get Working Directory %v", err)
46+
}
47+
48+
file := path.Join(pwd, out, filePath)
49+
if _, err = os.Stat(file); errors.Is(err, os.ErrNotExist) {
50+
dir := path.Dir(file)
51+
err := os.MkdirAll(dir, os.ModePerm)
52+
if err != nil {
53+
return err
54+
}
55+
56+
f, err := os.Create(file)
57+
if err != nil {
58+
return err
59+
}
60+
defer f.Close()
61+
62+
_, err = f.WriteString(content)
63+
if err != nil {
64+
return err
65+
}
66+
}
67+
return nil
68+
}
69+
70+
func (f *GeneratedFile) writeFile(out, filePath, content string) error {
71+
pwd, err := os.Getwd()
72+
if err != nil {
73+
return fmt.Errorf("failed to get Working Directory %v", err)
74+
}
75+
76+
fp := path.Join(pwd, out, filePath)
77+
dir := path.Dir(fp)
78+
err = os.MkdirAll(dir, os.ModePerm)
79+
if err != nil {
80+
return err
81+
}
82+
83+
file, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0644))
84+
if err != nil {
85+
return fmt.Errorf("failed to open or create file %v", err)
86+
}
87+
defer file.Close()
88+
89+
_, err = file.WriteString(content)
90+
if err != nil {
91+
return err
92+
}
93+
return nil
94+
}

0 commit comments

Comments
 (0)