private/apigen: prevent self imports

This change prevents Go code produced by the API generator from
importing its own package. Previously, we tried to prevent self imports
by skipping import paths whose last segment matched the generated Go
code's package name. However, aliased imports circumvented this.
We now require API definitions to define the Go package path so that we
can compare this with the import path directly.

Change-Id: I7ae7ec5e1a342d2f76cd28ff72d4cd7285c2820a
This commit is contained in:
Jeremy Wharton 2023-11-14 11:04:57 -06:00 committed by Storj Robot
parent 24370964ab
commit a52934ef4d
5 changed files with 38 additions and 15 deletions

View File

@ -30,7 +30,11 @@ type API struct {
Version string
Description string
// The package name to use for the Go generated code.
// If omitted, the last segment of the PackagePath will be used as the package name.
PackageName string
// The path of the package that will use the generated Go code.
// This is used to prevent the code from importing its own package.
PackagePath string
// BasePath is the base path for the API endpoints. E.g. "/api".
// It doesn't require to begin with "/". When empty, "/" is used.
BasePath string

View File

@ -16,7 +16,11 @@ import (
)
func main() {
a := &apigen.API{PackageName: "example", Version: "v0", BasePath: "/api"}
a := &apigen.API{
PackagePath: "storj.io/storj/private/apigen/example",
Version: "v0",
BasePath: "/api",
}
g := a.Group("Documents", "docs")

View File

@ -4,6 +4,7 @@
package apigen
import (
"fmt"
"go/format"
"os"
"reflect"
@ -38,14 +39,14 @@ func (a *API) generateGo() ([]byte, error) {
result := &StringBuilder{}
pf := result.Writelnf
getPackageName := func(path string) string {
pathPackages := strings.Split(path, "/")
name := pathPackages[len(pathPackages)-1]
if name == "main" {
panic(errs.New(`invalid package name. Your types cannot be defined in a package named "main"`))
}
if a.PackagePath == "" {
return nil, errs.New("Package path must be defined")
}
return name
packageName := a.PackageName
if packageName == "" {
parts := strings.Split(a.PackagePath, "/")
packageName = parts[len(parts)-1]
}
imports := struct {
@ -59,7 +60,7 @@ func (a *API) generateGo() ([]byte, error) {
i := func(paths ...string) {
for _, path := range paths {
if path == "" || getPackageName(path) == a.PackageName {
if path == "" || path == a.PackagePath {
continue
}
@ -107,7 +108,7 @@ func (a *API) generateGo() ([]byte, error) {
pf(
"var Err%sAPI = errs.Class(\"%s %s api\")",
capitalize(group.Prefix),
a.PackageName,
packageName,
strings.ToLower(group.Prefix),
)
}
@ -286,7 +287,7 @@ func (a *API) generateGo() ([]byte, error) {
pf("// DO NOT EDIT.")
pf("")
pf("package %s", a.PackageName)
pf("package %s", packageName)
pf("")
pf("import (")
@ -322,8 +323,17 @@ func (a *API) generateGo() ([]byte, error) {
// If type is from the same package then we use only type's name.
// If type is from external package then we use type along with its appropriate package name.
func (a *API) handleTypesPackage(t reflect.Type) string {
if strings.HasPrefix(t.String(), a.PackageName) {
return t.Elem().Name()
switch t.Kind() {
case reflect.Array:
return fmt.Sprintf("[%d]%s", t.Len(), a.handleTypesPackage(t.Elem()))
case reflect.Slice:
return "[]" + a.handleTypesPackage(t.Elem())
case reflect.Pointer:
return "*" + a.handleTypesPackage(t.Elem())
}
if t.PkgPath() == a.PackagePath {
return t.Name()
}
return t.String()

View File

@ -13,7 +13,12 @@ import (
)
func main() {
api := &apigen.API{PackageName: "admin", Version: "v1", BasePath: "/api"}
api := &apigen.API{
PackageName: "admin",
PackagePath: "storj.io/storj/satellite/admin/back-office",
Version: "v1",
BasePath: "/api",
}
// This is an example and must be deleted when we define the first real endpoint.
group := api.Group("Example", "example")

View File

@ -24,7 +24,7 @@ func main() {
Version: "v0",
BasePath: "/api",
Description: "Interacts with projects",
PackageName: "consoleapi",
PackagePath: "storj.io/storj/satellite/console/consoleweb/consoleapi",
}
{