diff --git a/private/apigen/common.go b/private/apigen/common.go index cb15a9614..53d27d9ae 100644 --- a/private/apigen/common.go +++ b/private/apigen/common.go @@ -62,6 +62,15 @@ func (a *API) Group(name, prefix string) *EndpointGroup { ) } + for _, g := range a.EndpointGroups { + if strings.EqualFold(g.Name, name) { + panic(fmt.Sprintf("name has to be case-insensitive unique across all the groups. name=%q", name)) + } + if strings.EqualFold(g.Prefix, prefix) { + panic(fmt.Sprintf("prefix has to be case-insensitive unique across all the groups. prefix=%q", prefix)) + } + } + group := &EndpointGroup{ Name: name, Prefix: prefix, diff --git a/private/apigen/common_test.go b/private/apigen/common_test.go index 14b48c2d9..3d0778c53 100644 --- a/private/apigen/common_test.go +++ b/private/apigen/common_test.go @@ -83,4 +83,36 @@ func TestAPI_Group(t *testing.T) { api.Group("testname", "t_name") }) }) + + t.Run("group with repeated name", func(t *testing.T) { + api := API{} + + require.NotPanics(t, func() { + api.Group("testName", "tName") + }) + + require.Panics(t, func() { + api.Group("TESTNAME", "tName2") + }) + + require.Panics(t, func() { + api.Group("testname", "tName3") + }) + }) + + t.Run("group with repeated prefix", func(t *testing.T) { + api := API{} + + require.NotPanics(t, func() { + api.Group("testName", "tName") + }) + + require.Panics(t, func() { + api.Group("testName2", "tname") + }) + + require.Panics(t, func() { + api.Group("testname3", "tnamE") + }) + }) } diff --git a/private/apigen/endpoint.go b/private/apigen/endpoint.go index 860aa7220..29df2db81 100644 --- a/private/apigen/endpoint.go +++ b/private/apigen/endpoint.go @@ -122,7 +122,9 @@ type fullEndpoint struct { } // requestType guarantees to return a named Go type associated to the Endpoint.Request field. -func (fe fullEndpoint) requestType() reflect.Type { +// g is used to avoid clashes with types defined in different groups that are different, but with +// the same name. It cannot be nil. +func (fe fullEndpoint) requestType(g *EndpointGroup) reflect.Type { t := reflect.TypeOf(fe.Request) if t.Name() != "" { return t @@ -131,10 +133,10 @@ func (fe fullEndpoint) requestType() reflect.Type { switch k := t.Kind(); k { case reflect.Array, reflect.Slice: if t.Elem().Name() == "" { - t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Request")} + t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Request")} } case reflect.Struct: - t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Request")} + t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Request")} default: panic( fmt.Sprintf( @@ -148,7 +150,9 @@ func (fe fullEndpoint) requestType() reflect.Type { } // responseType guarantees to return a named Go type associated to the Endpoint.Response field. -func (fe fullEndpoint) responseType() reflect.Type { +// g is used to avoid clashes with types defined in different groups that are different, but with +// the same name. It cannot be nil. +func (fe fullEndpoint) responseType(g *EndpointGroup) reflect.Type { t := reflect.TypeOf(fe.Response) if t.Name() != "" { return t @@ -157,10 +161,10 @@ func (fe fullEndpoint) responseType() reflect.Type { switch k := t.Kind(); k { case reflect.Array, reflect.Slice: if t.Elem().Name() == "" { - t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Response")} + t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Response")} } case reflect.Struct: - t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Response")} + t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Response")} default: panic( fmt.Sprintf( diff --git a/private/apigen/example/api.gen.go b/private/apigen/example/api.gen.go index 770d1ddd9..600ef685f 100644 --- a/private/apigen/example/api.gen.go +++ b/private/apigen/example/api.gen.go @@ -22,6 +22,7 @@ import ( const dateLayout = "2006-01-02T15:04:05.999Z" var ErrDocsAPI = errs.Class("example docs api") +var ErrUsersAPI = errs.Class("example users api") type DocumentsService interface { Get(ctx context.Context) ([]struct { @@ -47,6 +48,14 @@ type DocumentsService interface { }, api.HTTPError) } +type UsersService interface { + Get(ctx context.Context) ([]struct { + Name string "json:\"name\"" + Surname string "json:\"surname\"" + Email string "json:\"email\"" + }, api.HTTPError) +} + // DocumentsHandler is an api handler that implements all Documents API endpoints functionality. type DocumentsHandler struct { log *zap.Logger @@ -55,6 +64,14 @@ type DocumentsHandler struct { auth api.Auth } +// UsersHandler is an api handler that implements all Users API endpoints functionality. +type UsersHandler struct { + log *zap.Logger + mon *monkit.Scope + service UsersService + auth api.Auth +} + func NewDocuments(log *zap.Logger, mon *monkit.Scope, service DocumentsService, router *mux.Router, auth api.Auth) *DocumentsHandler { handler := &DocumentsHandler{ log: log, @@ -73,6 +90,20 @@ func NewDocuments(log *zap.Logger, mon *monkit.Scope, service DocumentsService, return handler } +func NewUsers(log *zap.Logger, mon *monkit.Scope, service UsersService, router *mux.Router, auth api.Auth) *UsersHandler { + handler := &UsersHandler{ + log: log, + mon: mon, + service: service, + auth: auth, + } + + usersRouter := router.PathPrefix("/api/v0/users").Subrouter() + usersRouter.HandleFunc("/", handler.handleGet).Methods("GET") + + return handler +} + func (h *DocumentsHandler) handleGet(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -264,3 +295,29 @@ func (h *DocumentsHandler) handleUpdateContent(w http.ResponseWriter, r *http.Re h.log.Debug("failed to write json UpdateContent response", zap.Error(ErrDocsAPI.Wrap(err))) } } + +func (h *UsersHandler) handleGet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer h.mon.Task()(&ctx)(&err) + + w.Header().Set("Content-Type", "application/json") + + ctx, err = h.auth.IsAuthenticated(ctx, r, true, true) + if err != nil { + h.auth.RemoveAuthCookie(w) + api.ServeError(h.log, w, http.StatusUnauthorized, err) + return + } + + retVal, httpErr := h.service.Get(ctx) + if httpErr.Err != nil { + api.ServeError(h.log, w, httpErr.Status, httpErr.Err) + return + } + + err = json.NewEncoder(w).Encode(retVal) + if err != nil { + h.log.Debug("failed to write json Get response", zap.Error(ErrUsersAPI.Wrap(err))) + } +} diff --git a/private/apigen/example/apidocs.gen.md b/private/apigen/example/apidocs.gen.md index 22d9ac873..4566b3805 100644 --- a/private/apigen/example/apidocs.gen.md +++ b/private/apigen/example/apidocs.gen.md @@ -12,6 +12,8 @@ * [Get a tag](#documents-get-a-tag) * [Get Version](#documents-get-version) * [Update Content](#documents-update-content) +* Users + * [Get Users](#users-get-users)