private/apigen: Panic types defined in main package

The API generator was generating invalid code when types were defined in
a main package because the generated Go code was defining in import from
it.

This commit update the Go generator to panic with a explicit error
message if that situation happens.

The commit also add a new endpoint to the example with a named types
(i.e. no anonymous) to show that the Generator works fine with them.

Change-Id: Ieddd89c67048de50516f7ac7787d602660dc4a54
This commit is contained in:
Ivan Fraixedes 2023-09-25 14:08:33 +02:00
parent 7ab7ac49c8
commit 48d7be7eab
No known key found for this signature in database
GPG Key ID: FB6101AFB5CB5AD5
7 changed files with 123 additions and 1 deletions

View File

@ -16,6 +16,7 @@ import (
"storj.io/common/uuid" "storj.io/common/uuid"
"storj.io/storj/private/api" "storj.io/storj/private/api"
"storj.io/storj/private/apigen/example/myapi"
) )
const dateLayout = "2006-01-02T15:04:05.999Z" const dateLayout = "2006-01-02T15:04:05.999Z"
@ -23,6 +24,7 @@ const dateLayout = "2006-01-02T15:04:05.999Z"
var ErrDocsAPI = errs.Class("example docs api") var ErrDocsAPI = errs.Class("example docs api")
type DocumentsService interface { type DocumentsService interface {
GetOne(ctx context.Context, path string) (*myapi.Document, api.HTTPError)
UpdateContent(ctx context.Context, path string, id uuid.UUID, date time.Time, request struct { UpdateContent(ctx context.Context, path string, id uuid.UUID, date time.Time, request struct {
Content string "json:\"content\"" Content string "json:\"content\""
}) (*struct { }) (*struct {
@ -50,11 +52,44 @@ func NewDocuments(log *zap.Logger, mon *monkit.Scope, service DocumentsService,
} }
docsRouter := router.PathPrefix("/api/v0/docs").Subrouter() docsRouter := router.PathPrefix("/api/v0/docs").Subrouter()
docsRouter.HandleFunc("/{path}", handler.handleGetOne).Methods("GET")
docsRouter.HandleFunc("/{path}", handler.handleUpdateContent).Methods("POST") docsRouter.HandleFunc("/{path}", handler.handleUpdateContent).Methods("POST")
return handler return handler
} }
func (h *DocumentsHandler) handleGetOne(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")
path, ok := mux.Vars(r)["path"]
if !ok {
api.ServeError(h.log, w, http.StatusBadRequest, errs.New("missing path route param"))
return
}
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.GetOne(ctx, path)
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 GetOne response", zap.Error(ErrDocsAPI.Wrap(err)))
}
}
func (h *DocumentsHandler) handleUpdateContent(w http.ResponseWriter, r *http.Request) { func (h *DocumentsHandler) handleUpdateContent(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var err error var err error

View File

@ -7,8 +7,34 @@
<h2 id='list-of-endpoints'>List of Endpoints</h2> <h2 id='list-of-endpoints'>List of Endpoints</h2>
* Documents * Documents
* [Get One](#documents-get-one)
* [Update Content](#documents-update-content) * [Update Content](#documents-update-content)
<h3 id='documents-get-one'>Get One (<a href='#list-of-endpoints'>go to full list</a>)</h3>
Get one document with the specified version
`GET /api/v0/docs/{path}`
**Path Params:**
| name | type | elaboration |
|---|---|---|
| `path` | `string` | |
**Response body:**
```typescript
{
id: string // UUID formatted as `00000000-0000-0000-0000-000000000000`
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
pathParam: string
body: string
version: number
}
```
<h3 id='documents-update-content'>Update Content (<a href='#list-of-endpoints'>go to full list</a>)</h3> <h3 id='documents-update-content'>Update Content (<a href='#list-of-endpoints'>go to full list</a>)</h3>
Update the content of the document with the specified path and ID if the last update is before the indicated date Update the content of the document with the specified path and ID if the last update is before the indicated date

View File

@ -4,6 +4,14 @@
import { HttpClient } from '@/utils/httpClient'; import { HttpClient } from '@/utils/httpClient';
import { Time, UUID } from '@/types/common'; import { Time, UUID } from '@/types/common';
export class Document {
id: UUID;
date: Time;
pathParam: string;
body: string;
version: number;
}
export class UpdateContentRequest { export class UpdateContentRequest {
content: string; content: string;
} }
@ -19,6 +27,16 @@ export class docsHttpApiV0 {
private readonly http: HttpClient = new HttpClient(); private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/docs'; private readonly ROOT_PATH: string = '/api/v0/docs';
public async GetOne(path: string): Promise<Document> {
const fullPath = `${this.ROOT_PATH}/${path}`;
const response = await this.http.get(fullPath);
if (response.ok) {
return response.json().then((body) => body as Document);
}
const err = await response.json();
throw new Error(err.error);
}
public async UpdateContent(request: UpdateContentRequest, path: string, id: UUID, date: Time): Promise<UpdateContentResponse> { public async UpdateContent(request: UpdateContentRequest, path: string, id: UUID, date: Time): Promise<UpdateContentResponse> {
const u = new URL(`${this.ROOT_PATH}/${path}`); const u = new URL(`${this.ROOT_PATH}/${path}`);
u.searchParams.set('id', id); u.searchParams.set('id', id);

View File

@ -11,6 +11,7 @@ import (
"storj.io/common/uuid" "storj.io/common/uuid"
"storj.io/storj/private/apigen" "storj.io/storj/private/apigen"
"storj.io/storj/private/apigen/example/myapi"
) )
func main() { func main() {
@ -18,6 +19,16 @@ func main() {
g := a.Group("Documents", "docs") g := a.Group("Documents", "docs")
g.Get("/{path}", &apigen.Endpoint{
Name: "Get One",
Description: "Get one document with the specified version",
MethodName: "GetOne",
Response: myapi.Document{},
PathParams: []apigen.Param{
apigen.NewParam("path", ""),
},
})
g.Post("/{path}", &apigen.Endpoint{ g.Post("/{path}", &apigen.Endpoint{
Name: "Update Content", Name: "Update Content",
Description: "Update the content of the document with the specified path and ID if the last update is before the indicated date", Description: "Update the content of the document with the specified path and ID if the last update is before the indicated date",

View File

@ -0,0 +1,19 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
package myapi
import (
"time"
"storj.io/common/uuid"
)
// Document is a retrieved document.
type Document struct {
ID uuid.UUID `json:"id"`
Date time.Time `json:"date"`
PathParam string `json:"pathParam"`
Body string `json:"body"`
Version uint `json:"version"`
}

View File

@ -42,7 +42,12 @@ func (a *API) generateGo() ([]byte, error) {
getPackageName := func(path string) string { getPackageName := func(path string) string {
pathPackages := strings.Split(path, "/") pathPackages := strings.Split(path, "/")
return pathPackages[len(pathPackages)-1] name := pathPackages[len(pathPackages)-1]
if name == "main" {
panic(errs.New(`invalid package name. Your types cannot be defined in a package named "main"`))
}
return name
} }
imports := struct { imports := struct {

View File

@ -25,6 +25,7 @@ import (
"storj.io/storj/private/api" "storj.io/storj/private/api"
"storj.io/storj/private/apigen" "storj.io/storj/private/apigen"
"storj.io/storj/private/apigen/example" "storj.io/storj/private/apigen/example"
"storj.io/storj/private/apigen/example/myapi"
) )
type ( type (
@ -44,6 +45,13 @@ func (a auth) IsAuthenticated(ctx context.Context, r *http.Request, isCookieAuth
func (a auth) RemoveAuthCookie(w http.ResponseWriter) {} func (a auth) RemoveAuthCookie(w http.ResponseWriter) {}
func (s service) GetOne(
ctx context.Context,
pathParam string,
) (*myapi.Document, api.HTTPError) {
return &myapi.Document{}, api.HTTPError{}
}
func (s service) UpdateContent( func (s service) UpdateContent(
ctx context.Context, ctx context.Context,
pathParam string, pathParam string,