diff --git a/interpreter/template_interpreter.go b/interpreter/template_interpreter.go index 81b1202..3bc5bd2 100644 --- a/interpreter/template_interpreter.go +++ b/interpreter/template_interpreter.go @@ -128,12 +128,40 @@ func (ti *TemplateInterpreter) Interpret(masonryInput string, tmplText string) ( } return 8080 }, + "getServerHostPort": func(settings []lang.ServerSetting) string { + host := "localhost" + port := 8080 + for _, s := range settings { + if s.Host != nil { + host = *s.Host + } + if s.Port != nil { + port = *s.Port + } + } + return fmt.Sprintf("%s:%d", host, port) + }, "slice": func() []interface{} { return []interface{}{} }, "append": func(slice []interface{}, item interface{}) []interface{} { return append(slice, item) }, + "add": func(a, b int) int { + return a + b + }, + "derefString": func(s *string) string { + if s != nil { + return *s + } + return "" + }, + "derefInt": func(i *int) int { + if i != nil { + return *i + } + return 0 + }, }).Parse(tmplText)) data := struct { @@ -219,12 +247,40 @@ func NewTemplateRegistry() *TemplateRegistry { } return 8080 }, + "getServerHostPort": func(settings []lang.ServerSetting) string { + host := "localhost" + port := 8080 + for _, s := range settings { + if s.Host != nil { + host = *s.Host + } + if s.Port != nil { + port = *s.Port + } + } + return fmt.Sprintf("%s:%d", host, port) + }, "slice": func() []interface{} { return []interface{}{} }, "append": func(slice []interface{}, item interface{}) []interface{} { return append(slice, item) }, + "add": func(a, b int) int { + return a + b + }, + "derefString": func(s *string) string { + if s != nil { + return *s + } + return "" + }, + "derefInt": func(i *int) int { + if i != nil { + return *i + } + return 0 + }, } return tr } diff --git a/lang_templates/proto/application.proto.tmpl b/lang_templates/proto/application.proto.tmpl index 56ec33d..aabb749 100644 --- a/lang_templates/proto/application.proto.tmpl +++ b/lang_templates/proto/application.proto.tmpl @@ -1,105 +1,287 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. +// Code generated by Masonry DSL CLI tool. DO NOT EDIT. syntax = "proto3"; -package {{ .AppName }}; +{{- $serverName := "MyService" }} +{{- $serverHost := "localhost:8080" }} +{{- range .AST.Definitions }} + {{- if .Server }} + {{- $serverName = .Server.Name }} + {{- $serverHost = getServerHostPort .Server.Settings }} + {{- end }} +{{- end }} + +package {{ $serverName | title }}; import "gorm/options/gorm.proto"; -//import "gorm/types/types.proto"; +import "gorm/types/types.proto"; import "google/api/annotations.proto"; +import "google/protobuf/timestamp.proto"; option go_package = "./;pb"; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { info: { - title: "Your API Title" + title: "{{ $serverName | title }} API" version: "v1.0" - description: "Your API description" + description: "Generated API for {{ $serverName }}" } - host: "localhost:8080" // Set the server host + host: "{{ $serverHost }}" }; -service {{ .AppNameCaps }} { +service {{ $serverName | title }}Service { option (gorm.server).autogen = true; - // Add your service methods here - rpc CreateProduct (CreateProductRequest) returns (CreateProductResponse) { - option (google.api.http) = { - post: "/v1/Product" - body: "*" - }; - } + {{- range .AST.Definitions }} + {{- if .Entity }} + {{- $entityName := .Entity.Name }} - rpc ReadProduct (ReadProductRequest) returns (ReadProductResponse) { - option (google.api.http) = { - get: "/v1/Product/{id}" - }; - } + // CRUD operations for {{ $entityName }} + rpc Create{{ $entityName }} (Create{{ $entityName }}Request) returns (Create{{ $entityName }}Response) { + option (google.api.http) = { + post: "/v1/{{ $entityName | title }}" + body: "*" + }; + } - rpc ListProducts (ListProductsRequest) returns (ListProductsResponse) { - option (google.api.http) = { - get: "/v1/Product" - }; - } + rpc Read{{ $entityName }} (Read{{ $entityName }}Request) returns (Read{{ $entityName }}Response) { + option (google.api.http) = { + get: "/v1/{{ $entityName | title }}/{id}" + }; + } - rpc UpdateProduct (UpdateProductRequest) returns (UpdateProductResponse) { - option (google.api.http) = { - put: "/v1/Product" - body: "*" - }; - } + rpc List{{ $entityName }}s (List{{ $entityName }}sRequest) returns (List{{ $entityName }}sResponse) { + option (google.api.http) = { + get: "/v1/{{ $entityName | title }}" + }; + } - rpc DeleteProduct (DeleteProductRequest) returns (DeleteProductResponse) { - option (gorm.method).object_type = "Product"; - option (google.api.http) = { - delete: "/v1/Product/{id}" - }; - } + rpc Update{{ $entityName }} (Update{{ $entityName }}Request) returns (Update{{ $entityName }}Response) { + option (google.api.http) = { + put: "/v1/{{ $entityName | title }}" + body: "*" + }; + } + + rpc Delete{{ $entityName }} (Delete{{ $entityName }}Request) returns (Delete{{ $entityName }}Response) { + option (gorm.method).object_type = "{{ $entityName }}"; + option (google.api.http) = { + delete: "/v1/{{ $entityName | title }}/{id}" + }; + } + {{- end }} + {{- end }} + + {{- range .AST.Definitions }} + {{- if .Endpoint }} + {{- $method := .Endpoint.Method }} + {{- $path := .Endpoint.Path }} + {{- $entity := .Endpoint.Entity }} + {{- $handlerName := pathToHandlerName $path }} + + // Custom endpoint: {{ $method }} {{ $path }} + rpc {{ $handlerName }}{{ $method }} ({{ $handlerName }}{{ $method }}Request) returns ({{ $handlerName }}{{ $method }}Response) { + option (google.api.http) = { + {{- if eq $method "GET" }} + get: "{{ $path }}" + {{- else if eq $method "POST" }} + post: "{{ $path }}" + body: "*" + {{- else if eq $method "PUT" }} + put: "{{ $path }}" + body: "*" + {{- else if eq $method "PATCH" }} + patch: "{{ $path }}" + body: "*" + {{- else if eq $method "DELETE" }} + delete: "{{ $path }}" + {{- end }} + }; + } + {{- end }} + {{- end }} } -message Create{{ .ObjName }}Request { - {{ .ObjName }} payload = 1; +{{- range .AST.Definitions }} + {{- if .Entity }} + {{- $entityName := .Entity.Name }} + +// CRUD Request/Response messages for {{ $entityName }} +message Create{{ $entityName }}Request { + {{ $entityName }} payload = 1; } -message Create{{ .ObjName }}Response { - {{ .ObjName }} result = 1; +message Create{{ $entityName }}Response { + {{ $entityName }} result = 1; } -message Read{{ .ObjName }}Request { - uint64 id = 1; +message Read{{ $entityName }}Request { + {{- $idField := "" }} + {{- $idType := "uint64" }} + {{- range .Entity.Fields }} + {{- if eq .Name "id" }} + {{- $idField = .Name }} + {{- if eq .Type "uuid" }} + {{- $idType = "string" }} + {{- else if eq .Type "string" }} + {{- $idType = "string" }} + {{- else }} + {{- $idType = "uint64" }} + {{- end }} + {{- end }} + {{- end }} + {{ $idType }} id = 1; } -message Read{{ .ObjName }}Response { - {{ .ObjName }} result = 1; +message Read{{ $entityName }}Response { + {{ $entityName }} result = 1; } -message List{{ .ObjName }}sRequest {} - -message List{{ .ObjName }}sResponse { - repeated {{ .ObjName }} results = 1; +message List{{ $entityName }}sRequest { + int32 page_size = 1; + string page_token = 2; } -message Update{{ .ObjName }}Request { - {{ .ObjName }} payload = 1; +message List{{ $entityName }}sResponse { + repeated {{ $entityName }} results = 1; + string next_page_token = 2; } -message Update{{ .ObjName }}Response { - {{ .ObjName }} result = 1; +message Update{{ $entityName }}Request { + {{ $entityName }} payload = 1; } -message Delete{{ .ObjName }}Request { - uint64 id = 1; +message Update{{ $entityName }}Response { + {{ $entityName }} result = 1; } -message Delete{{ .ObjName }}Response {} +message Delete{{ $entityName }}Request { + {{- $idType := "uint64" }} + {{- range .Entity.Fields }} + {{- if eq .Name "id" }} + {{- if eq .Type "uuid" }} + {{- $idType = "string" }} + {{- else if eq .Type "string" }} + {{- $idType = "string" }} + {{- else }} + {{- $idType = "uint64" }} + {{- end }} + {{- end }} + {{- end }} + {{ $idType }} id = 1; +} -message {{ .ObjName }} { +message Delete{{ $entityName }}Response {} + {{- end }} +{{- end }} + +{{- range .AST.Definitions }} + {{- if .Endpoint }} + {{- $method := .Endpoint.Method }} + {{- $path := .Endpoint.Path }} + {{- $handlerName := pathToHandlerName $path }} + +// Custom endpoint messages for {{ $method }} {{ $path }} +message {{ $handlerName }}{{ $method }}Request { + {{- if .Endpoint.Params }} + {{- $fieldNum := 1 }} + {{- range .Endpoint.Params }} + {{ goType .Type }} {{ .Name }} = {{ $fieldNum }}; + {{- $fieldNum = add $fieldNum 1 }} + {{- end }} + {{- end }} +} + +message {{ $handlerName }}{{ $method }}Response { + {{- if .Endpoint.Response }} + {{- if eq .Endpoint.Response.Type "list" }} + repeated {{ .Endpoint.Entity }} results = 1; + {{- else }} + {{ .Endpoint.Response.Type }} result = 1; + {{- end }} + {{- else }} + bool success = 1; + {{- end }} +} + {{- end }} +{{- end }} + +{{- range .AST.Definitions }} + {{- if .Entity }} + {{- $entityName := .Entity.Name }} + +// {{ $entityName }} entity message +message {{ $entityName }} { option (gorm.opts).ormable = true; - uint64 id = 1; - // add object fields here + {{- if .Entity.Description }} + // {{ .Entity.Description }} + {{- end }} + + {{- $fieldNum := 1 }} + {{- range .Entity.Fields }} + {{- $protoType := "string" }} + {{- $gormTags := slice }} + + {{- if eq .Type "string" }} + {{- $protoType = "string" }} + {{- else if eq .Type "int" }} + {{- $protoType = "int64" }} + {{- else if eq .Type "uuid" }} + {{- $protoType = "string" }} + {{- $gormTags = append $gormTags "type:uuid" }} + {{- else if eq .Type "boolean" }} + {{- $protoType = "bool" }} + {{- else if eq .Type "timestamp" }} + {{- $protoType = "google.protobuf.Timestamp" }} + {{- else if eq .Type "text" }} + {{- $protoType = "string" }} + {{- $gormTags = append $gormTags "type:text" }} + {{- else }} + {{- $protoType = "string" }} + {{- end }} + + {{- if .Required }} + {{- $gormTags = append $gormTags "not null" }} + {{- end }} + + {{- if .Unique }} + {{- $gormTags = append $gormTags "unique" }} + {{- end }} + + {{- if .Index }} + {{- $gormTags = append $gormTags "index" }} + {{- end }} + + {{- if .Default }} + {{- $gormTags = append $gormTags (printf "default:%s" (derefString .Default)) }} + {{- end }} + + {{- if .Relationship }} + {{- if eq .Relationship.Cardinality "one" }} + {{- if .Relationship.ForeignKey }} + {{- $gormTags = append $gormTags (printf "foreignKey:%s" (derefString .Relationship.ForeignKey)) }} + {{- else }} + {{- $gormTags = append $gormTags (printf "foreignKey:%sID" .Name) }} + {{- end }} + {{- else if eq .Relationship.Cardinality "many" }} + {{- $protoType = printf "repeated %s" .Relationship.Type }} + {{- if .Relationship.Through }} + {{- $gormTags = append $gormTags (printf "many2many:%s" (derefString .Relationship.Through)) }} + {{- else }} + {{- $gormTags = append $gormTags (printf "foreignKey:%sID" $entityName) }} + {{- end }} + {{- end }} + {{- end }} + + {{ $protoType }} {{ .Name }} = {{ $fieldNum }} + {{- if $gormTags }} + [(gorm.field).tag = { + {{- range $i, $tag := $gormTags }} + {{- if $i }}, {{ end }}{{ $tag }} + {{- end }} + }] + {{- end }}; + {{- $fieldNum = add $fieldNum 1 }} + {{- end }} } - - - - - - + {{- end }} +{{- end }} diff --git a/readme.md b/readme.md index 8cf89d8..04cf78e 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,12 @@ Masonry is a library that provides and implements all the basics necessary to bu - [ ] - [ ] +## DSL Features + +- [ ] Indicate values that are based on Environment variables +- [ ] Yaml file to include custom template functions (Otto for JS) +- [ ] On Entities, we should indicate what CRUD functions should be implemented instead of implementing all, or forcing the user to define each in the Endpoints section + ## Design Philosophy (changeable...) The goal of this project is to make building software for web and mobile applications as fast as possible while maintaining diff --git a/test_proto_template.go b/test_proto_template.go new file mode 100644 index 0000000..e69de29