diff --git a/private/apigen/common.go b/private/apigen/common.go
index a27770028..ad294ced0 100644
--- a/private/apigen/common.go
+++ b/private/apigen/common.go
@@ -5,6 +5,7 @@ package apigen
import (
"fmt"
+ "path"
"reflect"
"strings"
@@ -13,9 +14,18 @@ import (
// API represents specific API's configuration.
type API struct {
- Version string
- Description string
- PackageName string
+ // Version is the corresponding version of the API.
+ // It's concatenated to the BasePath, so assuming the base path is "/api" and the version is "v1"
+ // the API paths will begin with `/api/v1`.
+ // When empty, the version doesn't appear in the API paths. If it starts or ends with one or more
+ // "/", they are stripped from the API endpoint paths.
+ Version string
+ Description string
+ // The package name to use for the Go generated code.
+ PackageName 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
Auth api.Auth
EndpointGroups []*EndpointGroup
}
@@ -32,6 +42,14 @@ func (a *API) Group(name, prefix string) *EndpointGroup {
return group
}
+func (a *API) endpointBasePath() string {
+ if strings.HasPrefix(a.BasePath, "/") {
+ return path.Join(a.BasePath, a.Version)
+ }
+
+ return "/" + path.Join(a.BasePath, a.Version)
+}
+
// StringBuilder is an extension of strings.Builder that allows for writing formatted lines.
type StringBuilder struct{ strings.Builder }
diff --git a/private/apigen/common_test.go b/private/apigen/common_test.go
new file mode 100644
index 000000000..00b2f39f6
--- /dev/null
+++ b/private/apigen/common_test.go
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 Storj Labs, Inc.
+// See LICENSE for copying information.
+
+package apigen
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAPI_endpointBasePath(t *testing.T) {
+ cases := []struct {
+ version string
+ basePath string
+ expected string
+ }{
+ {version: "", basePath: "", expected: "/"},
+ {version: "v1", basePath: "", expected: "/v1"},
+ {version: "v0", basePath: "/", expected: "/v0"},
+ {version: "", basePath: "api", expected: "/api"},
+ {version: "v2", basePath: "api", expected: "/api/v2"},
+ {version: "v2", basePath: "/api", expected: "/api/v2"},
+ {version: "v2", basePath: "api/", expected: "/api/v2"},
+ {version: "v2", basePath: "/api/", expected: "/api/v2"},
+ {version: "/v3", basePath: "api", expected: "/api/v3"},
+ {version: "/v3/", basePath: "api", expected: "/api/v3"},
+ {version: "v3/", basePath: "api", expected: "/api/v3"},
+ {version: "//v3/", basePath: "api", expected: "/api/v3"},
+ {version: "v3///", basePath: "api", expected: "/api/v3"},
+ {version: "/v3///", basePath: "/api/test/", expected: "/api/test/v3"},
+ {version: "/v4.2", basePath: "api/test", expected: "/api/test/v4.2"},
+ {version: "/v4/2", basePath: "/api/test", expected: "/api/test/v4/2"},
+ }
+
+ for _, c := range cases {
+ t.Run(fmt.Sprintf("version:%s basePath: %s", c.version, c.basePath), func(t *testing.T) {
+ a := API{
+ Version: c.version,
+ BasePath: c.basePath,
+ }
+
+ assert.Equal(t, c.expected, a.endpointBasePath())
+ })
+ }
+}
diff --git a/private/apigen/docgen.go b/private/apigen/docgen.go
index 575c54b74..9f8b8f613 100644
--- a/private/apigen/docgen.go
+++ b/private/apigen/docgen.go
@@ -58,7 +58,7 @@ func (api *API) generateDocumentation() string {
for _, endpoint := range group.endpoints {
wf("
\n\n", getEndpointLink(group.Name, endpoint.Name), endpoint.Name)
wf("%s\n\n", endpoint.Description)
- wf("`%s /%s%s`\n\n", endpoint.Method, group.Prefix, endpoint.Path)
+ wf("`%s %s/%s%s`\n\n", endpoint.Method, api.endpointBasePath(), group.Prefix, endpoint.Path)
if len(endpoint.QueryParams) > 0 {
wf("**Query Params:**\n\n")
@@ -140,7 +140,6 @@ func getTypeNameRecursively(t reflect.Type, level int) string {
elemType := t.Elem()
if elemType.Kind() == reflect.Uint8 { // treat []byte as string in docs
return prefix + "string"
-
}
return fmt.Sprintf("%s[\n%s\n%s]\n", prefix, getTypeNameRecursively(elemType, level+1), prefix)
case reflect.Struct:
diff --git a/private/apigen/example/apidocs.gen.md b/private/apigen/example/apidocs.gen.md
index 631db3ea8..52ff036bb 100644
--- a/private/apigen/example/apidocs.gen.md
+++ b/private/apigen/example/apidocs.gen.md
@@ -4,15 +4,16 @@
**Version:** `v0`
-**List of endpoints:**
+List of Endpoints
+
* TestAPI
- * [](#e-31104e2390954bdc113e2444e69a0667)
+ * [](#testapi-)
-
+
-`POST /testapi/{path}`
+`POST /api/v0/testapi/{path}`
**Query Params:**
diff --git a/private/apigen/example/gen.go b/private/apigen/example/gen.go
index 7b44d7f92..9267fb456 100644
--- a/private/apigen/example/gen.go
+++ b/private/apigen/example/gen.go
@@ -14,7 +14,7 @@ import (
)
func main() {
- a := &apigen.API{PackageName: "example", Version: "v0"}
+ a := &apigen.API{PackageName: "example", Version: "v0", BasePath: "/api"}
g := a.Group("TestAPI", "testapi")
diff --git a/private/apigen/gogen.go b/private/apigen/gogen.go
index ed272ccdf..160c13c50 100644
--- a/private/apigen/gogen.go
+++ b/private/apigen/gogen.go
@@ -101,7 +101,12 @@ func (a *API) generateGo() ([]byte, error) {
for _, group := range a.EndpointGroups {
i("github.com/zeebo/errs")
- pf("var Err%sAPI = errs.Class(\"%s %s api\")", cases.Title(language.Und).String(group.Prefix), a.PackageName, group.Prefix)
+ pf(
+ "var Err%sAPI = errs.Class(\"%s %s api\")",
+ cases.Title(language.Und).String(group.Prefix),
+ a.PackageName,
+ group.Prefix,
+ )
}
pf("")
@@ -168,10 +173,16 @@ func (a *API) generateGo() ([]byte, error) {
pf("auth: auth,")
pf("}")
pf("")
- pf("%sRouter := router.PathPrefix(\"/api/v0/%s\").Subrouter()", group.Prefix, group.Prefix)
+ pf("%sRouter := router.PathPrefix(\"%s/%s\").Subrouter()", group.Prefix, a.endpointBasePath(), group.Prefix)
for _, endpoint := range group.endpoints {
handlerName := "handle" + endpoint.MethodName
- pf("%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", group.Prefix, endpoint.Path, handlerName, endpoint.Method)
+ pf(
+ "%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")",
+ group.Prefix,
+ endpoint.Path,
+ handlerName,
+ endpoint.Method,
+ )
}
pf("")
pf("return handler")
@@ -243,7 +254,11 @@ func (a *API) generateGo() ([]byte, error) {
pf("")
pf("err = json.NewEncoder(w).Encode(retVal)")
pf("if err != nil {")
- pf("h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.MethodName, cases.Title(language.Und).String(group.Prefix))
+ pf(
+ "h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))",
+ endpoint.MethodName,
+ cases.Title(language.Und).String(group.Prefix),
+ )
pf("}")
pf("}")
}
diff --git a/private/apigen/tsgen.go b/private/apigen/tsgen.go
index 1a7d86f65..a7e86e1ad 100644
--- a/private/apigen/tsgen.go
+++ b/private/apigen/tsgen.go
@@ -86,7 +86,7 @@ func (f *tsGenFile) registerTypes() {
func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
f.pf("\nexport class %sHttpApi%s {", group.Prefix, strings.ToUpper(f.api.Version))
f.pf("\tprivate readonly http: HttpClient = new HttpClient();")
- f.pf("\tprivate readonly ROOT_PATH: string = '/api/%s/%s';", f.api.Version, group.Prefix)
+ f.pf("\tprivate readonly ROOT_PATH: string = '%s/%s';", f.api.endpointBasePath(), group.Prefix)
for _, method := range group.endpoints {
f.pf("")
diff --git a/satellite/console/consoleweb/consoleapi/apidocs.gen.md b/satellite/console/consoleweb/consoleapi/apidocs.gen.md
index a81aa5b6e..8a1a193d4 100644
--- a/satellite/console/consoleweb/consoleapi/apidocs.gen.md
+++ b/satellite/console/consoleweb/consoleapi/apidocs.gen.md
@@ -24,7 +24,7 @@
Creates new Project with given info
-`POST /projects/create`
+`POST /api/v0/projects/create`
**Request body:**
@@ -49,7 +49,7 @@ unknown
Updates project with given info
-`PATCH /projects/update/{id}`
+`PATCH /api/v0/projects/update/{id}`
**Path Params:**
@@ -99,7 +99,7 @@ Updates project with given info
Deletes project by id
-`DELETE /projects/delete/{id}`
+`DELETE /api/v0/projects/delete/{id}`
**Path Params:**
@@ -111,7 +111,7 @@ Deletes project by id
Gets all projects user has
-`GET /projects/`
+`GET /api/v0/projects/`
**Response body:**
@@ -145,7 +145,7 @@ Gets all projects user has
Gets project's single bucket usage by bucket ID
-`GET /projects/bucket-rollup`
+`GET /api/v0/projects/bucket-rollup`
**Query Params:**
@@ -179,7 +179,7 @@ Gets project's single bucket usage by bucket ID
Gets project's all buckets usage
-`GET /projects/bucket-rollups`
+`GET /api/v0/projects/bucket-rollups`
**Query Params:**
@@ -215,7 +215,7 @@ Gets project's all buckets usage
Gets API keys by project ID
-`GET /projects/apikeys/{projectID}`
+`GET /api/v0/projects/apikeys/{projectID}`
**Query Params:**
@@ -265,7 +265,7 @@ Gets API keys by project ID
Creates new macaroon API key with given info
-`POST /apikeys/create`
+`POST /api/v0/apikeys/create`
**Request body:**
@@ -291,7 +291,7 @@ Creates new macaroon API key with given info
Deletes macaroon API key by id
-`DELETE /apikeys/delete/{id}`
+`DELETE /api/v0/apikeys/delete/{id}`
**Path Params:**
@@ -303,7 +303,7 @@ Deletes macaroon API key by id
Gets User by request context
-`GET /users/`
+`GET /api/v0/users/`
**Response body:**
diff --git a/satellite/console/consoleweb/consoleapi/gen/main.go b/satellite/console/consoleweb/consoleapi/gen/main.go
index 0ba3a458f..d43b153d3 100644
--- a/satellite/console/consoleweb/consoleapi/gen/main.go
+++ b/satellite/console/consoleweb/consoleapi/gen/main.go
@@ -22,6 +22,7 @@ func main() {
// definition for REST API
a := &apigen.API{
Version: "v0",
+ BasePath: "/api",
Description: "Interacts with projects",
PackageName: "consoleapi",
}