private/apigen: Don't force casing for API group name/prefix
The API generators rely on the Name and Prefix fields of the EndpointGroup type to generate code. Conventional naming code requires using upper or lower case for types, functions, etc, however requiring the user to set this fields with the correct casing seems cumbersome for them because they can be adjusted depending where those values are used on the generated code. This commit lifts the restriction for the user and adjust the casing of them according to where they are used. Change-Id: I700a879d13b4789b4d6ba0519b4d7508061eac73
This commit is contained in:
parent
7b50ece931
commit
3193ff9155
@ -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
|
||||
|
@ -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")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -50,7 +50,7 @@ export type GetResponse = Array<GetResponseItem>
|
||||
|
||||
export type GetResponseItemLastRetrievals = Array<GetResponseItemLastRetrievalsItem>
|
||||
|
||||
export class DocsHttpApiV0 {
|
||||
export class DocumentsHttpApiV0 {
|
||||
private readonly http: HttpClient = new HttpClient();
|
||||
private readonly ROOT_PATH: string = '/api/v0/docs';
|
||||
|
||||
|
@ -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("}")
|
||||
|
@ -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("")
|
||||
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user