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
1820type 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
2933type 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
3847const 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
255325func 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 )
0 commit comments