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("

%s (go to full list)

\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-) -

+

(go to full list)

-`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", }