Skip to content

Commit

Permalink
generate mains for apis in build folder
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Vlasic committed Dec 3, 2021
1 parent 399aafa commit 0f71832
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 75 deletions.
7 changes: 7 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@ Please check the following rules when naming projects, stages and functions:
return
}

var ane *controller.ApiNewError
if errors.As(err, &ane) {
ui.Errorf("function New for api %s does not have proper type", ane.Api)
ui.Info("Function should have no parameters and only one return value of type struct or pointer to struct.")
return
}

ui.Error(err)
}

Expand Down
10 changes: 9 additions & 1 deletion cli/controller/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controller

import (
"fmt"
"path/filepath"
"time"

"github.com/mantil-io/mantil/cli/log"
Expand All @@ -11,14 +12,18 @@ import (
)

const (
FunctionsDir = "functions"
ApiDir = "api"
PublicDir = "public"
BuildDir = "build"
BinaryName = "bootstrap"
DeployHTTPMethod = "deploy"
HashCharacters = 8
)

var (
FunctionsPath = filepath.Join(BuildDir, "functions")
)

type DeployArgs struct {
Stage string
}
Expand Down Expand Up @@ -154,6 +159,9 @@ func (d *Deploy) HasUpdates() bool {
}

func (d *Deploy) buildAndFindDiffs() error {
if err := d.createMains(); err != nil {
return log.Wrap(err)
}
lf, err := d.localFunctions()
if err != nil {
return log.Wrap(err)
Expand Down
29 changes: 26 additions & 3 deletions cli/controller/deploy_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,37 @@ import (
"github.com/mantil-io/mantil/kit/shell"
)

func (d *Deploy) createMains() error {
apis, err := d.localDirs(ApiDir)
if err != nil {
return log.Wrap(err)
}
for _, api := range apis {
dir := d.apiDir(api)
mainDest := filepath.Join(d.apiMainDir(api), "main.go")
if err := generateMain(api, dir, mainDest); err != nil {
return log.Wrap(err)
}
}
return nil
}

func (d *Deploy) apiDir(api string) string {
return filepath.Join(d.store.ProjectRoot(), ApiDir, api)
}

func (d *Deploy) apiMainDir(api string) string {
return filepath.Join(d.store.ProjectRoot(), FunctionsPath, api)
}

func (d *Deploy) localFunctions() ([]domain.Resource, error) {
localFuncNames, err := d.localDirs(FunctionsDir)
localFuncNames, err := d.localDirs(FunctionsPath)
if err != nil {
return nil, log.Wrap(err)
}
var localFuncs []domain.Resource
for _, n := range localFuncNames {
funcDir := path.Join(d.store.ProjectRoot(), FunctionsDir, n)
funcDir := d.apiMainDir(n)
if err := d.buildTimer(func() error { return d.buildFunction(BinaryName, funcDir) }); err != nil {
return nil, log.Wrap(err)
}
Expand Down Expand Up @@ -85,7 +108,7 @@ func (d *Deploy) uploadFunctions() error {
if f == nil {
continue
}
path := filepath.Join(d.store.ProjectRoot(), FunctionsDir, n, BinaryName)
path := filepath.Join(d.apiMainDir(n), BinaryName)
ui.Info("\t%s", n)
if err := d.uploadBinaryToS3(f.S3Key, path); err != nil {
return log.Wrap(err, "failed to upload file %s to s3", path)
Expand Down
101 changes: 54 additions & 47 deletions cli/controller/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ import (
"golang.org/x/mod/modfile"
)

type ApiNewError struct {
Api string
}

func (e ApiNewError) Error() string {
return fmt.Sprintf("function New for api %s doesn't have proper type", e.Api)
}

type GenerateApiArgs struct {
Name string
Methods []string
Expand All @@ -42,12 +50,6 @@ func GenerateApi(a GenerateApiArgs) error {
if err != nil {
return log.Wrap(err)
}
if err := generateFunctionMain(a.Name, importPath, projectPath); err != nil {
return log.Wrap(err)
}
if err := generateFunctionGitignore(a.Name, projectPath); err != nil {
return log.Wrap(err)
}
if err := generateFunctionTest(importPath, projectPath, a.Name, a.Methods); err != nil {
return log.Wrap(err)
}
Expand All @@ -63,42 +65,14 @@ func findPackageImportPath(projectPath string) (string, error) {
return modfile.ModulePath(buf), nil
}

func generateFunctionMain(functionName, importPath, projectPath string) error {
functionPath := filepath.Join(FunctionsDir, functionName)
root := filepath.Join(projectPath, functionPath)
mainFile := filepath.Join(root, "main.go")
if fileExists(mainFile) {
ui.Info("%s already exists", relativePath(projectPath, mainFile))
return nil
}
ui.Info("Generating %s...", relativePath(projectPath, mainFile))
if err := generateFromTemplate(
apiFunctionMainTemplate,
&function{
Name: functionName,
ImportPath: importPath,
},
mainFile,
); err != nil {
return log.Wrap(err)
}
return nil
}

func generateFunctionGitignore(functionName, projectPath string) error {
functionPath := filepath.Join(FunctionsDir, functionName)
gitignoreFile := filepath.Join(projectPath, functionPath, ".gitignore")
if fileExists(gitignoreFile) {
ui.Info("%s already exists", relativePath(projectPath, gitignoreFile))
return nil
}
ui.Info("Generating %s...", relativePath(projectPath, gitignoreFile))
f, err := os.Create(gitignoreFile)
func generateGitignore(path, content string) error {
gitignoreFile := filepath.Join(path, ".gitignore")
f, err := os.OpenFile(gitignoreFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return log.Wrap(err)
}
defer f.Close()
_, err = f.WriteString(fmt.Sprintf("%s\n", BinaryName))
_, err = f.WriteString(fmt.Sprintf("%s\n", content))
return log.Wrap(err)
}

Expand Down Expand Up @@ -259,28 +233,61 @@ func saveFile(in []byte, path string) error {
return nil
}

func extractApiStructName(api, dir string) (string, error) {
func generateMain(api, apiDir, destination string) error {
if err := isNewValid(api, apiDir); err != nil {
return log.Wrap(err)
}
projectPath, err := domain.FindProjectRoot(".")
if err != nil {
return log.Wrap(err)
}
importPath, err := findPackageImportPath(projectPath)
if err != nil {
return log.Wrap(err)
}
if err := generateFromTemplate(
apiFunctionMainTemplate,
&function{
Name: api,
ImportPath: importPath,
},
destination,
); err != nil {
return log.Wrap(err)
}
return nil
}

// isNewValid checks whether function New in api is of proper type
// function should have no parameters and only one return value - struct or pointer to the struct
func isNewValid(api, dir string) error {
pkgs, err := parser.ParseDir(token.NewFileSet(), dir, nil, parser.AllErrors)
if err != nil {
return "", log.Wrap(err)
return log.Wrap(err)
}
pkg, ok := pkgs[api]
if !ok {
return "", log.Wrapf("package %s doesn't exist in folder %s", api, dir)
return log.Wrapf("package %s doesn't exist in folder %s", api, dir)
}
for _, v := range pkg.Files {
for _, o := range v.Scope.Objects {
if o.Name == "New" && o.Kind.String() == "func" {
decl, ok := o.Decl.(*ast.FuncDecl)
if !ok {
return "", log.Wrapf("could not find declaration of function New")
return log.Wrap(&ApiNewError{api})
}
// is not a function
if decl.Recv != nil {
continue
}
// has no parameters
if len(decl.Type.Params.List) > 0 {
return "", log.Wrapf("function New should have no parameters")
return log.Wrap(&ApiNewError{api})
}
rl := decl.Type.Results.List
// has only one return value which is either struct or pointer to struct
if len(rl) > 1 {
return "", log.Wrapf("too many return values for New")
return log.Wrap(&ApiNewError{api})
}
var idExpr ast.Expr
expr, ok := rl[0].Type.(*ast.StarExpr)
Expand All @@ -295,13 +302,13 @@ func extractApiStructName(api, dir string) (string, error) {
if ok {
_, ok := ft.Type.(*ast.StructType)
if ok {
return ident.Name, nil
return nil
}
}
}
return "", log.Wrapf("return value of New is not struct or pointer to struct")
return log.Wrap(&ApiNewError{api})
}
}
}
return "", log.Wrapf("could not find function New for api %s", api)
return log.Wrap(&ApiNewError{api})
}
42 changes: 19 additions & 23 deletions cli/controller/generate_test.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,47 @@
package controller

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestExtractApiStructNamePtr(t *testing.T) {
stc, err := extractApiStructName("ping", "testdata/generate/ping_ptr")
func TestIsNewValidPtr(t *testing.T) {
err := isNewValid("ping", "testdata/generate/ping_ptr")
require.NoError(t, err)
assert.Equal(t, "Ping", stc)
}

func TestExtractApiStructNameStruct(t *testing.T) {
stc, err := extractApiStructName("ping", "testdata/generate/ping_struct")
func TestIsNewValidStruct(t *testing.T) {
err := isNewValid("ping", "testdata/generate/ping_struct")
require.NoError(t, err)
assert.Equal(t, "Ping", stc)
}

func TestExtractApiStructNameNoNew(t *testing.T) {
_, err := extractApiStructName("ping", "testdata/generate/no_new")
fmt.Println(err)
func TestIsNewValidNoNew(t *testing.T) {
err := isNewValid("ping", "testdata/generate/no_new")
require.Error(t, err)
}

func TestExtractApiStructNameNewWithParameters(t *testing.T) {
_, err := extractApiStructName("ping", "testdata/generate/parameters")
fmt.Println(err)
func TestIsNewValidWithParameters(t *testing.T) {
err := isNewValid("ping", "testdata/generate/parameters")
require.Error(t, err)
}

func TestExtractApiStructNameTooManyValues(t *testing.T) {
_, err := extractApiStructName("ping", "testdata/generate/return_values")
fmt.Println(err)
func TestIsNewValidTooManyReturnValues(t *testing.T) {
err := isNewValid("ping", "testdata/generate/return_values")
require.Error(t, err)
}

func TestExtractApiStructNameWrongValue(t *testing.T) {
_, err := extractApiStructName("ping", "testdata/generate/return_value")
fmt.Println(err)
func TestIsNewValidWrongReturnValue(t *testing.T) {
err := isNewValid("ping", "testdata/generate/return_value")
require.Error(t, err)
}

func TestExtractApiStructNameNewWrongType(t *testing.T) {
_, err := extractApiStructName("ping", "testdata/generate/new_wrong_type")
fmt.Println(err)
func TestIsNewValidNewWrongType(t *testing.T) {
err := isNewValid("ping", "testdata/generate/new_wrong_type")
require.Error(t, err)
}

func TestIsNewValidMethod(t *testing.T) {
err := isNewValid("ping", "testdata/generate/new_method")
require.Error(t, err)
}
6 changes: 5 additions & 1 deletion cli/controller/new.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package controller

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand All @@ -10,7 +11,6 @@ import (
"github.com/mantil-io/mantil/cli/ui"
"github.com/mantil-io/mantil/domain"
"github.com/mantil-io/mantil/kit/git"
"github.com/pkg/errors"
)

var TemplateRepos = map[string]string{
Expand Down Expand Up @@ -60,10 +60,14 @@ func createProject(name, from, moduleName string) error {
return log.Wrap(err, "could not initialize repository from source %s: %v", repo, err)

}

// delete LICENSE from template repositories
if !isExternalRepo(from) {
os.Remove(filepath.Join(projectPath, LicenseFile))
}
// create .gitignore with BuildDir
generateGitignore(projectPath, BuildDir)

fs, err := newStore()
if err != nil {
return log.Wrap(err)
Expand Down
5 changes: 5 additions & 0 deletions cli/controller/testdata/generate/new_method/ping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ping

type Ping struct{}

func (p Ping) New() {}

0 comments on commit 0f71832

Please sign in to comment.