diff --git a/private/apigen/common.go b/private/apigen/common.go index ab1508c74..cb15a9614 100644 --- a/private/apigen/common.go +++ b/private/apigen/common.go @@ -16,10 +16,9 @@ import ( "storj.io/storj/private/api" ) -var ( - groupNameRegExp = regexp.MustCompile(`^([A-Z0-9]\w*)?$`) - groupPrefixRegExp = regexp.MustCompile(`^\w*$`) -) +// groupNameAndPrefixRegExp guarantees that Group name and prefix are empty or have are only formed +// by ASCII letters or digits and not starting with a digit. +var groupNameAndPrefixRegExp = regexp.MustCompile(`^([A-Za-z][0-9A-Za-z]*)?$`) // API represents specific API's configuration. type API struct { @@ -43,21 +42,21 @@ type API struct { // name must be `^([A-Z0-9]\w*)?$“ // prefix must be `^\w*$`. func (a *API) Group(name, prefix string) *EndpointGroup { - if !groupNameRegExp.MatchString(name) { + if !groupNameAndPrefixRegExp.MatchString(name) { panic( fmt.Sprintf( "invalid name for API Endpoint Group. name must fulfill the regular expression %q, got %q", - groupNameRegExp, + groupNameAndPrefixRegExp, name, ), ) } - if !groupPrefixRegExp.MatchString(prefix) { + if !groupNameAndPrefixRegExp.MatchString(prefix) { panic( fmt.Sprintf( "invalid prefix for API Endpoint Group %q. prefix must fulfill the regular expression %q, got %q", name, - groupPrefixRegExp, + groupNameAndPrefixRegExp, prefix, ), ) @@ -140,6 +139,15 @@ func capitalize(s string) string { return string(unicode.ToTitle(r)) + s[size:] } +func uncapitalize(s string) string { + r, size := utf8.DecodeRuneInString(s) + if size <= 0 { + return s + } + + return string(unicode.ToLower(r)) + s[size:] +} + type typeAndName struct { Type reflect.Type Name string diff --git a/private/apigen/common_test.go b/private/apigen/common_test.go index 00b2f39f6..14b48c2d9 100644 --- a/private/apigen/common_test.go +++ b/private/apigen/common_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAPI_endpointBasePath(t *testing.T) { @@ -45,3 +46,41 @@ func TestAPI_endpointBasePath(t *testing.T) { }) } } + +func TestAPI_Group(t *testing.T) { + t.Run("valid name and prefix", func(t *testing.T) { + api := API{} + + require.NotPanics(t, func() { + api.Group("testName", "tName") + }) + + require.NotPanics(t, func() { + api.Group("TestName1", "TName1") + }) + }) + + t.Run("invalid name", func(t *testing.T) { + api := API{} + + require.Panics(t, func() { + api.Group("1testName", "tName") + }) + + require.Panics(t, func() { + api.Group("test-name", "tName") + }) + }) + + t.Run("invalid prefix", func(t *testing.T) { + api := API{} + + require.Panics(t, func() { + api.Group("testName", "5tName") + }) + + require.Panics(t, func() { + api.Group("testname", "t_name") + }) + }) +} diff --git a/private/apigen/endpoint.go b/private/apigen/endpoint.go index 2e0ba8143..860aa7220 100644 --- a/private/apigen/endpoint.go +++ b/private/apigen/endpoint.go @@ -177,7 +177,24 @@ func (fe fullEndpoint) responseType() reflect.Type { // You should always create a group using API.Group because it validates the field values to // guarantee correct code generation. type EndpointGroup struct { - Name string + // Name is the group name. + // + // Go generator uses it as part of type, functions, interfaces names, and in code comments. + // The casing is adjusted according where it's used. + // + // TypeScript generator uses it as part of types names for the API functionality of this group. + // The casing is adjusted according where it's used. + // + // Document generator uses as it is. + Name string + // Prefix is a prefix used for + // + // Go generator uses it as part of variables names, error messages, and the URL base path for the group. + // The casing is adjusted according where it's used, but for the URL base path, lowercase is used. + // + // TypeScript generator uses it for composing the URL base path (lowercase). + // + // Document generator uses as it is. Prefix string endpoints []*fullEndpoint } diff --git a/private/apigen/example/api.gen.go b/private/apigen/example/api.gen.go index d6f5866b2..770d1ddd9 100644 --- a/private/apigen/example/api.gen.go +++ b/private/apigen/example/api.gen.go @@ -47,7 +47,7 @@ type DocumentsService interface { }, api.HTTPError) } -// DocumentsHandler is an api handler that exposes all docs related functionality. +// DocumentsHandler is an api handler that implements all Documents API endpoints functionality. type DocumentsHandler struct { log *zap.Logger mon *monkit.Scope diff --git a/private/apigen/example/client-api.gen.ts b/private/apigen/example/client-api.gen.ts index e50ea7f17..fcbe1aac4 100644 --- a/private/apigen/example/client-api.gen.ts +++ b/private/apigen/example/client-api.gen.ts @@ -50,7 +50,7 @@ export type GetResponse = Array export type GetResponseItemLastRetrievals = Array -export class DocsHttpApiV0 { +export class DocumentsHttpApiV0 { private readonly http: HttpClient = new HttpClient(); private readonly ROOT_PATH: string = '/api/v0/docs'; diff --git a/private/apigen/gogen.go b/private/apigen/gogen.go index d7213a782..58a18d4aa 100644 --- a/private/apigen/gogen.go +++ b/private/apigen/gogen.go @@ -12,8 +12,6 @@ import ( "time" "github.com/zeebo/errs" - "golang.org/x/text/cases" - "golang.org/x/text/language" "storj.io/common/uuid" ) @@ -108,9 +106,9 @@ func (a *API) generateGo() ([]byte, error) { i("github.com/zeebo/errs") pf( "var Err%sAPI = errs.Class(\"%s %s api\")", - cases.Title(language.Und).String(group.Prefix), + capitalize(group.Prefix), a.PackageName, - group.Prefix, + strings.ToLower(group.Prefix), ) } @@ -119,7 +117,7 @@ func (a *API) generateGo() ([]byte, error) { params := make(map[*fullEndpoint][]Param) for _, group := range a.EndpointGroups { - pf("type %sService interface {", group.Name) + pf("type %sService interface {", capitalize(group.Name)) for _, e := range group.endpoints { params[e] = append(e.PathParams, e.QueryParams...) @@ -152,38 +150,49 @@ func (a *API) generateGo() ([]byte, error) { } for _, group := range a.EndpointGroups { + cname := capitalize(group.Name) i("go.uber.org/zap", "github.com/spacemonkeygo/monkit/v3") - pf("// %sHandler is an api handler that exposes all %s related functionality.", group.Name, group.Prefix) - pf("type %sHandler struct {", group.Name) + pf( + "// %sHandler is an api handler that implements all %s API endpoints functionality.", + cname, + group.Name, + ) + pf("type %sHandler struct {", cname) pf("log *zap.Logger") pf("mon *monkit.Scope") - pf("service %sService", group.Name) + pf("service %sService", cname) pf("auth api.Auth") pf("}") pf("") } for _, group := range a.EndpointGroups { + cname := capitalize(group.Name) i("github.com/gorilla/mux") pf( "func New%s(log *zap.Logger, mon *monkit.Scope, service %sService, router *mux.Router, auth api.Auth) *%sHandler {", - group.Name, - group.Name, - group.Name, + cname, + cname, + cname, ) - pf("handler := &%sHandler{", group.Name) + pf("handler := &%sHandler{", cname) pf("log: log,") pf("mon: mon,") pf("service: service,") pf("auth: auth,") pf("}") pf("") - pf("%sRouter := router.PathPrefix(\"%s/%s\").Subrouter()", group.Prefix, a.endpointBasePath(), group.Prefix) + pf( + "%sRouter := router.PathPrefix(\"%s/%s\").Subrouter()", + uncapitalize(group.Prefix), + a.endpointBasePath(), + strings.ToLower(group.Prefix), + ) for _, endpoint := range group.endpoints { handlerName := "handle" + endpoint.GoName pf( "%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", - group.Prefix, + uncapitalize(group.Prefix), endpoint.Path, handlerName, endpoint.Method, @@ -200,7 +209,7 @@ func (a *API) generateGo() ([]byte, error) { i("net/http") pf("") handlerName := "handle" + endpoint.GoName - pf("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName) + pf("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", capitalize(group.Name), handlerName) pf("ctx := r.Context()") pf("var err error") pf("defer h.mon.Task()(&ctx)(&err)") @@ -262,7 +271,7 @@ func (a *API) generateGo() ([]byte, error) { pf( "h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.GoName, - cases.Title(language.Und).String(group.Prefix), + capitalize(group.Prefix), ) pf("}") pf("}") diff --git a/private/apigen/tsgen.go b/private/apigen/tsgen.go index 3e58873fa..0ae58fc0d 100644 --- a/private/apigen/tsgen.go +++ b/private/apigen/tsgen.go @@ -85,9 +85,9 @@ func (f *tsGenFile) registerTypes() { } func (f *tsGenFile) createAPIClient(group *EndpointGroup) { - f.pf("\nexport class %sHttpApi%s {", capitalize(group.Prefix), strings.ToUpper(f.api.Version)) + f.pf("\nexport class %sHttpApi%s {", capitalize(group.Name), strings.ToUpper(f.api.Version)) f.pf("\tprivate readonly http: HttpClient = new HttpClient();") - f.pf("\tprivate readonly ROOT_PATH: string = '%s/%s';", f.api.endpointBasePath(), group.Prefix) + f.pf("\tprivate readonly ROOT_PATH: string = '%s/%s';", f.api.endpointBasePath(), strings.ToLower(group.Prefix)) for _, method := range group.endpoints { f.pf("") diff --git a/satellite/console/consoleweb/consoleapi/api.gen.go b/satellite/console/consoleweb/consoleapi/api.gen.go index 70df1bc3a..1bb806a80 100644 --- a/satellite/console/consoleweb/consoleapi/api.gen.go +++ b/satellite/console/consoleweb/consoleapi/api.gen.go @@ -46,7 +46,7 @@ type UserManagementService interface { GenGetUser(ctx context.Context) (*console.ResponseUser, api.HTTPError) } -// ProjectManagementHandler is an api handler that exposes all projects related functionality. +// ProjectManagementHandler is an api handler that implements all ProjectManagement API endpoints functionality. type ProjectManagementHandler struct { log *zap.Logger mon *monkit.Scope @@ -54,7 +54,7 @@ type ProjectManagementHandler struct { auth api.Auth } -// APIKeyManagementHandler is an api handler that exposes all apikeys related functionality. +// APIKeyManagementHandler is an api handler that implements all APIKeyManagement API endpoints functionality. type APIKeyManagementHandler struct { log *zap.Logger mon *monkit.Scope @@ -62,7 +62,7 @@ type APIKeyManagementHandler struct { auth api.Auth } -// UserManagementHandler is an api handler that exposes all users related functionality. +// UserManagementHandler is an api handler that implements all UserManagement API endpoints functionality. type UserManagementHandler struct { log *zap.Logger mon *monkit.Scope diff --git a/web/satellite/src/api/v0.gen.ts b/web/satellite/src/api/v0.gen.ts index 10c07d41c..0e4360d2b 100644 --- a/web/satellite/src/api/v0.gen.ts +++ b/web/satellite/src/api/v0.gen.ts @@ -94,7 +94,7 @@ export class UpsertProjectInfo { createdAt: Time; } -export class ProjectsHttpApiV0 { +export class ProjectManagementHttpApiV0 { private readonly http: HttpClient = new HttpClient(); private readonly ROOT_PATH: string = '/api/v0/projects'; @@ -184,7 +184,7 @@ export class ProjectsHttpApiV0 { } } -export class ApikeysHttpApiV0 { +export class APIKeyManagementHttpApiV0 { private readonly http: HttpClient = new HttpClient(); private readonly ROOT_PATH: string = '/api/v0/apikeys'; @@ -209,7 +209,7 @@ export class ApikeysHttpApiV0 { } } -export class UsersHttpApiV0 { +export class UserManagementHttpApiV0 { private readonly http: HttpClient = new HttpClient(); private readonly ROOT_PATH: string = '/api/v0/users';