Skip to content

Commit 8921e6f

Browse files
committed
1 parent da419ac commit 8921e6f

4 files changed

Lines changed: 152 additions & 49 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.22.0
55
require (
66
github.com/fsnotify/fsnotify v1.7.0
77
gopkg.in/yaml.v2 v2.4.0
8+
gopkg.in/yaml.v3 v3.0.1
89
)
910

1011
require golang.org/x/sys v0.4.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
66
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
77
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
88
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

lib/service-gen.go

Lines changed: 138 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"go/ast"
88
"go/parser"
99
"go/token"
10+
"gopkg.in/yaml.v3"
11+
"io/fs"
1012
"os"
1113
"os/exec"
1214
"path/filepath"
@@ -16,23 +18,30 @@ import (
1618
)
1719

1820
type MethodInfo struct {
19-
OriginalName string
20-
Name string
21-
InputType string
22-
InputSchema map[string]interface{}
23-
OutputSchema map[string]interface{}
24-
IsWorkflow bool
25-
IsService bool
26-
IsPointer bool // Whether the input type is a pointer
21+
OriginalName string `yaml:"originalName"`
22+
Name string `yaml:"name"`
23+
InputType string `yaml:"inputType"`
24+
IsInputPointer bool `yaml:"isInputPointer"` // Whether the input type is a pointer
25+
InputSchema []Field `yaml:"inputSchema"`
26+
OutputType string `yaml:"outputType"`
27+
IsOutputPointer bool `yaml:"isOutputPointer"` // Whether the output type is a pointer
28+
OutputSchema []Field `yaml:"outputSchema"`
29+
IsWorkflow bool `yaml:"isWorkflow"`
30+
IsService bool `yaml:"isService"`
2731
}
2832

2933
type ServiceInfo struct {
30-
ModuleName string
31-
ServiceName string
32-
ServiceStructName string
33-
Methods []MethodInfo
34-
IsProduction bool // New flag to determine if we are in production mode
35-
Imports []string
34+
ModuleName string `yaml:"moduleName"`
35+
ServiceName string `yaml:"serviceName"`
36+
ServiceStructName string `yaml:"serviceStructName"`
37+
Methods []MethodInfo `yaml:"methods"`
38+
IsProduction bool // New flag to determine if we are in production mode
39+
Imports []string `yaml:"imports"`
40+
}
41+
42+
type Field struct {
43+
Name string `yaml:"name"`
44+
Type string `yaml:"type"`
3645
}
3746

3847
const wrapperTemplate = `package _polycode
@@ -89,7 +98,7 @@ func (t *{{.ServiceStructName}}) ExecuteService(ctx polycode.ServiceContext, met
8998
{{range .Methods}}{{if .IsService}}case "{{.Name}}":
9099
{
91100
// Pass the input correctly as a pointer or value based on the method signature
92-
{{if .IsPointer}}
101+
{{if .IsInputPointer}}
93102
return service.{{.OriginalName}}(ctx, input.(*{{.InputType}}))
94103
{{else}}
95104
return service.{{.OriginalName}}(ctx, *(input.(*{{.InputType}})))
@@ -110,7 +119,7 @@ func (t *{{.ServiceStructName}}) ExecuteWorkflow(ctx polycode.WorkflowContext, m
110119
{{range .Methods}}{{if .IsWorkflow}}case "{{.Name}}":
111120
{
112121
// Pass the input correctly as a pointer or value based on the method signature
113-
{{if .IsPointer}}
122+
{{if .IsInputPointer}}
114123
return service.{{.OriginalName}}(ctx, input.(*{{.InputType}}))
115124
{{else}}
116125
return service.{{.OriginalName}}(ctx, *(input.(*{{.InputType}})))
@@ -168,8 +177,46 @@ func getModuleName(filePath string) (string, error) {
168177
return "", fmt.Errorf("module name not found in go.mod")
169178
}
170179

171-
func generateService(appPath string, servicePath string, moduleName string, serviceName string, prod bool) error {
172-
methods, imports, err := parseDir(servicePath)
180+
func extractStructs(root string) (map[string][]Field, error) {
181+
structDefs := make(map[string][]Field)
182+
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
183+
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") {
184+
return err
185+
}
186+
187+
fset := token.NewFileSet()
188+
file, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
189+
if err != nil {
190+
return nil
191+
}
192+
193+
for _, decl := range file.Decls {
194+
if g, ok := decl.(*ast.GenDecl); ok && g.Tok == token.TYPE {
195+
for _, spec := range g.Specs {
196+
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
197+
if structType, ok := typeSpec.Type.(*ast.StructType); ok {
198+
var fields []Field
199+
for _, field := range structType.Fields.List {
200+
if len(field.Names) > 0 {
201+
fields = append(fields, Field{
202+
Name: field.Names[0].Name,
203+
Type: fmt.Sprint(field.Type),
204+
})
205+
}
206+
}
207+
structDefs[typeSpec.Name.Name] = fields
208+
}
209+
}
210+
}
211+
}
212+
}
213+
return nil
214+
})
215+
return structDefs, err
216+
}
217+
218+
func generateService(appPath string, servicePath string, moduleName string, serviceName string, structDefs map[string][]Field, prod bool) error {
219+
methods, imports, err := parseDir(servicePath, structDefs)
173220
if err != nil {
174221
fmt.Printf("Error parsing directory: %v\n", err)
175222
return err
@@ -180,7 +227,18 @@ func generateService(appPath string, servicePath string, moduleName string, serv
180227
return nil
181228
}
182229

183-
generatedCode, err := generateServiceCode(moduleName, serviceName, methods, imports, prod)
230+
serviceStructName := toPascalCase(serviceName)
231+
232+
serviceInfo := ServiceInfo{
233+
ModuleName: moduleName,
234+
ServiceName: serviceName,
235+
ServiceStructName: serviceStructName,
236+
Methods: methods,
237+
IsProduction: prod,
238+
Imports: imports,
239+
}
240+
241+
generatedCode, err := generateServiceCode(serviceInfo)
184242
if err != nil {
185243
fmt.Printf("Error generating code: %v\n", err)
186244
return err
@@ -198,6 +256,12 @@ func generateService(appPath string, servicePath string, moduleName string, serv
198256
return err
199257
}
200258

259+
err = writeServiceDefinition(appPath, serviceName, serviceInfo)
260+
if err != nil {
261+
fmt.Printf("Error writing service definition: %v\n", err)
262+
return err
263+
}
264+
201265
return nil
202266
}
203267

@@ -208,6 +272,12 @@ func GenerateServices(appPath string, prod bool) error {
208272
return err
209273
}
210274

275+
structDefs, err := extractStructs(appPath)
276+
if err != nil {
277+
fmt.Printf("Error extracting structs: %v\n", err)
278+
return err
279+
}
280+
211281
polycodeFolder := filepath.Join(appPath, ".polycode")
212282
servicesFolder := filepath.Join(appPath, "services")
213283

@@ -226,7 +296,7 @@ func GenerateServices(appPath string, prod bool) error {
226296
servicePath := filepath.Join(servicesFolder, entry.Name())
227297
println("Generating code for path: ", servicePath)
228298
serviceName := entry.Name()
229-
err = generateService(appPath, servicePath, moduleName, serviceName, prod)
299+
err = generateService(appPath, servicePath, moduleName, serviceName, structDefs, prod)
230300
if err != nil {
231301
fmt.Printf("Error generating service: %v\n", err)
232302
return err
@@ -251,7 +321,7 @@ func GenerateServices(appPath string, prod bool) error {
251321
return nil
252322
}
253323

254-
// Modified validateFunctionParams to check for polycode.ServiceContext or polycode.WorkflowContext
324+
// ValidateFunctionParams to check for polycode.ServiceContext or polycode.WorkflowContext
255325
func validateFunctionParams(fn *ast.FuncDecl) (string, error) {
256326
// Check if there are at least two parameters (ctx and input)
257327
if fn.Type.Params == nil || len(fn.Type.Params.List) < 2 {
@@ -276,7 +346,7 @@ func validateFunctionParams(fn *ast.FuncDecl) (string, error) {
276346
}
277347

278348
// Updated parseDir function to mark methods as workflow or service
279-
func parseDir(serviceFolder string) ([]MethodInfo, []string, error) {
349+
func parseDir(serviceFolder string, structDefs map[string][]Field) ([]MethodInfo, []string, error) {
280350
fset := token.NewFileSet()
281351

282352
var methods []MethodInfo
@@ -317,27 +387,42 @@ func parseDir(serviceFolder string) ([]MethodInfo, []string, error) {
317387
// Extract the function name and input/output parameters
318388
methodName := strings.ToLower(fn.Name.Name) // Normalize to lowercase
319389

320-
paramType := ""
321-
isPointer := false
390+
inputType := ""
391+
outputType := ""
392+
isInputPointer := false
393+
isOutputPointer := false
322394
// Handle pointer types and normal types
323395
if starExpr, ok := fn.Type.Params.List[1].Type.(*ast.StarExpr); ok {
324-
isPointer = true
396+
isInputPointer = true
325397
if selectorExpr, ok := starExpr.X.(*ast.SelectorExpr); ok {
326-
paramType = fmt.Sprintf("%s.%s", selectorExpr.X.(*ast.Ident).Name, selectorExpr.Sel.Name)
398+
inputType = fmt.Sprintf("%s.%s", selectorExpr.X.(*ast.Ident).Name, selectorExpr.Sel.Name)
327399
}
328400
} else if selectorExpr, ok := fn.Type.Params.List[1].Type.(*ast.SelectorExpr); ok {
329-
paramType = fmt.Sprintf("%s.%s", selectorExpr.X.(*ast.Ident).Name, selectorExpr.Sel.Name)
401+
inputType = fmt.Sprintf("%s.%s", selectorExpr.X.(*ast.Ident).Name, selectorExpr.Sel.Name)
402+
}
403+
404+
if starExpr, ok := fn.Type.Results.List[0].Type.(*ast.StarExpr); ok {
405+
isOutputPointer = true
406+
if selectorExpr, ok := starExpr.X.(*ast.SelectorExpr); ok {
407+
outputType = fmt.Sprintf("%s.%s", selectorExpr.X.(*ast.Ident).Name, selectorExpr.Sel.Name)
408+
}
409+
} else if selectorExpr, ok := fn.Type.Results.List[0].Type.(*ast.SelectorExpr); ok {
410+
outputType = fmt.Sprintf("%s.%s", selectorExpr.X.(*ast.Ident).Name, selectorExpr.Sel.Name)
330411
}
331412

332413
// Append the method and its corresponding input type to methods
333-
if paramType != "" {
414+
if inputType != "" && outputType != "" {
334415
methods = append(methods, MethodInfo{
335-
OriginalName: OriginalName,
336-
Name: methodName,
337-
InputType: paramType,
338-
IsPointer: isPointer, // Track whether the input type is a pointer
339-
IsWorkflow: contextType == "Workflow", // Mark as workflow or service
340-
IsService: contextType == "Service",
416+
OriginalName: OriginalName,
417+
Name: methodName,
418+
InputType: inputType,
419+
IsInputPointer: isInputPointer, // Track whether the input type is a pointer
420+
InputSchema: structDefs[inputType],
421+
OutputType: outputType,
422+
IsOutputPointer: isOutputPointer,
423+
OutputSchema: structDefs[outputType],
424+
IsWorkflow: contextType == "Workflow", // Mark as workflow or service
425+
IsService: contextType == "Service",
341426
})
342427
}
343428
}
@@ -383,19 +468,28 @@ func toPascalCase(input string) string {
383468
return strings.Join(words, "")
384469
}
385470

386-
// GenerateService the wrapper code based on the extracted information
387-
func generateServiceCode(moduleName string, serviceName string, methods []MethodInfo, imports []string, isProd bool) (string, error) {
388-
serviceStructName := toPascalCase(serviceName)
471+
func writeServiceDefinition(appPath string, serviceName string, serviceInfo ServiceInfo) error {
472+
outputDir := filepath.Join(appPath, ".polycode/definition")
473+
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
474+
return err
475+
}
389476

390-
serviceInfo := ServiceInfo{
391-
ModuleName: moduleName,
392-
ServiceName: serviceName,
393-
ServiceStructName: serviceStructName,
394-
Methods: methods,
395-
IsProduction: isProd,
396-
Imports: imports,
477+
ymlData, err := yaml.Marshal(serviceInfo)
478+
if err != nil {
479+
return err
397480
}
398481

482+
serviceFile := filepath.Join(outputDir, serviceName+".yml")
483+
if err := os.WriteFile(serviceFile, ymlData, 0644); err != nil {
484+
return err
485+
}
486+
487+
fmt.Printf("Generated definition for: %s\n", serviceFile)
488+
return nil
489+
}
490+
491+
// GenerateService the wrapper code based on the extracted information
492+
func generateServiceCode(serviceInfo ServiceInfo) (string, error) {
399493
// Use template to generate the code
400494
var buf bytes.Buffer
401495
tmpl, err := template.New("wrapper").Parse(wrapperTemplate)

main.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"github.com/cloudimpl/next-gen/lib"
67
"github.com/fsnotify/fsnotify"
78
"log"
@@ -86,11 +87,13 @@ func watch(appPath string, onChange func()) {
8687
<-done
8788
}
8889

89-
func generate(appPath string) {
90+
func generate(appPath string) error {
9091
err := lib.GenerateServices(appPath, true)
9192
if err != nil {
92-
log.Fatalf("Error generating services: %s\n", err.Error())
93+
return fmt.Errorf("Error generating services: %s\n", err.Error())
9394
}
95+
96+
return nil
9497
}
9598

9699
func watchAndGenerate(appPath string) {
@@ -103,9 +106,9 @@ func watchAndGenerate(appPath string) {
103106
log.Printf("Starting watcher on: %s", servicesPath)
104107

105108
watch(servicesPath, func() {
106-
err := lib.GenerateServices(appPath, true)
109+
err := generate(appPath)
107110
if err != nil {
108-
log.Printf("Error generating services: %v", err)
111+
log.Println(err.Error())
109112
}
110113
})
111114
}
@@ -151,6 +154,9 @@ func main() {
151154
if *watch {
152155
watchAndGenerate(appPath)
153156
} else {
154-
generate(appPath)
157+
err := generate(appPath)
158+
if err != nil {
159+
log.Fatalf(err.Error())
160+
}
155161
}
156162
}

0 commit comments

Comments
 (0)