Skip to content

Commit

Permalink
private/apigen: Panic types defined in main package
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ifraixedes committed Sep 26, 2023
1 parent 7ab7ac4 commit 48d7be7
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 1 deletion.
35 changes: 35 additions & 0 deletions private/apigen/example/api.gen.go
Expand Up @@ -16,13 +16,15 @@ import (

"storj.io/common/uuid"
"storj.io/storj/private/api"
"storj.io/storj/private/apigen/example/myapi"
)

const dateLayout = "2006-01-02T15:04:05.999Z"

var ErrDocsAPI = errs.Class("example docs api")

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 {
Content string "json:\"content\""
}) (*struct {
Expand Down Expand Up @@ -50,11 +52,44 @@ func NewDocuments(log *zap.Logger, mon *monkit.Scope, service DocumentsService,
}

docsRouter := router.PathPrefix("/api/v0/docs").Subrouter()
docsRouter.HandleFunc("/{path}", handler.handleGetOne).Methods("GET")
docsRouter.HandleFunc("/{path}", handler.handleUpdateContent).Methods("POST")

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) {
ctx := r.Context()
var err error
Expand Down
26 changes: 26 additions & 0 deletions private/apigen/example/apidocs.gen.md
Expand Up @@ -7,8 +7,34 @@
<h2 id='list-of-endpoints'>List of Endpoints</h2>

* Documents
* [Get One](#documents-get-one)
* [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>

Update the content of the document with the specified path and ID if the last update is before the indicated date
Expand Down
18 changes: 18 additions & 0 deletions private/apigen/example/client-api.gen.ts
Expand Up @@ -4,6 +4,14 @@
import { HttpClient } from '@/utils/httpClient';
import { Time, UUID } from '@/types/common';

export class Document {
id: UUID;
date: Time;
pathParam: string;
body: string;
version: number;
}

export class UpdateContentRequest {
content: string;
}
Expand All @@ -19,6 +27,16 @@ export class docsHttpApiV0 {
private readonly http: HttpClient = new HttpClient();
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> {
const u = new URL(`${this.ROOT_PATH}/${path}`);
u.searchParams.set('id', id);
Expand Down
11 changes: 11 additions & 0 deletions private/apigen/example/gen.go
Expand Up @@ -11,13 +11,24 @@ import (

"storj.io/common/uuid"
"storj.io/storj/private/apigen"
"storj.io/storj/private/apigen/example/myapi"
)

func main() {
a := &apigen.API{PackageName: "example", Version: "v0", BasePath: "/api"}

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{
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",
Expand Down
19 changes: 19 additions & 0 deletions private/apigen/example/myapi/types.go
@@ -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"`
}
7 changes: 6 additions & 1 deletion private/apigen/gogen.go
Expand Up @@ -42,7 +42,12 @@ func (a *API) generateGo() ([]byte, error) {

getPackageName := func(path string) string {
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 {
Expand Down
8 changes: 8 additions & 0 deletions private/apigen/gogen_test.go
Expand Up @@ -25,6 +25,7 @@ import (
"storj.io/storj/private/api"
"storj.io/storj/private/apigen"
"storj.io/storj/private/apigen/example"
"storj.io/storj/private/apigen/example/myapi"
)

type (
Expand All @@ -44,6 +45,13 @@ func (a auth) IsAuthenticated(ctx context.Context, r *http.Request, isCookieAuth

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(
ctx context.Context,
pathParam string,
Expand Down

0 comments on commit 48d7be7

Please sign in to comment.