Moving server files to ./server

This commit is contained in:
Corteza Monorepo Migrator
2022-11-14 09:26:39 +01:00
parent 204738f7c9
commit 683c7c56e2
5750 changed files with 0 additions and 26430 deletions
+297
View File
@@ -0,0 +1,297 @@
package codegen
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/cortezaproject/corteza-server/pkg/handle"
"gopkg.in/yaml.v3"
)
type (
// definitions are in multiple files and each definition
// should produce one output
actionsDef struct {
Component string
Source string
outputDir string
// List of imports
// Used only by generated file and not pre-generated-user-file
Import []string `yaml:"import"`
Service string `yaml:"service"`
Resource string `yaml:"resource"`
// Default severity for actions
DefaultActionSeverity string `yaml:"defaultActionSeverity"`
// Default severity for errors
DefaultErrorSeverity string `yaml:"defaultErrorSeverity"`
// If at least one of the errors has HTTP status defined,
// add support for http errors
SupportHttpErrors bool
Props []*propsDef
Actions []*actionDef
Errors []*errorDef
}
// List of event/log properties that can/will be captured
// and injected into log or message string
propsDef struct {
Name string
Type string
Fields []string
Builtin bool
}
actionDef struct {
// Action name
Action string `yaml:"action"`
// String to log when action is successful
Log string `yaml:"log"`
// String to log when error was yield
//ErrorLog string `yaml:"errorLog"`
// Action severity
Severity string `yaml:"severity"`
}
// Event definition
errorDef struct {
// Error key
// message can contain {variables} from meta data
Error string `yaml:"error"`
// Error key
// message can contain {variables} from meta data
Message string `yaml:"message"`
// Formatted and readable audit log message
// message can contain {variables} from meta data
Log string `yaml:"log"`
// Longer message or error description that can help resolving the error
Details string `yaml:"details"`
// Relative link to content in the documentation
Documentation string `yaml:"documentation"`
// Reference to "safe" error
// safe error should hide any information that might cause
// personal data leakage or expose system internals
MaskedWith string `yaml:"maskedWith"`
// Error severity
Severity string `yaml:"severity"`
// HTTP Status code for this error
HttpStatus string `yaml:"httpStatus"`
}
)
// Processes multiple action definitions
func procActions(mm ...string) (dd []*actionsDef, err error) {
var (
f io.ReadCloser
d *actionsDef
)
dd = make([]*actionsDef, 0)
for _, m := range mm {
err = func() error {
if f, err = os.Open(m); err != nil {
return err
}
defer f.Close()
d = &actionsDef{Component: strings.SplitN(m, string(filepath.Separator), 2)[0]}
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return err
}
if err = actionNormalize(d); err != nil {
return err
}
d.Source = m
d.outputDir = path.Dir(m)
dd = append(dd, d)
return nil
}()
if err != nil {
return nil, fmt.Errorf("could not process %s: %w", m, err)
}
}
return dd, nil
}
func actionNormalize(d *actionsDef) error {
// Prepend generic error
d.Errors = append([]*errorDef{{
Error: "generic",
Message: "failed to complete request due to internal error",
Log: "{err}",
Severity: "error",
}}, d.Errors...)
// index known meta fields and sanitize types (no type => string type)
knownProps := map[string]bool{
"err": true,
}
for _, m := range d.Props {
if m.Type == "" {
m.Type = "string"
}
// very optimistic check if referenced type is builtin or not
m.Builtin = !strings.Contains(m.Type, ".")
knownProps[m.Name] = true
for _, f := range m.Fields {
knownProps[fmt.Sprintf("%s.%s", m.Name, f)] = true
}
}
for _, a := range d.Actions {
if a.Severity == "" {
a.Severity = d.DefaultActionSeverity
}
}
for _, e := range d.Errors {
if e.Severity == "" {
e.Severity = d.DefaultErrorSeverity
}
if e.HttpStatus != "" {
d.SupportHttpErrors = true
}
}
checkHandle := func(s string) error {
if !handle.IsValid(s) {
return fmt.Errorf("handle empty")
}
if !handle.IsValid(s) {
return fmt.Errorf("invalid handle format: %q", s)
}
return nil
}
placeholderMatcher := regexp.MustCompile(`\{\{(.+?)\}\}`)
checkPlaceholders := func(def string, kind, s string) error {
for _, match := range placeholderMatcher.FindAllStringSubmatch(s, 1) {
placeholder := match[1]
if !knownProps[placeholder] {
return fmt.Errorf("unknown placeholder %q used in %s for %s", placeholder, def, kind)
}
}
return nil
}
for _, a := range d.Actions {
checkHandle(a.Action)
if a.Log == "" {
// If no log is defined, use action handle
a.Log = a.Action
}
if err := checkPlaceholders(a.Action, "log", a.Log); err != nil {
return err
}
}
for _, e := range d.Errors {
if err := checkHandle(e.Error); err != nil {
return err
}
if err := checkPlaceholders(e.Error, "message", e.Message); err != nil {
return err
}
if err := checkPlaceholders(e.Error, "log", e.Log); err != nil {
return err
}
}
return nil
}
func (a actionsDef) Package() string {
return path.Base(path.Dir(a.Source))
}
func (a actionDef) SeverityConstName() string {
return severityConstName(a.Severity)
}
func (e errorDef) SeverityConstName() string {
return severityConstName(e.Severity)
}
func severityConstName(s string) string {
switch strings.ToLower(s) {
case "emergency":
return "actionlog.Emergency"
case "alert":
return "actionlog.Alert"
case "crit", "critical":
return "actionlog.Critical"
case "warn", "warning":
return "actionlog.Warning"
case "notice":
return "actionlog.Notice"
case "info", "informational":
return "actionlog.Info"
case "debug":
return "actionlog.Debug"
default:
return "actionlog.Err"
}
}
func genActions(tpl *template.Template, dd ...*actionsDef) (err error) {
var (
// Will only be generated if file does not exist previously
tplActionsGen = tpl.Lookup("actions.gen.go.tpl")
dst string
)
for _, d := range dd {
// Generic code, actions for every resource goes to a separated file
dst = path.Join(d.outputDir, path.Base(d.Source)[:strings.LastIndex(path.Base(d.Source), ".")]+".gen.go")
err = goTemplate(dst, tplActionsGen, d)
if err != nil {
return
}
}
return nil
}
+235
View File
@@ -0,0 +1,235 @@
package codegen
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"text/template"
. "github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
)
type (
// definitions are in one file
aFuncDefs struct {
Package string
Name string
Source string
Prefix string
outputDir string
// List of imports
// Used only by generated file and not pre-generated-user-file
Imports []string
Functions aFunctionSet
}
aFunctionSet []*aFuncDef
aFuncDef struct {
Name string
Kind string
Labels map[string]string
Meta *aFuncMetaDef
Params aFuncParamSet
Results aFuncResultSet
}
aFuncParamSet []*aFuncParamDef
aFuncResultSet []*aFuncResultDef
aFuncParamDef struct {
Name string
Required bool
IsArray bool `yaml:"isArray"`
Types []*aFuncParamTypeVarDef
Meta *aFuncParamMetaDef
}
aFuncParamTypeVarDef struct {
WorkflowType string `yaml:"wf"`
GoType string `yaml:"go"`
Suffix string
}
aFuncResultDef struct {
Name string
IsArray bool `yaml:"isArray"`
WorkflowType string `yaml:"wf"`
GoType string `yaml:"go"`
Meta *aFuncParamMetaDef
}
aFuncMetaDef struct {
Short string
Description string
Visual map[string]interface{}
}
aFuncParamMetaDef struct {
Label string
Description string
Visual map[string]interface{}
}
)
func procAutomationFunctions(mm ...string) (dd []*aFuncDefs, err error) {
for _, m := range mm {
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
var (
d = &aFuncDefs{
Package: "automation",
Source: m,
Name: path.Base(m),
outputDir: path.Dir(m),
}
)
d.Name = d.Name[:len(d.Name)-13]
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return nil, fmt.Errorf("could not decode %s: %w", m, err)
}
dd = append(dd, d)
}
return
}
func (set *aFunctionSet) UnmarshalYAML(n *yaml.Node) error {
return Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := &aFuncDef{Name: k.Value}
if err = v.Decode(&def); err != nil {
return err
}
if def.Kind == "" {
def.Kind = "function"
}
*set = append(*set, def)
return nil
})
}
func (set *aFuncParamSet) UnmarshalYAML(n *yaml.Node) error {
return Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := aFuncParamDef{}
if k != nil {
def.Name = k.Value
}
*set = append(*set, &def)
return v.Decode(&def)
})
}
func (set *aFuncResultSet) UnmarshalYAML(n *yaml.Node) error {
return Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := aFuncResultDef{}
if k != nil {
def.Name = k.Value
}
*set = append(*set, &def)
return v.Decode(&def)
})
}
func expandAutomationFunctionTypes(ff []*aFuncDefs, tt []*exprTypesDef) {
// index of all known types
ti := make(map[string]*exprTypeDef)
for _, t := range tt {
for typ, d := range t.Types {
ti[typ] = d
}
}
for _, f := range ff {
for _, fn := range f.Functions {
for _, p := range fn.Params {
for _, t := range p.Types {
if ti[t.WorkflowType] == nil {
fmt.Printf("%s/%s(): unknown type %q used for param %q\n", f.Prefix, fn.Name, t.WorkflowType, p.Name)
continue
}
if t.GoType == "" {
t.GoType = ti[t.WorkflowType].As
if "[]TypedValue" == t.GoType {
t.GoType = "[]expr.TypedValue"
}
}
if t.Suffix == "" && len(p.Types) > 1 {
t.Suffix = t.WorkflowType
}
}
}
for _, r := range fn.Results {
if ti[r.WorkflowType] == nil {
fmt.Printf("%s/%s(): unknown type %q used for result %q\n", f.Prefix, fn.Name, r.WorkflowType, r.Name)
continue
}
if r.GoType == "" {
r.GoType = ti[r.WorkflowType].As
if "[]TypedValue" == r.GoType {
r.GoType = "[]expr.TypedValue"
}
}
}
}
}
}
func genAutomationFunctions(tpl *template.Template, dd ...*aFuncDefs) (err error) {
var (
// Will only be generated if file does not exist previously
tplAFuncGen = tpl.Lookup("afunc.gen.go.tpl")
dst string
)
for _, d := range dd {
// Generic code, actions for every resource goes to a separated file
dst = path.Join(d.outputDir, path.Base(d.Source)[:strings.LastIndex(path.Base(d.Source), ".")]+".gen.go")
json.NewEncoder(os.Stdout).SetIndent("", " ")
err = goTemplate(dst, tplAFuncGen, d)
if err != nil {
return
}
}
return nil
}
// genAutomationFunctionDocs look for afunc.gen.adoc.tpl and generates afunc.gen.adoc from it
func genAutomationFunctionDocs(tpl *template.Template, docsPath string, dd ...*aFuncDefs) (err error) {
var (
typeGenAdoc = tpl.Lookup("afunc.gen.adoc.tpl")
dst string
)
dst = path.Join(docsPath, "afunc.gen.adoc")
return plainTemplate(dst, typeGenAdoc, map[string]interface{}{
"Definitions": dd,
})
}
@@ -0,0 +1,320 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"context"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/locale"
{{- range .Import }}
{{ normalizeImport . }}
{{- end }}
"strings"
"time"
)
type (
{{ $.Service }}ActionProps struct {
{{- range $prop := $.Props }}
{{ $prop.Name }} {{ $prop.Type }}
{{- end }}
}
{{ if $.Actions }}
{{ $.Service }}Action struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *{{ $.Service }}ActionProps
}
{{ end }}
{{ $.Service }}LogMetaKey struct {}
{{ $.Service }}PropsMetaKey struct {}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
{{- range $prop := $.Props }}
// {{ camelCase "set" $prop.Name }} updates {{ $.Service }}ActionProps's {{ $prop.Name }}
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *{{ $.Service }}ActionProps) {{ camelCase "set" $prop.Name }}({{ $prop.Name }} {{ $prop.Type }}) *{{ $.Service }}ActionProps {
p.{{ $prop.Name }} = {{ $prop.Name }}
return p
}
{{ end }}
// Serialize converts {{ $.Service }}ActionProps to actionlog.Meta
//
// This function is auto-generated.
//
func (p {{ $.Service }}ActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
{{ range $prop := $.Props }}
{{- if $prop.Builtin }}
m.Set("{{ $prop.Name }}", p.{{ $prop.Name }}, true)
{{- else }}
if p.{{ $prop.Name }} != nil {
{{- range $f := $prop.Fields }}
m.Set("{{ $prop.Name }}.{{ $f }}", p.{{ $prop.Name }}.{{ camelCase " " $f }}, true)
{{- end }}
}
{{- end }}
{{- end }}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
//
func (p {{ $.Service }}ActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{"{{"}}err}}"}
{{- if $.Props }}
// first non-empty string
fns = func(ii ... interface{}) string {
for _, i:= range ii {
if s :=fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
{{- end }}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
{{- range $prop := $.Props }}
{{- if $prop.Builtin }}
pairs = append(pairs, "{{"{{"}}{{ $prop.Name }}}}", fns(p.{{ $prop.Name }}))
{{- else }}
if p.{{ $prop.Name }} != nil {
// replacement for "{{"{{"}}{{ $prop.Name }}}}" (in order how fields are defined)
pairs = append(
pairs,
"{{"{{"}}{{ $prop.Name }}}}",
fns(
{{- range $f := $prop.Fields }}
p.{{ $prop.Name }}.{{ camelCase " " $f }},
{{- end }}
),
)
{{- range $f := $prop.Fields }}
pairs = append(pairs, "{{"{{"}}{{ $prop.Name }}.{{ $f }}}}", fns(p.{{ $prop.Name }}.{{ camelCase " " $f }}))
{{- end }}
}
{{- end }}
{{- end }}
return strings.NewReplacer(pairs...).Replace(in)
}
{{ if $.Actions }}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
//
func (a *{{ $.Service }}Action) String() string {
var props = &{{ $.Service }}ActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *{{ $.Service }}Action) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
{{ end }}
{{ if $.Actions }}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
{{ range $a := $.Actions }}
// {{ camelCase "" $.Service "Action" $a.Action }} returns "{{ $.Resource }}.{{ $a.Action }}" action
//
// This function is auto-generated.
//
func {{ camelCase "" $.Service "Action" $a.Action }}(props ... *{{ $.Service }}ActionProps) *{{ $.Service }}Action {
a := &{{ $.Service }}Action{
timestamp: time.Now(),
resource: "{{ $.Resource }}",
action: "{{ $a.Action }}",
log: "{{ $a.Log }}",
severity: {{ $a.SeverityConstName }},
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
{{ end }}
{{ end }}
{{ if $.Errors }}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
{{ range $e := $.Errors }}
// {{ camelCase "" $.Service "Err" $e.Error }} returns "{{ $.Resource }}.{{ $e.Error }}" as *errors.Error
//
{{- if $e.MaskedWith }}
// Note: This error will be wrapped with safe ({{ $.Resource }}.{{ $e.MaskedWith }}) error!
{{- end }}
//
// This function is auto-generated.
//
func {{ camelCase "" $.Service "Err" $e.Error }}(mm ... *{{ $.Service }}ActionProps) *errors.Error {
var p = &{{ $.Service }}ActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
{{ if $e.Message }}p.Format({{ printf "%q" $e.Message }}, nil){{ else }}{{ printf "%q" $e.Error }}{{ end }},
errors.Meta("type", {{ printf "%q" $e.Error }}),
errors.Meta("resource", {{ printf "%q" $.Resource }}),
{{ if $e.Documentation }}
// link to documentation; formatting applies in case we need some special link formatting
errors.Meta("documentation", p.Format({{ printf "%q" $e.Documentation }}, nil)),
{{ end -}}
{{ if $e.Details }}
// details, used in detailed eror reporting
errors.Meta("details", p.Format({{ printf "%q" $e.Details }}, nil)),
{{ end -}}
{{- if $e.Log }}
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta({{ $.Service }}LogMetaKey{}, {{ printf "%q" $e.Log }}),
{{ end -}}
errors.Meta({{ $.Service }}PropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, {{ printf "%q" $.Component }}),
errors.Meta(locale.ErrorMetaKey{}, "{{ kebabCase $.Service }}.errors.{{ $e.Error }}"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
{{ if $e.MaskedWith }}
// Wrap with safe error
e = {{ camelCase "" $.Service "Err" $e.MaskedWith }}().Wrap(e)
{{ end }}
return e
}
{{ end }}
{{ end }}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
//
func (svc {{ $.Service }}) recordAction(ctx context.Context, props *{{ $.Service }}ActionProps, actionFn func(... *{{ $.Service }}ActionProps) *{{ $.Service }}Action, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString({{ $.Service }}LogMetaKey{}), err)
if p, has := m[{{ $.Service }}PropsMetaKey{}]; has {
a.Meta = p.(*{{ $.Service }}ActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}
@@ -0,0 +1,63 @@
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
{{- range .Definitions }}
// - {{ .Source }}
{{- end }}
{{ range $d := .Definitions }}
= `{{ $d.Name }}`
[cols="2m,4a,3a"]
|===
| Name | Description | I/O
{{- range $f := .Functions }}
{{- if eq $f.Kind "function" }}
| [#fnc-{{ toLower $d.Name }}-{{ toLower $f.Name }}]#<<fnc-{{ toLower $d.Name }}-{{ toLower $f.Name }},{{ if $f.Meta.Short }}{{ $f.Meta.Short }}{{ else }}{{ $f.Name }}{{ end }}>>#
| {{ if $f.Meta.Description }}{{ $f.Meta.Description }}{{ end }}
|
{{- if gt (len $f.Params) 0}}
.Parameters:
{{- range $p := $f.Params}}
* {{ if $p.Required }}#*# {{ end }}`{{ if $p.Meta }}
{{- if $p.Meta.Label }}
{{- $p.Meta.Label }}
{{- else }}
{{- $p.Name }}
{{- end }}
{{- else }}
{{- $p.Name }}
{{- end }}`
({{- range $pti, $pt := $p.Types }}
`{{- $pt.WorkflowType }}`,
{{- end }})
{{- end }}
{{- end }}
{{- if gt (len $f.Results) 0}}
.Results:
{{- range $r := $f.Results}}
* {{ if $r.Meta }}
{{- if $r.Meta.Label }}
{{- $r.Meta.Label }}
{{- else }}
{{- $r.Name }}
{{- end }}
{{- else }}
{{- $r.Name }}
{{- end }} (`{{ $r.WorkflowType }}`)
{{- end }}
{{- end }}
{{- end }}
{{- end }}
|===
{{- end }}
+269
View File
@@ -0,0 +1,269 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
atypes "github.com/cortezaproject/corteza-server/automation/types"
"context"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/wfexec"
{{- range .Imports }}
{{ normalizeImport . }}
{{- end }}
)
var _ wfexec.ExecResponse
type (
{{ $.Name }}HandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h {{ $.Name }}Handler) register() {
h.reg.AddFunctions(
{{- range .Functions }}
h.{{ export .Name }}(),
{{- end }}
)
}
{{ range .Functions }}
{{ $REF := unexport $.Prefix $.Name .Name }}
{{ $ARGS := unexport $.Name .Name "Args" }}
{{ $RESULTS := unexport $.Name .Name "Results" }}
type (
{{ $ARGS }} struct {
{{ range .Params }}
{{ $name := .Name }}
{{ $isArray := .IsArray }}
has{{ export .Name }} bool
{{- if gt (len .Types) 1 }}
{{ export .Name }} interface{}
{{- range .Types }}
{{ $name }}{{ export .Suffix }} {{ if $isArray }}[]{{ end }}{{ .GoType }}
{{- end }}
{{- else -}}
{{ range .Types }}
{{ export $name }}{{ export .Suffix }} {{ if $isArray }}[]{{ end }}{{ .GoType }}
{{- end }}
{{- end -}}
{{- end }}
}
{{ if .Results }}
{{ unexport $.Name .Name }}Results struct {
{{ range .Results }}
{{ export .Name }} {{ if .IsArray }}[]{{ end }}{{ .GoType }}
{{- end }}
}
{{- end }}
)
{{ range .Params }}
{{- if gt (len .Types) 1 }}
{{ $name := .Name }}
{{ $isArray := .IsArray }}
func (a {{ $ARGS }}) {{ export "get" $name }}() (bool, {{ range .Types }}{{ if $isArray }}[]{{ end }}{{ .GoType }},{{ end }}) {
return a.has{{ export $name }}{{ range .Types }}, a.{{ $name }}{{ export .Suffix }}{{ end }}
}
{{- end }}
{{- end }}
// {{ export .Name }} function {{ .Meta.Short }}
//
// expects implementation of {{ .Name }} function:
// func (h {{ $.Name }}Handler) {{ .Name }}(ctx context.Context, args *{{ $ARGS }}) ({{ if .Results }}results *{{ $RESULTS }}, {{ end }}err error) {
// return
// }
func (h {{ $.Name }}Handler) {{ export .Name }}() *atypes.Function {
return &atypes.Function{
Ref: {{ printf "%q" ( $REF ) }},
Kind: {{ printf "%q" .Kind }},
Labels: {{ printf "%#v" .Labels }},
{{- if .Meta }}
Meta: &atypes.FunctionMeta{
{{- if .Meta.Short }}
Short: {{ printf "%q" .Meta.Short }},
{{- end }}
{{- if .Meta.Description }}
Description: {{ printf "%q" .Meta.Description }},
{{- end }}
{{- if .Meta.Visual }}
Visual: {{ printf "%#v" .Meta.Visual }},
{{- end }}
},
{{- end }}
Parameters: []*atypes.Param{
{{- range .Params }}
{
Name: {{ printf "%q" .Name }},
Types: []string{ {{ range .Types }}{{ printf "%q" .WorkflowType }},{{ end }} },
{{- if .Required }}Required: true,{{ end }}
{{- if .IsArray }}IsArray: true,{{ end }}
{{- if .Meta }}
Meta: &atypes.ParamMeta{
{{- if .Meta.Label }}
Label: {{ printf "%#v" .Meta.Label }},
{{- end }}
{{- if .Meta.Description }}
Description: {{ printf "%#v" .Meta.Description }},
{{- end }}
{{- if .Meta.Visual }}
Visual: {{ printf "%#v" .Meta.Visual }},
{{- end }}
},
{{ end }}
},
{{- end }}
},
{{ if .Results }}
Results: []*atypes.Param{
{{ range .Results }}
{
Name: {{ printf "%q" .Name }},
Types: []string{ {{ printf "%q" .WorkflowType }} },
{{ if .IsArray }}IsArray: true,{{ end -}}
{{ if .Meta -}}
Meta: &atypes.ParamMeta{
{{- if .Meta.Label }}
Label: {{ printf "%#v" .Meta.Label }},
{{- end }}
{{- if .Meta.Description }}
Description: {{ printf "%#v" .Meta.Description }},
{{- end }}
{{- if .Meta.Visual }}
Visual: {{ printf "%#v" .Meta.Visual }},
{{- end }}
},
{{ end }}
},
{{ end }}
},
{{ end }}
{{ if eq .Kind "iterator" }}
Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) {
var (
args = &{{ $ARGS }}{
{{- range .Params }}
has{{ export .Name }}: in.Has({{ printf "%q" .Name }}),
{{- end }}
}
)
{{ template "params" .Params }}
return h.{{ .Name }}(ctx, args)
},
{{ else }}
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &{{ $ARGS }}{
{{- range .Params }}
has{{ export .Name }}: in.Has({{ printf "%q" .Name }}),
{{- end }}
}
)
{{ template "params" .Params }}
{{ if .Results }}
var results *{{ $RESULTS }}
if results, err = h.{{ .Name }}(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{{- range .Results }}
{{ if .IsArray }}
{
// converting results.{{ export .Name }} ({{ .GoType }}) to Array (of {{ .WorkflowType }})
var (
tval expr.TypedValue
tarr = make([]expr.TypedValue, len(results.{{ export .Name }}))
)
for i := range results.{{ export .Name }} {
if tarr[i], err = h.reg.Type({{ printf "%q" .WorkflowType }}).Cast(results.{{ export .Name }}[i]); err != nil {
return
}
}
if tval, err = expr.NewArray(tarr); err != nil {
return
} else if err = expr.Assign(out, {{ printf "%q" .Name }}, tval); err != nil {
return
}
}
{{ else }}
{
// converting results.{{ export .Name }} ({{ .GoType }}) to {{ .WorkflowType }}
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type({{ printf "%q" .WorkflowType }}).Cast(results.{{ export .Name }}); err != nil {
return
} else if err = expr.Assign(out, {{ printf "%q" .Name }}, tval); err != nil {
return
}
}
{{ end }}
{{ end }}
return
{{- else }}
return out, h.{{ .Name }}(ctx, args)
{{- end }}
},
{{ end }}
}
}
{{ end }}
{{ define "params" }}
if err = in.Decode(args); err != nil {
return
}
{{ range . }}
{{ $name := .Name }}
{{ $isArray := .IsArray }}
{{ if gt (len .Types) 1 }}
// Converting {{ export .Name }} argument
if args.has{{ export .Name }} {
aux := expr.Must(expr.Select(in, {{ printf "%q" .Name }}))
switch aux.Type() {
{{- range .Types }}
case h.reg.Type({{ printf "%q" .WorkflowType }}).Type():
args.{{ $name }}{{ export .Suffix }} = aux.Get().({{ if $isArray }}[]{{ end }}{{ .GoType }})
{{- end -}}
}
}
{{- end }}
{{ end }}
{{ end }}
@@ -0,0 +1,55 @@
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
{{- range .Definitions }}
// - {{ .Source }}
{{- end }}
= Resources and events
{{- range .Definitions }}
{{- range .Resources }}
== {{ .ResourceString }}
=== Events
{{- if .BeforeAfter }}
.Before/after events:
{{- range $ba := .BeforeAfter }}
* `before('{{ $ba }}')`
{{- end }}
{{- range $ba := .BeforeAfter }}
* `after('{{ $ba }}')`
{{- end }}
{{- end }}
{{ if .On -}}
.On events:
{{- range $on := .On }}
* `on('{{ $on }}')`
{{- end }}
{{- end }}
=== Exec arguments
.Argument properties:
[%header, cols=3*]
|===
|Name|Type|Mutable
{{- range $p := .Properties }}
| `{{ $p.Name }}`
| `{{ $p.Type }}`
{{- if $p.Immutable }}
| no
{{ else }}
| yes
{{ end -}}
{{ end -}}
|===
{{- end }}
{{- end }}
+240
View File
@@ -0,0 +1,240 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"encoding/json"
{{- range .Imports }}
{{ normalizeImport . }}
{{- end }}
"github.com/cortezaproject/corteza-server/pkg/expr"
)
// dummy placing to simplify import generation logic
var _ = json.NewEncoder
type (
{{ range $r := $.Resources }}
// {{ camelCase $r.ResourceIdent "base" }}
//
// This type is auto-generated.
{{ camelCase $r.ResourceIdent "base" }} struct {
immutable bool
{{- range $r.Properties }}
{{ .Name }} {{ .Type }}
{{- end }}
}
{{ range $event := $r.Events }}
// {{ camelCase $r.ResourceIdent $event }}
//
// This type is auto-generated.
{{ camelCase $r.ResourceIdent $event }} struct {
*{{ camelCase $r.ResourceIdent "base" }}
}
{{ end }}
{{ end }}
)
{{ range $r := $.Resources }}
// ResourceType returns "{{ $r.ResourceString }}"
//
// This function is auto-generated.
func ({{ camelCase .ResourceIdent "base" }}) ResourceType() string {
return "{{ .ResourceString }}"
}
{{ range $event := $r.Events }}
// EventType on {{ camelCase $r.ResourceIdent $event }} returns "{{ $event }}"
//
// This function is auto-generated.
func ({{ camelCase $r.ResourceIdent $event }}) EventType() string {
return "{{ $event }}"
}
{{ end }}
{{ range $event := $r.Events }}
// {{ camelCase "" $r.ResourceIdent $event }} creates {{ $event }} for {{ $r.ResourceString }} resource
//
// This function is auto-generated.
func {{ camelCase "" $r.ResourceIdent $event }}(
{{- range $r.Properties }}
{{- if not .Internal }}
{{ camelCase "arg" .Name }} {{ .Type }},
{{- end -}}
{{- end}}
) *{{ camelCase $r.ResourceIdent $event }} {
return &{{ camelCase $r.ResourceIdent $event }}{
{{ camelCase $r.ResourceIdent "base" }}: &{{ camelCase $r.ResourceIdent "base" }}{
immutable: false,
{{- range $r.Properties }}
{{- if not .Internal }}
{{ .Name }}: {{ camelCase "arg" .Name }},
{{- end -}}
{{- end}}
},
}
}
// {{ camelCase "" $r.ResourceIdent $event "Immutable" }} creates {{ $event }} for {{ $r.ResourceString }} resource
//
// None of the arguments will be mutable!
//
// This function is auto-generated.
func {{ camelCase "" $r.ResourceIdent $event "Immutable" }}(
{{- range $r.Properties }}
{{- if not .Internal }}
{{ camelCase "arg" .Name }} {{ .Type }},
{{- end -}}
{{- end}}
) *{{ camelCase $r.ResourceIdent $event }} {
return &{{ camelCase $r.ResourceIdent $event }}{
{{ camelCase $r.ResourceIdent "base" }}: &{{ camelCase $r.ResourceIdent "base" }}{
immutable: true,
{{- range $r.Properties }}
{{- if not .Internal }}
{{ .Name }}: {{ camelCase "arg" .Name }},
{{- end -}}
{{- end}}
},
}
}
{{ end }}
{{ range $r.Properties }}
{{ if not .Immutable }}
// {{ camelCase "Set" .Name }} sets new {{ .Name }} value
//
// This function is auto-generated.
func (res *{{ camelCase $r.ResourceIdent "base" }}) {{ camelCase "Set" .Name }}({{ camelCase "arg" .Name }} {{ .Type }}) {
res.{{ .Name }} = {{ camelCase "arg" .Name }}
}
{{ end }}
// {{ camelCase "" .Name }} returns {{ .Name }}
//
// This function is auto-generated.
func (res {{ camelCase $r.ResourceIdent "base" }}) {{ camelCase "" .Name }}() {{ .Type }} {
return res.{{ .Name }}
}
{{ end }}
// Encode internal data to be passed as event params & arguments to triggered Corredor script
func (res {{ camelCase .ResourceIdent "base" }}) Encode() (args map[string][]byte, err error) {
{{- if $r.Properties }}
args = make(map[string][]byte)
{{ range $prop := $r.Properties }}
if args["{{ $prop.Name }}"], err = json.Marshal(res.{{ $prop.Name }}); err != nil {
return nil, err
}
{{ end }}
{{ else }}
// Handle argument encoding
{{ end -}}
return
}
// Encode internal data to be passed as event params & arguments to workflow
func (res {{ camelCase .ResourceIdent "base" }}) EncodeVars() (out *expr.Vars, err error) {
{{- if $r.Properties }}
out = &expr.Vars{}
var v expr.TypedValue
{{ range $r.Properties }}
{{- if .ExprType }}
if v, err = automation.{{ export "new" .ExprType }}(res.{{ .Name }}); err == nil {
err = out.Set({{ printf "%q" .Name }},v)
}
if err != nil {
return
}
{{- else }}
// Could not found expression-type counterpart for {{ .Type }}
{{- end }}
{{ end }}
{{ end -}}
_ = v
return
}
// Decode return values from Corredor script into struct props
func (res *{{ camelCase .ResourceIdent "base" }}) Decode(results map[string][]byte)( err error) {
if res.immutable {
// Respect immutability
return
}
{{- if $r.Result }}
if res.{{ $r.Result }} != nil {
if r, ok := results["result"]; ok && len(results) == 1 {
if err = json.Unmarshal(r, res.{{ $r.Result }}); err != nil {
return
}
}
}
{{ end -}}
{{- range $prop := $r.Properties }}
{{- if not $prop.Immutable }}
if res.{{ $prop.Name }} != nil {
if r, ok := results["{{ $prop.Name }}"]; ok {
if err = json.Unmarshal(r, res.{{ $prop.Name }}); err != nil {
return
}
}
}
{{ else }}
// Do not decode {{ $prop.Name }}; marked as immutable
{{ end -}}
{{ end -}}
return
}
func (res *{{ camelCase .ResourceIdent "base" }}) DecodeVars(vars *expr.Vars) (err error) {
if res.immutable {
// Respect immutability
return
}
{{- range $r.Properties }}
{{- if .Immutable }}
// {{ .Name }} marked as immutable
{{- else }}
{{- if .ExprType }}
if res.{{ .Name }} != nil && vars.Has({{ printf "%q" .Name }}) {
var aux *automation.{{ export .ExprType }}
aux, err = automation.{{ export "new" .ExprType }}(expr.Must(vars.Select({{ printf "%q" .Name }})))
if err != nil {
return
}
res.{{ .Name }} = aux.GetValue()
}
{{- else }}
// Could not find expression-type counterpart for {{ .Type }}
{{- end }}
{{- end }}
{{- end }}
return
}
{{ end }}
+19
View File
@@ -0,0 +1,19 @@
package {{ .Package }}
import (
"github.com/cortezaproject/corteza-server/pkg/eventbus"
)
var _=eventbus.ConstraintMaker
// Match returns false if given conditions do not match event & resource internals
func (res {{ camelCase .ResourceIdent "base" }}) Match(c eventbus.ConstraintMatcher) bool {
// By default we match no mather what kind of constraints we receive
//
// Function will be called multiple times - once for every trigger constraint
// All should match (return true):
// constraint#1 AND constraint#2 AND constraint#3 ...
//
// When there are multiple values, Match() can decide how to treat them (OR, AND...)
return true
}
@@ -0,0 +1,45 @@
package rest
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
func getEventTypeDefinitions() []eventTypeDef {
return []eventTypeDef{
{{ range .Definitions }}
{{ range $r := .Resources }}
{{ template "eventTypeDefinitions" dict "res" $r "type" "on" "types" .On }}
{{ template "eventTypeDefinitions" dict "res" $r "type" "before" "types" .BeforeAfter }}
{{ template "eventTypeDefinitions" dict "res" $r "type" "after" "types" .BeforeAfter }}
{{ end }}
{{ end }}
}
}
{{ define "eventTypeDefinitions" }}
{{ range $ev := $.types }}
{
ResourceType: {{ printf "%q" $.res.ResourceString }},
EventType: {{ printf "%q" (camelCase $.type $ev) }},
Properties: []eventTypePropertyDef{
{{ range $.res.Properties }}
{{ if not .Internal }}
{
Name: {{ printf "%q" .Name }},
Type: {{ printf "%q" .ExprType }},
Immutable: {{ printf "%v" .Immutable }},
},
{{ end }}
{{ end }}
},
Constraints: []eventTypeConstraintDef{
{{ range $.res.Constraints }}
{
Name: {{ printf "%q" .Name }},
},
{{ end }}
},
},
{{ end }}
{{ end }}
@@ -0,0 +1,32 @@
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
{{- range .Definitions }}
// - {{ .Source }}
{{- end }}
[cols="2m,3a"]
|===
| Type | Structure
{{- range .Definitions }}
{{- range $tName, $tDef := .Types }}
{{- if gt (len $tDef.Struct) 0}}
| [#objref-{{ toLower $tName }}]#<<objref-{{ toLower $tName }},{{ $tName }}>>#
|
{{- if $tDef.Struct }}
[source]
----
{
{{- range $s := .Struct }}
{{ $s.Name }} ({{ $s.ExprType }})
{{- end }}
}
----
{{ end }}
{{- end }}
{{- end }}
{{ end }}
|===
@@ -0,0 +1,218 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"context"
"fmt"
"sync"
{{- range .Imports }}
{{ normalizeImport . }}
{{- end }}
{{- if ne .Package "expr" }}
. "github.com/cortezaproject/corteza-server/pkg/expr"
{{- end }}
)
var _ = context.Background
var _ = fmt.Errorf
{{ range $exprType, $def := .Types }}
{{ if not $def.CustomType }}
// {{ $exprType }} is an expression type, wrapper for {{ $def.As }} type
type {{ $exprType }} struct{
value {{ $def.As }}
mux sync.RWMutex
}
// New{{ $exprType }} creates new instance of {{ $exprType }} expression type
func New{{ $exprType }}(val interface{}) (*{{ $exprType }}, error) {
if c, err := {{ export "CastTo" $exprType }}(val); err != nil {
return nil, fmt.Errorf("unable to create {{ $exprType }}: %w", err)
} else {
return &{{ $exprType }}{value: c}, nil
}
}
// Get return underlying value on {{ $exprType }}
func (t *{{ $exprType }}) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on {{ $exprType }}
func (t *{{ $exprType }}) GetValue() {{ $def.As }} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func ({{ $exprType }}) Type() string { return "{{ $exprType }}" }
// Cast converts value to {{ $def.As }}
func ({{ $exprType }}) Cast(val interface{}) (TypedValue, error) {
return New{{ $exprType }}(val)
}
// Assign new value to {{ $exprType }}
//
// value is first passed through {{ export "CastTo" $exprType }}
func (t *{{ $exprType }}) Assign(val interface{}) (error) {
if c, err := {{ export "CastTo" $exprType }}(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
{{ if $def.Comparable }}
{{ if not $def.CustomComparator }}
// Compare the two {{ $exprType }} values
func (t {{ $exprType }}) Compare(to TypedValue) (int, error) {
c, err := New{{ $exprType }}(to)
if err != nil {
return 0, fmt.Errorf("cannot compare %s and %s: %s", t.Type(), c.Type(), err.Error())
}
switch {
case t.value == c.value:
return 0, nil
case t.value < c.value:
return -1, nil
case t.value > c.value:
return 1, nil
default:
return 0, fmt.Errorf("cannot compare %s and %s: unknown state", t.Type(), c.Type())
}
}
{{ else }}
// Compare the two {{ $exprType }} values
func (t {{ $exprType }}) Compare(to TypedValue) (int, error) {
return compareTo{{ $exprType }}(t, to)
}
{{ end }}
{{ end }}
{{ if $def.Struct }}
{{ if not $def.CustomFieldAssigner }}
func (t *{{ $exprType }}) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return {{ $def.AssignerFn }}(t.value, key, val)
}
{{ end }}
{{ if not $def.CustomGValSelector }}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access {{ $exprType }}'s underlying value ({{ $def.As }})
// and it's fields
//
func (t *{{ $exprType }}) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return {{ unexport $exprType "GValSelector" }}(t.value, k)
}
{{ end }}
{{ if not $def.CustomSelector }}
// Select is field accessor for {{ $def.As }}
//
// Similar to SelectGVal but returns typed values
func (t *{{ $exprType }}) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return {{ unexport $exprType "TypedValueSelector" }}(t.value, k)
}
{{ end }}
func (t *{{ $exprType }}) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
{{- range $def.Struct }}
{{- if .ExprType }}
case {{ printf "%q" .Name }}{{ if .Alias }}, {{ printf "%q" .Alias }}{{ end }}:
return true
{{- end }}
{{- end }}
}
return false
}
// {{ unexport $exprType "GValSelector" }} is field accessor for {{ $def.As }}
func {{ unexport $exprType "GValSelector" }}(res {{ $def.As }}, k string) (interface{}, error) {
{{- if hasPtr $def.As }}
if res == nil {
return nil, nil
}
{{- end }}
switch k {
{{- range $def.Struct }}
{{- if .ExprType }}
case {{ printf "%q" .Name }}{{ if .Alias }}, {{ printf "%q" .Alias }}{{ end }}:
return res.{{ export .Name }}, nil
{{- end }}
{{- end }}
}
return nil, fmt.Errorf("unknown field '%s'", k)}
// {{ unexport $exprType "TypedValueSelector" }} is field accessor for {{ $def.As }}
func {{ unexport $exprType "TypedValueSelector" }}(res {{ $def.As }}, k string) (TypedValue, error) {
{{- if hasPtr $def.As }}
if res == nil {
return nil, nil
}
{{- end }}
switch k {
{{- range $def.Struct }}
{{- if .ExprType }}
case {{ printf "%q" .Name }}{{ if .Alias }}, {{ printf "%q" .Alias }}{{ end }}:
return {{ export "New" .ExprType }}(res.{{ export .Name }})
{{- end }}
{{- end }}
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
{{ if $def.BuiltInAssignerFn }}
// {{ $def.AssignerFn }} is field value setter for {{ $def.As }}
func {{ $def.AssignerFn }}(res {{ $def.As }}, k string, val interface{}) (error) {
switch k {
{{- range $def.Struct }}
case {{ printf "%q" .Name }}{{ if .Alias }}, {{ printf "%q" .Alias }}{{ end }}:
{{- if .Readonly }}
return fmt.Errorf("field '%s' is read-only", k)
{{- else }}
aux, err := {{ export "CastTo" .ExprType }}(val)
if err != nil {
return err
}
res.{{ export .Name }} = aux
return nil
{{- end }}
{{- end }}
}
return fmt.Errorf("unknown field '%s'", k)
}
{{ end }}
{{ end }} {{/* if $def.Struct */}}
{{ end }} {{/* if not $def.CustomType */}}
{{ end }} {{/* types loop */}}
@@ -0,0 +1,66 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/cortezaproject/corteza-server/{{ .App }}/rest/request"
"github.com/cortezaproject/corteza-server/pkg/api"
)
type (
// Internal API interface
{{ export $.Endpoint.Entrypoint }}API interface {
{{- range $a := $.Endpoint.Apis }}
{{ export $a.Name }}(context.Context, *request.{{ export $.Endpoint.Entrypoint $a.Name }}) (interface{}, error)
{{- end }}
}
// HTTP API interface
{{ export .Endpoint.Entrypoint }} struct {
{{- range $a := .Endpoint.Apis }}
{{ export $a.Name }} func(http.ResponseWriter, *http.Request)
{{- end }}
}
)
func {{ export "New" $.Endpoint.Entrypoint }}(h {{ export $.Endpoint.Entrypoint }}API) *{{ export $.Endpoint.Entrypoint }} {
return &{{ export $.Endpoint.Entrypoint }}{
{{- range $a := .Endpoint.Apis }}
{{ export $a.Name }}: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.New{{ export $.Endpoint.Entrypoint $a.Name }}()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.{{ export $a.Name }}(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
{{- end }}
}
}
func (h {{ export $.Endpoint.Entrypoint }}) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
{{- range $a := .Endpoint.Apis }}
r.{{ export ( toLower $a.Method ) }}("{{ $.Endpoint.Path }}{{ $a.Path }}", h.{{ export $a.Name }})
{{- end }}
})
}
@@ -0,0 +1,222 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"encoding/json"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/go-chi/chi/v5"
"io"
"fmt"
"mime/multipart"
"net/http"
"strings"
{{- range .Imports }}
{{ normalizeImport . }}
{{- end }}
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
{{- range $a := $.Endpoint.Apis }}
{{ export $.Endpoint.Entrypoint $a.Name }} struct {
{{- range $p := $a.Params.All }}
// {{ export $p.Name }} {{ $p.Origin }} parameter
//
// {{ $p.Title }}
{{ export $p.Name }} {{ $p.Type }} {{ $p.FieldTag }}
{{ end }}
}
{{ end }}
)
{{- range $a := $.Endpoint.Apis }}
// {{ export "New" $.Endpoint.Entrypoint $a.Name }} request
func {{ export "New" $.Endpoint.Entrypoint $a.Name }}() *{{ export $.Endpoint.Entrypoint $a.Name }} {
return &{{ export $.Endpoint.Entrypoint $a.Name }}{}
}
// Auditable returns all auditable/loggable parameters
func (r {{ export $.Endpoint.Entrypoint $a.Name }}) Auditable() map[string]interface{} {
return map[string]interface{}{
{{- range $p := $a.Params.All }}
{{- if not $p.Sensitive }}
"{{ $p.Name }}": r.{{ export $p.Name }},
{{- end }}
{{- end }}
}
}
{{- range $p := $a.Params.All }}
// Auditable returns all auditable/loggable parameters
func (r {{ export $.Endpoint.Entrypoint $a.Name }}) Get{{ export $p.Name }}() {{ $p.Type }} {
return r.{{ export $p.Name }}
}
{{- end }}
// Fill processes request and fills internal variables
func (r *{{ export $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (err error) {
{{ if $a.Params.Post }}
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{{- end }}
{{ if $a.Params.Get }}
{
// GET params
tmp := req.URL.Query()
{{ range $p := $a.Params.Get }}
{{- if or $p.IsSlice $p.HasExplicitParser }}
if val, ok := tmp["{{ $p.Name }}[]"]; ok {
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
} else if val, ok := tmp["{{ $p.Name }}"]; ok {
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
}
{{- else }}
if val, ok := tmp["{{ $p.Name }}"]; ok && len(val) > 0 {
r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }}
if err != nil {
return err
}
}
{{- end }}
{{- end }}
}
{{- end }}
{{ if $a.Params.Post }}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
{{ range $p := $a.Params.Post }}
{{ if $p.IsUpload }}
// Ignoring {{ $p.Name }} as its handled in the POST params section
{{- else }}
{{- if or $p.HasExplicitParser }}
if val, ok := req.MultipartForm.Value["{{ $p.Name }}[]"]; ok {
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
} else if val, ok := req.MultipartForm.Value["{{ $p.Name }}"]; ok {
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
}
{{- else if not $p.IsSlice }}
if val, ok := req.MultipartForm.Value["{{ $p.Name }}"]; ok && len(val) > 0 {
r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }}
if err != nil {
return err
}
}
{{- end }}
{{- end }}
{{- end }}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
{{ range $p := $a.Params.Post }}
{{ if $p.IsUpload }}
if _, r.{{ export $p.Name }}, err = req.FormFile("{{ $p.Name }}"); err != nil {
return fmt.Errorf("error processing uploaded file: %w", err)
}
{{ else }}
{{- if or $p.HasExplicitParser }}
if val, ok := req.Form["{{ $p.Name }}[]"]; ok {
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
} else if val, ok := req.Form["{{ $p.Name }}"]; ok {
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
}
{{- else if or $p.IsSlice }}
//if val, ok := req.Form["{{ $p.Name }}[]"]; ok && len(val) > 0 {
// r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
// if err != nil {
// return err
// }
//}
{{- else }}
if val, ok := req.Form["{{ $p.Name }}"]; ok && len(val) > 0 {
r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }}
if err != nil {
return err
}
}
{{- end }}
{{- end }}
{{- end }}
}
{{ end }}
{{ if $a.Params.Path }}
{
var val string
// path params
{{ range $p := $a.Params.Path }}
val = chi.URLParam(req, "{{ $p.Name }}")
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
{{ end }}
}
{{ end }}
return err
}
{{- end }}
@@ -0,0 +1,38 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
{{ range $name, $set := .Types }}
{{ if $set.LabelResourceType }}
// SetLabel adds new label to label map
func (m *{{ $name }}) SetLabel(key string, value string) {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
m.Labels[key] = value
}
// GetLabels adds new label to label map
func (m {{ $name }}) GetLabels() map[string]string {
return m.Labels
}
// GetLabels adds new label to label map
func ({{ $name }}) LabelResourceKind() string {
return {{ printf "%q" $set.LabelResourceType }}
}
// GetLabels adds new label to label map
func (m {{ $name }}) LabelResourceID() uint64 {
return m.ID
}
{{ end }}
{{ end }}
@@ -0,0 +1,89 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
{{ if .Imports }}
import (
{{ range $i, $import := .Imports }}
"{{ $import }}"
{{ end }}
)
{{ end }}
type (
{{ range $name, $set := .Types }}
// {{ $name }}Set slice of {{ $name }}
//
// This type is auto-generated.
{{ $name }}Set []*{{ $name }}
{{ end }}
)
{{ range $name, $set := .Types }}
// Walk iterates through every slice item and calls w({{ $name }}) err
//
// This function is auto-generated.
func (set {{ $name }}Set) Walk(w func(*{{ $name }}) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f({{ $name }}) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set {{ $name }}Set) Filter(f func(*{{ $name }}) (bool, error)) (out {{ $name }}Set, err error) {
var ok bool
out = {{ $name }}Set{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
} else if ok {
out = append(out, set[i])
}
}
return
}
{{ if not $set.NoIdField }}
// FindByID finds items from slice by its ID property
//
// This function is auto-generated.
func (set {{ $name }}Set) FindByID(ID uint64) *{{ $name }} {
for i := range set {
if set[i].ID == ID {
return set[i]
}
}
return nil
}
// IDs returns a slice of uint64s from all items in the set
//
// This function is auto-generated.
func (set {{ $name }}Set) IDs() (IDs []uint64) {
IDs = make([]uint64, len(set))
for i := range set {
IDs[i] = set[i].ID
}
return
}
{{ end }}
{{ end }}
@@ -0,0 +1,116 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"fmt"
"github.com/stretchr/testify/require"
"testing"
{{ range $i, $import := .Imports }}
"{{ $import }}"
{{ end }}
)
{{ range $name, $set := .Types }}
func Test{{ $name }}SetWalk(t *testing.T) {
var (
value = make({{ $name }}Set, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*{{ $name }}) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*{{ $name }}) error { return fmt.Errorf("walk error") }))
}
func Test{{ $name }}SetFilter(t *testing.T) {
var (
value = make({{ $name }}Set, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*{{ $name }}) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*{{ $name }}) (bool, error) {
if !found {
found = true
return found, nil
}
return false, nil
})
req.NoError(err)
req.Len(set, 1)
}
// filter error
{
_, err := value.Filter(func(*{{ $name }}) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
{{ if not $set.NoIdField }}
func Test{{ $name }}SetIDs(t *testing.T) {
var (
value = make({{ $name }}Set, 3)
req = require.New(t)
)
// construct objects
value[0] = new({{ $name }})
value[1] = new({{ $name }})
value[2] = new({{ $name }})
// set ids
value[0].ID = 1
value[1].ID = 2
value[2].ID = 3
// Find existing
{
val := value.FindByID(2)
req.Equal(uint64(2), val.ID)
}
// Find non-existing
{
val := value.FindByID(4)
req.Nil(val)
}
// List IDs from set
{
val := value.IDs()
req.Equal(len(val), len(value))
}
}
{{ end }}
{{ end }}
+304
View File
@@ -0,0 +1,304 @@
package codegen
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/fsnotify/fsnotify"
)
func Proc() {
const (
docGenBase = "/generated/partials"
)
var (
err error
watchChanges bool
beVerbose bool
docPath string
genCode = true
genDocs = false
fileList []string
watcher *fsnotify.Watcher
templatesPath = filepath.Join("pkg", "codegen", "assets", "*.tpl")
templatesSrc []string
actionSrcPath = filepath.Join("*", "service", "*_actions.yaml")
actionSrc []string
actionDefs []*actionsDef
eventSrcPath = filepath.Join("*", "service", "event", "events.yaml")
eventSrc []string
eventDefs []*eventsDef
// workaround because
// filepath.Join merges "*","*" into "**" instead of "*/*"
pkgTypeSrcPath = filepath.Join("*"+string(filepath.Separator)+"*"+string(filepath.Separator)+"*", "types.yaml")
typeSrcPath = filepath.Join("*"+string(filepath.Separator)+"*", "types.yaml")
typeSrc []string
typeDefs []*typesDef
// workaround because
// filepath.Join merges "*","*" into "**" instead of "*/*"
exprTypeSrcPath = filepath.Join("*"+string(filepath.Separator)+"*", "expr_types.yaml")
exprTypeSrc []string
exprTypeDefs []*exprTypesDef
restSrcPath = filepath.Join("*", "rest.yaml")
restSrc []string
restDefs []*restDef
aFuncsSrcPath = filepath.Join("*", "automation", "*_handler.yaml")
aFuncsSrc []string
aFuncsDefs []*aFuncDefs
tpls *template.Template
tplBase = template.New("").
Funcs(map[string]interface{}{
"camelCase": camelCase,
"kebabCase": kebabCase,
"export": export,
"unexport": unexport,
"removePtr": removePtr,
"hasPtr": hasPtr,
"toggleExport": toggleExport,
"toLower": strings.ToLower,
"toUpper": strings.ToUpper,
"cc2underscore": cc2underscore,
"normalizeImport": normalizeImport,
"comment": func(text string, skip1st bool) string {
ll := strings.Split(text, "\n")
s := 0
out := ""
if skip1st {
s = 1
out = ll[0] + "\n"
}
for ; s < len(ll); s++ {
out += "// " + ll[s] + "\n"
}
return out
},
}).
Funcs(sprig.TxtFuncMap())
output = func(format string, aa ...interface{}) {
if beVerbose {
fmt.Fprintf(os.Stdout, format, aa...)
}
}
outputErr = func(err error, format string, aa ...interface{}) bool {
if err != nil {
fmt.Fprintf(os.Stdout, format, aa...)
fmt.Fprintf(os.Stdout, "%v\n", err)
return true
}
return false
}
)
flag.BoolVar(&watchChanges, "w", false, "regenerate on change of template or definition files")
flag.BoolVar(&beVerbose, "v", false, "output loaded definitions, templates and outputs")
flag.StringVar(&docPath, "d", "", "generate docs on template or definition change")
flag.Parse()
defer func() {
if watcher != nil {
watcher.Close()
}
}()
if len(docPath) > 0 {
docPath = strings.TrimRight(docPath, "/") + "/src/modules"
if i, err := os.Stat(docPath); err != nil {
handleError(err)
} else if !i.IsDir() {
handleError(fmt.Errorf("expecting directory: %q", docPath))
}
genDocs = true
}
for {
fileList = make([]string, 0, 100)
templatesSrc = glob(templatesPath)
output("loaded %d templates from %s\n", len(templatesSrc), templatesPath)
actionSrc = glob(actionSrcPath)
output("loaded %d action definitions from %s\n", len(actionSrc), actionSrcPath)
eventSrc = glob(eventSrcPath)
output("loaded %d event definitions from %s\n", len(eventSrc), eventSrcPath)
typeSrc = glob(typeSrcPath)
typeSrc = append(typeSrc, glob(pkgTypeSrcPath)...)
output("loaded %d type definitions from %s\n", len(typeSrc), typeSrcPath)
exprTypeSrc = glob(exprTypeSrcPath)
output("loaded %d exprType definitions from %s\n", len(exprTypeSrc), exprTypeSrcPath)
restSrc = glob(restSrcPath)
output("loaded %d rest definitions from %s\n", len(restSrc), restSrcPath)
aFuncsSrc = glob(aFuncsSrcPath)
output("loaded %d function definitions from %s\n", len(aFuncsSrc), aFuncsSrcPath)
if watchChanges {
if watcher != nil {
watcher.Close()
}
watcher, err = fsnotify.NewWatcher()
handleError(err)
fileList = append(fileList, templatesSrc...)
fileList = append(fileList, actionSrc...)
fileList = append(fileList, eventSrc...)
fileList = append(fileList, typeSrc...)
fileList = append(fileList, exprTypeSrc...)
fileList = append(fileList, restSrc...)
fileList = append(fileList, aFuncsSrc...)
for _, d := range fileList {
handleError(watcher.Add(d))
}
}
func() {
tpls, err = tplBase.ParseFiles(templatesSrc...)
if outputErr(err, "could not parse templates:\n") {
return
}
if actionDefs, err = procActions(actionSrc...); err == nil {
if genCode {
err = genActions(tpls, actionDefs...)
}
}
if outputErr(err, "failed to process actions:\n") {
return
}
if exprTypeDefs, err = procExprTypes(exprTypeSrc...); err == nil {
if genCode {
err = genExprTypes(tpls, exprTypeDefs...)
}
if genDocs && err == nil {
err = genExprTypeDocs(tpls, docPath+docGenBase, exprTypeDefs...)
}
}
if outputErr(err, "failed to process expr types:\n") {
return
}
if eventDefs, err = procEvents(eventSrc...); err == nil {
if genCode {
expandEventTypes(eventDefs, exprTypeDefs)
err = genEvents(tpls, eventDefs...)
}
if genDocs && err == nil {
err = genEventsDocs(tpls, docPath+docGenBase, eventDefs...)
}
}
if outputErr(err, "failed to process events:\n") {
return
}
if typeDefs, err = procTypes(typeSrc...); err == nil {
if genCode {
err = genTypes(tpls, typeDefs...)
}
}
if outputErr(err, "failed to process types:\n") {
return
}
if restDefs, err = procRest(restSrc...); err == nil {
if genCode {
err = genRest(tpls, restDefs...)
}
}
if outputErr(err, "failed to process rest:\n") {
return
}
if outputErr(err, "failed to process store:\n") {
return
}
if outputErr(err, "fail to process options:\n") {
return
}
if aFuncsDefs, err = procAutomationFunctions(aFuncsSrc...); err == nil {
if genCode {
expandAutomationFunctionTypes(aFuncsDefs, exprTypeDefs)
err = genAutomationFunctions(tpls, aFuncsDefs...)
}
if genDocs && err == nil {
err = genAutomationFunctionDocs(tpls, docPath+docGenBase, aFuncsDefs...)
}
}
if outputErr(err, "failed to process automation functions:\n") {
return
}
}()
if !watchChanges {
break
}
// @todo fix this (without causing too many "too-many-files" issues :)
output("waiting for changes (if you add a new file, restart codegen manually)\n")
select {
case <-watcher.Events:
case err = <-watcher.Errors:
handleError(err)
}
}
}
func glob(path string) []string {
src, err := filepath.Glob(path)
if err != nil {
handleError(fmt.Errorf("failed to glob %q: %w", path, err))
}
return src
}
// Similar to cli.HandleError but without cli pkg dependencies
//
// pkg/cli deps can give us issues when generating go files with invalid code
func handleError(err error) {
if err == nil {
return
}
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
+298
View File
@@ -0,0 +1,298 @@
package codegen
import (
"fmt"
"os"
"path"
"strings"
"text/template"
"github.com/cortezaproject/corteza-server/pkg/slice"
"gopkg.in/yaml.v3"
)
type (
// definitions are in one file
eventsDef struct {
Package string
App string
Source string
outputDir string
// List of imports
// Used only by generated file and not pre-generated-user-file
Imports []string
Resources evResourceDefMap
}
evResourceDefMap map[string]*evResourceDef
evResourceDef struct {
// used as string
ResourceString string
// used as (go) ident
ResourceIdent string
// used for filename
ResourceFile string
On []string `yaml:"on"`
BeforeAfter []string `yaml:"ba"`
Properties []*eventProps `yaml:"props"`
Constraints []*eventConstraint `yaml:"constraints"`
Result string `yaml:"result"`
}
eventProps struct {
Name string
Type string
ExprType string
// Import path for prop type, use package's type by default (see importTypePathTpl)
Import string
// Set property internally only, not via constructor
Internal bool
// Do not allow change of the variable through
Immutable bool
}
eventConstraint struct {
Name string
}
)
func procEvents(mm ...string) (dd []*eventsDef, err error) {
// <app>/service/event/events.yaml
const (
importTypePathTpl = "github.com/cortezaproject/corteza-server/%s/types"
importAutoPathTpl = "github.com/cortezaproject/corteza-server/%s/automation"
importAuthPath = "github.com/cortezaproject/corteza-server/pkg/auth"
)
dd = make([]*eventsDef, 0)
for _, m := range mm {
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
var (
e = evResourceDefMap{}
d = &eventsDef{
Package: "event",
Source: m,
App: m[:strings.Index(m, "/")],
outputDir: path.Dir(m),
Resources: map[string]*evResourceDef{},
}
)
if err := yaml.NewDecoder(f).Decode(e); err != nil {
return nil, err
}
for resName, evDef := range e {
d.Imports = []string{
fmt.Sprintf(importTypePathTpl, d.App),
}
if d.App != "messaging" {
d.Imports = append(d.Imports, fmt.Sprintf(importAutoPathTpl, d.App))
}
evDef.ResourceString = resName
if l := strings.Index(resName, ":"); l > 0 {
evDef.ResourceIdent = resName[l+1:]
} else {
evDef.ResourceIdent = resName
}
// make filename
evDef.ResourceFile = strings.ReplaceAll(evDef.ResourceIdent, ":", "_")
evDef.ResourceFile = strings.ReplaceAll(evDef.ResourceFile, "-", "_")
// make identifier (string that will be used for struct name)
evDef.ResourceIdent = camelCase(strings.Split(evDef.ResourceFile, "_")...)
// Prepare the data
// no default ("result") result set, use first one from properties
if evDef.Result == "" && len(evDef.Properties) > 0 {
evDef.Result = evDef.Properties[0].Name
}
// Invoker - user that invoked (triggered) the event
evDef.Properties = append(evDef.Properties, &eventProps{
Name: "invoker",
Type: "auth.Identifiable",
Import: importAuthPath,
Immutable: false,
Internal: true,
})
// Ensure all imports are checked
for _, p := range evDef.Properties {
if p.Import == "" {
if strings.HasPrefix(p.Type, "*types.") || strings.HasPrefix(p.Type, "types.") {
p.Import = fmt.Sprintf(importTypePathTpl, d.App)
}
}
if p.Import != "" && !slice.HasString(d.Imports, p.Import) {
d.Imports = append(d.Imports, p.Import)
}
p.Import = ""
}
d.Resources[resName] = evDef
}
dd = append(dd, d)
}
return dd, nil
}
func expandEventTypes(ee []*eventsDef, tt []*exprTypesDef) {
// index of all known types
expTypes := make(map[string]*exprTypeDef)
goTypes := make(map[string]string)
for _, t := range tt {
for typ, d := range t.Types {
expTypes[typ] = d
goTypes[d.As] = typ
}
}
for _, e := range ee {
for _, r := range e.Resources {
for _, p := range r.Properties {
if p.ExprType != "" && expTypes[p.ExprType] == nil {
fmt.Printf("unknown type %q used for param %q for events on resource %s\n", p.ExprType, p.Name, r.ResourceString)
}
if p.ExprType == "" && goTypes[p.Type] != "" {
p.ExprType = goTypes[p.Type]
}
}
}
}
}
func genEvents(tpl *template.Template, dd ...*eventsDef) (err error) {
var (
// Will only be generated if file does not exist previously
tplEvents = tpl.Lookup("events.go.tpl")
// Always regenerated
tplEventsGen = tpl.Lookup("events.gen.go.tpl")
// List of event-type definitions for automation REST endpoint
tplAutomationRestDefGen = tpl.Lookup("events_rest_def.gen.go.tpl")
dst string
)
for _, d := range dd {
// Generic code, all events go into one file (per app)
err = goTemplate(path.Join(d.outputDir, "events.gen.go"), tplEventsGen, d)
if err != nil {
return
}
for _, r := range d.Resources {
dst = path.Join(d.outputDir, r.ResourceFile+".go")
_, err = os.Stat(dst)
if os.IsNotExist(err) {
err = goTemplate(dst, tplEvents, map[string]interface{}{
"Package": d.Package,
"ResourceIdent": r.ResourceIdent,
})
}
if err != nil {
return
}
}
}
// Remove messaging
var msgIndex = -1
for i := range dd {
if dd[i].App == "messaging" {
msgIndex = i
}
}
if msgIndex > -1 {
dd = append(dd[:msgIndex], dd[msgIndex+1:]...)
}
err = goTemplate(
path.Join("automation", "rest", "eventTypes.gen.go"),
tplAutomationRestDefGen,
map[string]interface{}{
"Definitions": dd,
"Imports": collectEventDefImports("", dd...),
})
if err != nil {
return
}
return nil
}
// Merge on/before/after events
func (def evResourceDef) Events() []string {
return append(
makeEventGroup("on", def.On),
append(
makeEventGroup("before", def.BeforeAfter),
makeEventGroup("after", def.BeforeAfter)...,
)...,
)
}
func makeEventGroup(pfix string, ee []string) (out []string) {
for _, e := range ee {
out = append(out, pfix+strings.ToUpper(e[:1])+e[1:])
}
return
}
func genEventsDocs(tpl *template.Template, docsPath string, dd ...*eventsDef) (err error) {
var (
tplEventsAdoc = tpl.Lookup("events.gen.adoc.tpl")
dst string
)
dst = path.Join(docsPath, "resource-events.gen.adoc")
return plainTemplate(dst, tplEventsAdoc, map[string]interface{}{
"Definitions": dd,
})
}
func collectEventDefImports(basePkg string, dd ...*eventsDef) []string {
ii := make([]string, 0, len(dd))
for _, d := range dd {
for _, i := range d.Imports {
if !slice.HasString(ii, i) && (basePkg == "" || !strings.HasSuffix(i, basePkg)) {
ii = append(ii, i)
}
}
}
return ii
}
+136
View File
@@ -0,0 +1,136 @@
package codegen
import (
"fmt"
"os"
"path"
"text/template"
"gopkg.in/yaml.v3"
)
type (
exprTypesDef struct {
// source file path
Source string
// outputDir
// dir where the source file is
outputDir string
Imports []string
Package string `yaml:"package"`
Types map[string]*exprTypeDef `yaml:"types"`
}
exprTypeDef struct {
As string
RawDefault string `yaml:"default"`
AssignerFn string `yaml:"assignerFn"`
BuiltInCastFn bool
BuiltInAssignerFn bool
CustomType bool `yaml:"customType"`
CustomGValSelector bool `yaml:"customGValSelector"`
CustomSelector bool `yaml:"customSelector"`
CustomFieldAssigner bool `yaml:"customFieldAssigner"`
Comparable bool `yaml:"comparable"`
CustomComparator bool `yaml:"customComparator"`
Struct []*exprTypeStructDef
// @todo custom setters
// @todo custom getters
}
exprTypeStructDef struct {
Name string
Alias string
ExprType string `yaml:"exprType"`
GoType string `yaml:"goType"`
Mode string
// @todo custom expr-type-constructor NewExprType
}
)
func procExprTypes(mm ...string) (dd []*exprTypesDef, err error) {
dd = make([]*exprTypesDef, 0)
for _, m := range mm {
var (
d = &exprTypesDef{
Source: m,
outputDir: path.Dir(m),
Package: "types",
Types: make(map[string]*exprTypeDef),
}
)
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return nil, fmt.Errorf("%s decode failed: %w", m, err)
}
for tName, tdef := range d.Types {
if tdef.AssignerFn == "" {
tdef.BuiltInAssignerFn = true
tdef.AssignerFn = unexport("assignTo", tName)
}
}
dd = append(dd, d)
}
return dd, nil
}
// Generates all type set files & accompanying tests
//
// generates 2 files per type definition
func genExprTypes(tpl *template.Template, dd ...*exprTypesDef) (err error) {
var (
typeGen = tpl.Lookup("expr_types.gen.go.tpl")
)
for _, d := range dd {
err = goTemplate(path.Join(d.outputDir, "expr_types.gen.go"), typeGen, d)
if err != nil {
return
}
}
return nil
}
// genExprTypeDocs look for expr_types.gen.adoc.tpl and generates expr_types.gen.adoc from it
func genExprTypeDocs(tpl *template.Template, docsPath string, dd ...*exprTypesDef) (err error) {
var (
typeGenAdoc = tpl.Lookup("expr_types.gen.adoc.tpl")
dst string
)
dst = path.Join(docsPath, "expr-types.gen.adoc")
return plainTemplate(dst, typeGenAdoc, map[string]interface{}{
"Definitions": dd,
})
}
func (s exprTypeDef) Default() string {
if s.RawDefault == "" {
return "nil"
}
return s.RawDefault
}
func (s exprTypeStructDef) Readonly() bool {
return s.Mode == "ro"
}
+229
View File
@@ -0,0 +1,229 @@
package codegen
import (
"fmt"
"os"
"path"
"strings"
"text/template"
"gopkg.in/yaml.v3"
)
type (
// The following structure represents
// legacy API definition (spec.json)
//
//
// definitions are in one file
restDef struct {
App string
Source string
outputDir string
Endpoints []*restEndpointDef
}
restEndpointDef struct {
Title string `yaml:"title"`
Path string `yaml:"path"`
Entrypoint string `yaml:"entrypoint"`
Authentication []interface{} `yaml:"authentication,omitempty"`
Apis []*restEndpointApi `yaml:"apis"`
Imports []string `yaml:"imports"`
Description string `yaml:"description,omitempty"`
Params restEndpointParamsDef `yaml:"parameters,omitempty"`
}
restEndpointApi struct {
Name string `yaml:"name"`
Method string `yaml:"method"`
Title string `yaml:"title"`
Path string `yaml:"path"`
Params restEndpointParamsDef `yaml:"parameters,omitempty"`
}
restEndpointParamsDef struct {
Post []*restEndpointParamDef `yaml:"post"`
Path []*restEndpointParamDef `yaml:"path"`
Get []*restEndpointParamDef `yaml:"get"`
}
restEndpointParamDef struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Required bool `yaml:"required"`
Title string `yaml:"title"`
Origin string
Sensitive bool `yaml:"sensitive"`
DefinedParser string `yaml:"parser"`
}
)
func procRest(mm ...string) (dd []*restDef, err error) {
dd = make([]*restDef, 0)
for _, m := range mm {
err = func() error {
f, err := os.Open(m)
if err != nil {
return fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
var d = &restDef{}
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return err
}
d.outputDir = path.Dir(m)
// Append params from endpoit to all apis
for _, e := range d.Endpoints {
for _, a := range e.Apis {
a.Params.Path = append(e.Params.Path, a.Params.Path...)
a.Params.Post = append(e.Params.Post, a.Params.Post...)
a.Params.Get = append(e.Params.Get, a.Params.Get...)
}
}
dd = append(dd, d)
return nil
}()
if err != nil {
return nil, fmt.Errorf("failed to process %s: %w", m, err)
}
}
return dd, nil
}
func genRest(tpl *template.Template, dd ...*restDef) (err error) {
var (
// Will only be generated if file does not exist previously
tplHandler = tpl.Lookup("rest_handler.go.tpl")
tplRequest = tpl.Lookup("rest_request.go.tpl")
dst string
)
for _, d := range dd {
for _, e := range d.Endpoints {
// Generic code, every event goes into one file (per app)
dst = path.Join(d.outputDir, "rest", "handlers", e.Entrypoint+".go")
err = goTemplate(dst, tplHandler, map[string]interface{}{
"Source": d.Source,
"Endpoint": e,
"App": path.Base(d.outputDir),
})
if err != nil {
return
}
// Generic code, every event goes into one file (per app)
dst = path.Join(d.outputDir, "rest", "request", e.Entrypoint+".go")
err = goTemplate(dst, tplRequest, map[string]interface{}{
"Source": d.Source,
"Endpoint": e,
"Imports": e.Imports,
})
if err != nil {
return
}
}
}
return nil
}
func (d *restEndpointParamsDef) All() []*restEndpointParamDef {
var pp = make([]*restEndpointParamDef, 0)
for _, p := range d.Path {
p.Origin = "PATH"
pp = append(pp, p)
}
for _, p := range d.Get {
p.Origin = "GET"
pp = append(pp, p)
}
for _, p := range d.Post {
p.Origin = "POST"
pp = append(pp, p)
}
return pp
}
func (d *restEndpointParamDef) IsUpload() bool {
switch d.Type {
case "*multipart.FileHeader":
return true
}
return false
}
func (d *restEndpointParamDef) IsSlice() bool {
return strings.HasPrefix(d.Type, "[]") || strings.HasSuffix(d.Type, "Set")
}
func (d *restEndpointParamDef) IsString() bool {
switch d.Type {
case "string", "[]string", "[]*string":
return true
}
return false
}
func (d *restEndpointParamDef) FieldTag() string {
switch d.Type {
case "uint64":
return "`json:\",string\"`"
}
return ""
}
func (d *restEndpointParamDef) HasExplicitParser() bool {
return d.DefinedParser != ""
}
func (d *restEndpointParamDef) Parser(arg string) string {
if d.HasExplicitParser() {
return fmt.Sprintf("%s(%s)", d.DefinedParser, arg)
}
switch d.Type {
case "[]uint64":
return fmt.Sprintf("payload.ParseUint64s(%s), nil", arg)
case "[]uint":
return fmt.Sprintf("payload.ParseUints(%s), nil", arg)
case "time.Time":
return fmt.Sprintf("payload.ParseISODateWithErr(%s)", arg)
case "*time.Time":
return fmt.Sprintf("payload.ParseISODatePtrWithErr(%s)", arg)
case "sqlxTypes.JSONText":
return fmt.Sprintf("payload.ParseJSONTextWithErr(%s)", arg)
case "int", "uint", "uint64", "int64", "float", "float64", "bool":
return fmt.Sprintf("payload.Parse%s(%s), nil", export(d.Type), arg)
case "string", "[]string":
return fmt.Sprintf("%s, nil", arg)
case "filter.State":
return fmt.Sprintf("payload.ParseFilterState(%s), nil", arg)
default:
return fmt.Sprintf("%s(%s), nil", d.Type, arg)
}
}
+164
View File
@@ -0,0 +1,164 @@
package codegen
import (
"bytes"
"fmt"
"go/format"
"io"
"os"
"regexp"
"strings"
"text/template"
)
func goTemplate(dst string, tpl *template.Template, payload interface{}) (err error) {
var output io.WriteCloser
buf := bytes.Buffer{}
if err := tpl.Execute(&buf, payload); err != nil {
return err
}
fmtsrc, err := format.Source(buf.Bytes())
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s fmt warn: %v\n", dst, err)
err = nil
fmtsrc = buf.Bytes()
}
if dst == "" || dst == "-" {
output = os.Stdout
} else {
if output, err = os.Create(dst); err != nil {
return err
}
defer output.Close()
}
if _, err := output.Write(fmtsrc); err != nil {
return err
}
return nil
}
func plainTemplate(dst string, tpl *template.Template, payload interface{}) (err error) {
var output io.WriteCloser
buf := bytes.Buffer{}
if err := tpl.Execute(&buf, payload); err != nil {
return err
}
if dst == "" || dst == "-" {
output = os.Stdout
} else {
if output, err = os.Create(dst); err != nil {
return err
}
defer output.Close()
}
if _, err := output.Write(buf.Bytes()); err != nil {
return err
}
return nil
}
func camelCase(pp ...string) (out string) {
for i, p := range pp {
if i > 0 && len(p) > 1 {
p = strings.ToUpper(p[:1]) + p[1:]
}
out = out + p
}
return out
}
func kebabCase(pp string) (out string) {
var buf bytes.Buffer
for _, p := range pp {
if 'A' <= p && p <= 'Z' {
// just convert [A-Z] to _[a-z]
if buf.Len() > 0 {
buf.WriteRune('-')
}
buf.WriteRune(p - 'A' + 'a')
} else {
buf.WriteRune(p)
}
}
return buf.String()
}
// PubIdent returns published identifier by uppercasing
// input, cammelcasing it and removing ident unfriendly characters
var nonIdentChars = regexp.MustCompile(`[\s\\/]+`)
func export(pp ...string) (out string) {
for _, p := range pp {
if len(p) > 1 {
p = strings.ToUpper(p[:1]) + p[1:]
} else {
p = strings.ToUpper(p)
}
if ss := nonIdentChars.Split(p, -1); len(ss) > 1 {
p = export(ss...)
}
out = out + p
}
return out
}
func unexport(pp ...string) (out string) {
out = export(pp...)
return strings.ToLower(out[:1]) + out[1:]
}
func removePtr(name string) string {
return strings.TrimLeft(name, "*")
}
func hasPtr(name string) bool {
return len(name) > 0 && name[0:1] == "*"
}
func toggleExport(e bool, pp ...string) (out string) {
if e {
return export(pp...)
}
return unexport(pp...)
}
// convets to underscore
func cc2underscore(cc string) string {
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
u := matchFirstCap.ReplaceAllString(cc, "${1}_${2}")
u = matchAllCap.ReplaceAllString(u, "${1}_${2}")
return strings.ToLower(u)
}
// Handle list of imports, adds quotes around each import
//
// If import string contains a space, assume import alias and
// quotes only the 2nd part
func normalizeImport(i string) string {
if strings.Contains(i, " ") {
p := strings.SplitN(i, " ", 2)
return fmt.Sprintf(`%s "%s"`, p[0], strings.Trim(p[1], `"`))
} else {
return fmt.Sprintf(`"%s"`, strings.Trim(i, `"`))
}
}
+109
View File
@@ -0,0 +1,109 @@
package codegen
import (
"fmt"
"gopkg.in/yaml.v3"
"os"
"path"
"syscall"
"text/template"
)
type (
typesDef struct {
// source file path
Source string
// outputDir
// dir where the source file is
outputDir string
Imports []string
Package string `yaml:"package"`
Types map[string]typeDef `yaml:"types"`
}
typeDef struct {
NoIdField bool `yaml:"noIdField"`
LabelResourceType string `yaml:"labelResourceType"`
}
)
func procTypes(mm ...string) (dd []*typesDef, err error) {
dd = make([]*typesDef, 0)
for _, m := range mm {
var (
d = &typesDef{
Source: m,
outputDir: path.Dir(m),
Package: "types",
Types: map[string]typeDef{},
}
)
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return nil, fmt.Errorf("%s decode failed: %w", m, err)
}
dd = append(dd, d)
}
return dd, nil
}
// Generates all type set files & accompanying tests
//
// generates 2 files per type definition
func genTypes(tpl *template.Template, dd ...*typesDef) (err error) {
var (
typeGen = tpl.Lookup("type_set.gen.go.tpl")
typeGenTest = tpl.Lookup("type_set.gen_test.go.tpl")
typeLabelsGen = tpl.Lookup("type_labels.gen.go.tpl")
)
for _, d := range dd {
err = goTemplate(path.Join(d.outputDir, "type_set.gen.go"), typeGen, d)
if err != nil {
return
}
err = goTemplate(path.Join(d.outputDir, "type_set.gen_test.go"), typeGenTest, d)
if err != nil {
return
}
labelsOutput := path.Join(d.outputDir, "type_labels.gen.go")
if d.HasLabels() {
err = goTemplate(labelsOutput, typeLabelsGen, d)
} else if err = syscall.Unlink(labelsOutput); os.IsNotExist(err) {
err = nil
}
if err != nil {
return
}
}
return nil
}
func (d typesDef) HasLabels() bool {
for _, t := range d.Types {
if len(t.LabelResourceType) > 0 {
return true
}
}
return false
}