swaggest / rest

Web services with OpenAPI and JSON Schema done quick in Go
https://pkg.go.dev/github.com/swaggest/rest
MIT License
362 stars 17 forks source link

Set response header X-Total-Count without to envelope result #95

Closed reyesdiego closed 1 year ago

reyesdiego commented 1 year ago

I would like to be able to return (when paginating) an array in the response body and the total count of the query in a header response X-Total-Count. But If I set a struct for the response like for instance // Declare output port type. type helloOutput struct { TotalCount int64 header:"X-Total-Count" json:"-" Data []Vehicles json:"vehicles" } I need the response to be [{ vehicle:1 },{ vehicle:2} ] and NOT { data: [{ vehicle:1 },{ vehicle:2} ] }

Is there any suggestion ?

vearutop commented 1 year ago

OpenAPI reflector already supports embeddings of maps and slices, however standard encoding/json does not (to my best knowledge), so you need to implement a custom marshaler on your output structure to overcome the limitation.

package main

import (
    "context"
    "encoding/json"
    "github.com/swaggest/rest/web"
    swgui "github.com/swaggest/swgui/v4emb"
    "github.com/swaggest/usecase"
    "log"
    "net/http"
)

type Vehicle struct {
    Name string `json:"name"`
}

type Vehicles []Vehicle

// Declare output port type.
type helloOutput struct {
    Total int `header:"X-Total-Count" json:"-"`
    Vehicles
}

func (ho helloOutput) MarshalJSON() ([]byte, error) {
    return json.Marshal(ho.Vehicles)
}

func main() {
    s := web.DefaultService()

    // Create use case interactor with references to input/output types and interaction function.
    u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *helloOutput) error {
        output.Vehicles = Vehicles{Vehicle{Name: "foo"}, Vehicle{Name: "bar"}}
        output.Total = 2

        return nil
    })

    // Add use case handler to router.
    s.Get("/hello", u)

    // Swagger UI endpoint at /docs.
    s.Docs("/docs", swgui.New)

    // Start server.
    log.Println("http://localhost:8011/docs")
    if err := http.ListenAndServe("localhost:8011", s); err != nil {
        log.Fatal(err)
    }
}
image
reyesdiego commented 1 year ago

Viacheslav ! Thank you very much. Actually it was a philosophical discussion to use or not to use an envelop for the arrays results, the fact is that I am migrating from node to Go and the frontend it using without enveloping the arrays. Anyway, I really appreciate your answer. And I'm sorry, I opened and Issue which may be it was not :)

vearutop commented 1 year ago

I personally prefer to avoid unnecessary contract changes, so if an existing API uses plain arrays, I'd rather keep them, so that clients don't have to upgrade. And no worries about opening the issue, all questions are welcome (especially this case is a bit tricky)!