Skip to content

Commit

Permalink
chore: Improve generator template organization (#2820)
Browse files Browse the repository at this point in the history
Changes done:
- Templates were moved to files, so that
  - We get proper highlighting from IDE
- We get help with model type and fields or methods inside it + ability
to go to definition and more
(https://blog.jetbrains.com/go/2018/12/14/go-templates-made-easy/)
- Reorganized into smaller chunks, so it should be easier to navigate
(main templates are in the `templates` directory, and helper templates
used inside the main ones are kept inside the `subtemplates` directory).
- In the `templates.go` now all of the templates are loaded by embed,
except the ones that use other templates which have to be initiated in
`func init()`.
- All of the testing was made on the `managed account` resource and it
shouldn't collide with #2816
- Also, there were made small adjustments like 
- Checking for operation name inside unit tests so we won't make invalid
Show test
  - Mapping adjusted to the changes made in #2816
  • Loading branch information
sfc-gh-jcieslak committed Jun 23, 2024
1 parent 05b7eee commit 5035e2f
Show file tree
Hide file tree
Showing 19 changed files with 370 additions and 286 deletions.
6 changes: 3 additions & 3 deletions pkg/sdk/poc/generator/template_executors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func GenerateInterface(writer io.Writer, def *Interface) {
}

func generateOptionsStruct(writer io.Writer, operation *Operation) {
printTo(writer, OptionsTemplate, operation)
printTo(writer, OperationStructTemplate, operation)

for _, f := range operation.HelperStructs {
if !slices.Contains(generatedStructs, f.KindNoPtr()) {
Expand Down Expand Up @@ -84,12 +84,12 @@ func GenerateImplementation(writer io.Writer, def *Interface) {

func GenerateUnitTests(writer io.Writer, def *Interface) {
generatePackageDirective(writer)
printTo(writer, TestFuncTemplate, def)
printTo(writer, UnitTestsTemplate, def)
}

func GenerateValidations(writer io.Writer, def *Interface) {
generatePackageDirective(writer)
printTo(writer, ValidationsImplTemplate, def)
printTo(writer, ValidationsTemplate, def)
}

func GenerateIntegrationTests(writer io.Writer, def *Interface) {
Expand Down
346 changes: 63 additions & 283 deletions pkg/sdk/poc/generator/templates.go
Original file line number Diff line number Diff line change
@@ -1,308 +1,88 @@
package generator

import "text/template"

var PackageTemplate, _ = template.New("packageTemplate").Parse(`
package {{ . }}
`)

var InterfaceTemplate, _ = template.New("interfaceTemplate").
Funcs(template.FuncMap{
"deref": func(p *DescriptionMappingKind) string { return string(*p) },
}).
Parse(`
import "context"
type {{ .Name }} interface {
{{- range .Operations }}
{{- if and (eq .Name "Show") .ShowMapping }}
{{ .Name }}(ctx context.Context, request *{{ .OptsField.DtoDecl }}) ([]{{ .ShowMapping.To.Name }}, error)
{{- else if eq .Name "ShowByID" }}
{{ .Name }}(ctx context.Context, id {{ .ObjectInterface.IdentifierKind }}) (*{{ .ObjectInterface.NameSingular }}, error)
{{- else if and (eq .Name "Describe") .DescribeMapping }}
{{- if .DescribeKind }}
{{- if eq (deref .DescribeKind) "single_value" }}
{{ .Name }}(ctx context.Context, id {{ .ObjectInterface.IdentifierKind }}) (*{{ .DescribeMapping.To.Name }}, error)
{{- else if eq (deref .DescribeKind) "slice" }}
{{ .Name }}(ctx context.Context, id {{ .ObjectInterface.IdentifierKind }}) ([]{{ .DescribeMapping.To.Name }}, error)
{{- end }}
{{- end }}
{{- else }}
{{ .Name }}(ctx context.Context, request *{{ .OptsField.DtoDecl }}) error
{{- end -}}
{{ end }}
}
`)

var OptionsTemplate, _ = template.New("optionsTemplate").Parse(`
// {{ .OptsField.KindNoPtr }} is based on {{ .Doc }}.
type {{ .OptsField.KindNoPtr }} struct {
{{- range .OptsField.Fields }}
{{ .Name }} {{ .Kind }} {{ .TagsPrintable }}
{{- end }}
}
`)

// TODO: merge with template above? (requires moving Doc to field)
var StructTemplate, _ = template.New("structTemplate").Parse(`
type {{ .KindNoPtr }} struct {
{{- range .Fields }}
{{ .Name }} {{ .Kind }} {{ .TagsPrintable }}
{{- end }}
}
`)

var DtoTemplate, _ = template.New("dtoTemplate").Parse(`
//go:generate go run ./dto-builder-generator/main.go
var (
{{- range .Operations }}
{{- if .OptsField }}
_ optionsProvider[{{ .OptsField.KindNoPtr }}] = new({{ .OptsField.DtoDecl }})
{{- end }}
{{- end }}
import (
_ "embed"
"text/template"
)
`)

var DtoDeclTemplate, _ = template.New("dtoTemplate").Parse(`
type {{ .DtoDecl }} struct {
{{- range .Fields }}
{{- if .ShouldBeInDto }}
{{ .Name }} {{ .DtoKind }} {{ if .Required }}// required{{ end }}
{{- end }}
{{- end }}
}
`)
var (
//go:embed templates/package.tmpl
packageTemplateContent string
PackageTemplate, _ = template.New("packageTemplates").Parse(packageTemplateContent)

var ImplementationTemplate, _ = template.New("implementationTemplate").
Funcs(template.FuncMap{
//go:embed templates/interface.tmpl
interfaceTemplateContent string
InterfaceTemplate, _ = template.New("interfaceTemplate").Funcs(template.FuncMap{
"deref": func(p *DescriptionMappingKind) string { return string(*p) },
}).
Parse(`
{{ define "MAPPING" -}}
&{{ .KindNoPtr }}{
{{- range .Fields }}
{{- if .ShouldBeInDto }}
{{ if .IsStruct }}{{ else }}{{ .Name }}: r{{ .Path }},{{ end -}}
{{- end -}}
{{- end }}
}
{{- range .Fields }}
{{- if .ShouldBeInDto }}
{{- if .IsStruct }}
if r{{ .Path }} != nil {
{{- if not .IsSlice }}
opts{{ .Path }} = {{ template "MAPPING" . -}}
{{- else }}
s := make({{ .Kind }}, len(r{{ .Path }}))
for i, v := range r{{ .Path }} {
s[i] = {{ .KindNoSlice }}{
{{- range .Fields }}
{{ .Name }}: v.{{ .Name }},
{{- end }}
}
}
opts{{ .Path }} = s
{{ end -}}
}
{{- end -}}
{{ end -}}
{{ end }}
{{ end }}
{{ define "MAPPING_FUNC" }}
func (r {{ .From.Name }}) {{ .MappingFuncName }}() *{{ .To.KindNoPtr }} {
// TODO: Mapping
return &{{ .To.KindNoPtr }}{}
}
{{ end }}
import (
"context"
}).Parse(interfaceTemplateContent)

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections"
)
//go:embed templates/operation_struct.tmpl
operationStructTemplateContent string
OperationStructTemplate, _ = template.New("optionsTemplate").Parse(operationStructTemplateContent)

{{ $impl := .NameLowerCased }}
var _ {{ .Name }} = (*{{ $impl }})(nil)
//go:embed templates/struct.tmpl
structTemplateContent string
StructTemplate, _ = template.New("structTemplate").Parse(structTemplateContent)

type {{ $impl }} struct {
client *Client
}
{{ range .Operations }}
{{ if and (eq .Name "Show") .ShowMapping }}
func (v *{{ $impl }}) Show(ctx context.Context, request *{{ .OptsField.DtoDecl }}) ([]{{ .ShowMapping.To.Name }}, error) {
opts := request.toOpts()
dbRows, err := validateAndQuery[{{ .ShowMapping.From.Name }}](v.client, ctx, opts)
if err != nil {
return nil, err
}
resultList := convertRows[{{ .ShowMapping.From.Name }}, {{ .ShowMapping.To.Name }}](dbRows)
return resultList, nil
}
{{ else if eq .Name "ShowByID" }}
func (v *{{ $impl }}) ShowByID(ctx context.Context, id {{ .ObjectInterface.IdentifierKind }}) (*{{ .ObjectInterface.NameSingular }}, error) {
// TODO: adjust request if e.g. LIKE is supported for the resource
{{ $impl }}, err := v.Show(ctx, NewShow{{ .ObjectInterface.NameSingular }}Request())
if err != nil {
return nil, err
}
return collections.FindOne({{ $impl }}, func(r {{ .ObjectInterface.NameSingular }}) bool { return r.Name == id.Name() })
}
{{ else if and (eq .Name "Describe") .DescribeMapping }}
{{ if .DescribeKind }}
{{ if eq (deref .DescribeKind) "single_value" }}
func (v *{{ $impl }}) Describe(ctx context.Context, id {{ .ObjectInterface.IdentifierKind }}) (*{{ .DescribeMapping.To.Name }}, error) {
opts := &{{ .OptsField.Name }}{
name: id,
}
result, err := validateAndQueryOne[{{ .DescribeMapping.From.Name }}](v.client, ctx, opts)
if err != nil {
return nil, err
}
return result.convert(), nil
}
{{ else if eq (deref .DescribeKind) "slice" }}
func (v *{{ $impl }}) Describe(ctx context.Context, id {{ .ObjectInterface.IdentifierKind }}) ([]{{ .DescribeMapping.To.Name }}, error) {
opts := &{{ .OptsField.Name }}{
name: id,
}
rows, err := validateAndQuery[{{ .DescribeMapping.From.Name}}](v.client, ctx, opts)
if err != nil {
return nil, err
}
return convertRows[{{ .DescribeMapping.From.Name }}, {{ .DescribeMapping.To.Name }}](rows), nil
}
{{ end }}
{{ end }}
{{ else }}
func (v *{{ $impl }}) {{ .Name }}(ctx context.Context, request *{{ .OptsField.DtoDecl }}) error {
opts := request.toOpts()
return validateAndExec(v.client, ctx, opts)
}
{{ end }}
{{ end }}
//go:embed templates/dto_declarations.tmpl
dtoDeclarationsTemplateContent string
DtoTemplate, _ = template.New("dtoTemplate").Parse(dtoDeclarationsTemplateContent)

{{ range .Operations }}
{{- if .OptsField }}
func (r *{{ .OptsField.DtoDecl }}) toOpts() *{{ .OptsField.KindNoPtr }} {
opts := {{ template "MAPPING" .OptsField -}}
return opts
}
{{ if .ShowMapping }}
{{ template "MAPPING_FUNC" .ShowMapping }}
{{ end }}
{{ if .DescribeMapping }}
{{ template "MAPPING_FUNC" .DescribeMapping }}
{{ end }}
{{- end}}
{{ end }}
`)
//go:embed templates/dto_structs.tmpl
dtoStructsTemplateContent string
DtoDeclTemplate, _ = template.New("dtoTemplate").Parse(dtoStructsTemplateContent)

var TestFuncTemplate, _ = template.New("testFuncTemplate").Parse(`
{{ define "VALIDATION_TEST" }}
{{ $field := . }}
{{- range .Validations }}
t.Run("{{ .TodoComment $field }}", func(t *testing.T) {
opts := defaultOpts()
// TODO: fill me
assertOptsInvalidJoinedErrors(t, opts, {{ .ReturnedError $field }})
})
{{ end -}}
{{ end }}
//go:embed templates/integration_tests.tmpl
integrationTestTemplateContent string
IntegrationTestsTemplate, _ = template.New("integrationTestsTemplate").Parse(integrationTestTemplateContent)

{{ define "VALIDATIONS" }}
{{- template "VALIDATION_TEST" . -}}
{{ range .Fields }}
{{- if .HasAnyValidationInSubtree }}
{{- template "VALIDATIONS" . -}}
{{ end -}}
{{- end -}}
{{ end }}
//go:embed templates/implementation.tmpl
implementationTemplateContent string
ImplementationTemplate *template.Template

import "testing"
//go:embed templates/unit_tests.tmpl
unitTestTemplateContent string
UnitTestsTemplate *template.Template

{{ range .Operations }}
{{- if .OptsField }}
func Test{{ .ObjectInterface.Name }}_{{ .Name }}(t *testing.T) {
id := random{{ .ObjectInterface.IdentifierKind }}()
//go:embed templates/validations.tmpl
validationTemplateContent string
ValidationsTemplate *template.Template

// Minimal valid {{ .OptsField.KindNoPtr }}
defaultOpts := func() *{{ .OptsField.KindNoPtr }} {
return &{{ .OptsField.KindNoPtr }}{
name: id,
}
}
//go:embed templates/sub_templates/to_opts_mapping.tmpl
toOptsMappingTemplateContent string

t.Run("validation: nil options", func(t *testing.T) {
var opts *{{ .OptsField.KindNoPtr }} = nil
assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions)
})
//go:embed templates/sub_templates/convert.tmpl
convertTemplateContent string

{{- template "VALIDATIONS" .OptsField }}
//go:embed templates/sub_templates/implementation_mappings.tmpl
implementationMappingsTemplateContent string

t.Run("basic", func(t *testing.T) {
opts := defaultOpts()
// TODO: fill me
assertOptsValidAndSQLEquals(t, opts, "TODO: fill me")
})
//go:embed templates/sub_templates/implementation_functions.tmpl
implementationFunctionsTemplateContent string

t.Run("all options", func(t *testing.T) {
opts := defaultOpts()
// TODO: fill me
assertOptsValidAndSQLEquals(t, opts, "TODO: fill me")
})
}
{{- end }}
{{ end }}
`)
//go:embed templates/sub_templates/validation_test.tmpl
validationTestTemplateContent string

var ValidationsImplTemplate, _ = template.New("validationsImplTemplate").Parse(`
{{ define "VALIDATIONS" }}
{{- $field := . -}}
{{- range .Validations }}
if {{ .Condition $field }} {
errs = append(errs, {{ .ReturnedError $field }})
}
{{- end -}}
{{- range .Fields }}
{{- if .HasAnyValidationInSubtree }}
if valueSet(opts{{ .Path }}) {
{{- template "VALIDATIONS" . }}
}
{{- end -}}
{{- end -}}
{{ end }}
//go:embed templates/sub_templates/validation_tests.tmpl
validationTestsTemplateContent string

var (
{{- range .Operations }}
{{- if .OptsField }}
_ validatable = new({{ .OptsField.KindNoPtr }})
{{- end }}
{{- end }}
//go:embed templates/sub_templates/validation_implementation.tmpl
validationImplementationTemplateContent string
)
{{ range .Operations }}
{{- if .OptsField }}
func (opts *{{ .OptsField.KindNoPtr }}) validate() error {
if opts == nil {
return ErrNilOptions
}
var errs []error
{{- template "VALIDATIONS" .OptsField }}
return JoinErrors(errs...)
}
{{- end }}
{{ end }}
`)

var IntegrationTestsTemplate, _ = template.New("integrationTestsTemplate").Parse(`
import "testing"

func TestInt_{{ .Name }}(t *testing.T) {
// TODO: prepare common resources
{{ range .Operations }}
t.Run("{{ .Name }}", func(t *testing.T) {
// TODO: fill me
func init() {
subTemplates := template.New("subTemplates").Funcs(template.FuncMap{
"deref": func(p *DescriptionMappingKind) string { return string(*p) },
})
{{ end -}}
subTemplates, _ = subTemplates.New("toOptsMapping").Parse(toOptsMappingTemplateContent)
subTemplates, _ = subTemplates.New("convert").Parse(convertTemplateContent)
subTemplates, _ = subTemplates.New("implementationMappings").Parse(implementationMappingsTemplateContent)
subTemplates, _ = subTemplates.New("implementationFunctions").Parse(implementationFunctionsTemplateContent)
subTemplates, _ = subTemplates.New("validationTest").Parse(validationTestTemplateContent)
subTemplates, _ = subTemplates.New("validationTests").Parse(validationTestsTemplateContent)
subTemplates, _ = subTemplates.New("validationImplementation").Parse(validationImplementationTemplateContent)

ImplementationTemplate, _ = subTemplates.New("implementationTemplate").Parse(implementationTemplateContent)
UnitTestsTemplate, _ = subTemplates.New("unitTestsTemplate").Parse(unitTestTemplateContent)
ValidationsTemplate, _ = subTemplates.New("validationsTemplate").Parse(validationTemplateContent)
}
`)
Loading

0 comments on commit 5035e2f

Please sign in to comment.