Skip to content

Commit

Permalink
✨ Add support: pagination, sorting, YAML bindings. (#293)
Browse files Browse the repository at this point in the history
POST/PUT/PATCH body format _bind_ based on Content-Type (header)
supporting both JSON and YAML.
GET body format rendered based on Accept (header) supporting both JSON
and YAML. Defaults to JSON. Other MIMEs specified will be coerced to
JSON.

Improves support for `Accept` header matching.

[1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

Signed-off-by: Jeff Ortel <jortel@redhat.com>
  • Loading branch information
jortel committed Apr 12, 2023
1 parent 9f34c15 commit 1732757
Show file tree
Hide file tree
Showing 32 changed files with 393 additions and 177 deletions.
23 changes: 9 additions & 14 deletions addon/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin/binding"
liberr "github.com/konveyor/controller/pkg/error"
"github.com/konveyor/tackle2-hub/api"
"io"
Expand All @@ -22,12 +23,6 @@ import (
"time"
)

const (
Accept = api.Accept
AppJson = api.AppJson
AppOctet = api.AppOctet
)

const (
RetryLimit = 60
RetryDelay = time.Second * 10
Expand Down Expand Up @@ -112,7 +107,7 @@ func (r *Client) Get(path string, object interface{}, params ...Param) (err erro
Method: http.MethodGet,
URL: r.join(path),
}
request.Header.Set(Accept, AppJson)
request.Header.Set(api.Accept, binding.MIMEJSON)
if len(params) > 0 {
q := request.URL.Query()
for _, p := range params {
Expand Down Expand Up @@ -168,7 +163,7 @@ func (r *Client) Post(path string, object interface{}) (err error) {
Body: io.NopCloser(reader),
URL: r.join(path),
}
request.Header.Set(Accept, AppJson)
request.Header.Set(api.Accept, binding.MIMEJSON)
return
}
reply, err := r.send(request)
Expand Down Expand Up @@ -215,7 +210,7 @@ func (r *Client) Put(path string, object interface{}, params ...Param) (err erro
Body: io.NopCloser(reader),
URL: r.join(path),
}
request.Header.Set(Accept, AppJson)
request.Header.Set(api.Accept, binding.MIMEJSON)
if len(params) > 0 {
q := request.URL.Query()
for _, p := range params {
Expand Down Expand Up @@ -263,7 +258,7 @@ func (r *Client) Delete(path string, params ...Param) (err error) {
Method: http.MethodDelete,
URL: r.join(path),
}
request.Header.Set(Accept, "")
request.Header.Set(api.Accept, binding.MIMEJSON)
if len(params) > 0 {
q := request.URL.Query()
for _, p := range params {
Expand Down Expand Up @@ -303,7 +298,7 @@ func (r *Client) BucketGet(source, destination string) (err error) {
Method: http.MethodGet,
URL: r.join(source),
}
request.Header.Set(Accept, AppOctet)
request.Header.Set(api.Accept, api.MIMEOCTETSTREAM)
return
}
reply, err := r.send(request)
Expand Down Expand Up @@ -347,7 +342,7 @@ func (r *Client) BucketPut(source, destination string) (err error) {
Body: io.NopCloser(buf),
URL: r.join(destination),
}
request.Header.Set(Accept, AppOctet)
request.Header.Set(api.Accept, api.MIMEOCTETSTREAM)
writer := multipart.NewWriter(buf)
defer func() {
_ = writer.Close()
Expand Down Expand Up @@ -395,7 +390,7 @@ func (r *Client) FileGet(path, destination string) (err error) {
Method: http.MethodGet,
URL: r.join(path),
}
request.Header.Set(Accept, AppOctet)
request.Header.Set(api.Accept, api.MIMEOCTETSTREAM)
return
}
reply, err := r.send(request)
Expand Down Expand Up @@ -439,7 +434,7 @@ func (r *Client) FilePut(path, source string, object interface{}) (err error) {
Body: io.NopCloser(buf),
URL: r.join(path),
}
request.Header.Set(Accept, AppJson)
request.Header.Set(api.Accept, binding.MIMEJSON)
writer := multipart.NewWriter(buf)
defer func() {
_ = writer.Close()
Expand Down
4 changes: 2 additions & 2 deletions api/addon.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (h AddonHandler) Get(ctx *gin.Context) {
r := Addon{}
r.With(addon)

ctx.JSON(http.StatusOK, r)
h.Render(ctx, http.StatusOK, r)
}

// List godoc
Expand Down Expand Up @@ -91,7 +91,7 @@ func (h AddonHandler) List(ctx *gin.Context) {
content = append(content, addon)
}

ctx.JSON(http.StatusOK, content)
h.Render(ctx, http.StatusOK, content)
}

//
Expand Down
6 changes: 3 additions & 3 deletions api/adoptionplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (h AdoptionPlanHandler) Graph(ctx *gin.Context) {
ApplicationID uint `json:"applicationId"`
}

err := ctx.BindJSON(&requestedApps)
err := h.Bind(ctx, &requestedApps)
if err != nil {
_ = ctx.Error(err)
}
Expand Down Expand Up @@ -99,15 +99,15 @@ func (h AdoptionPlanHandler) Graph(ctx *gin.Context) {

sorted, ok := graph.TopologicalSort()
if !ok {
ctx.JSON(
h.Render(ctx,
http.StatusBadRequest,
gin.H{
"error": "dependency cycle detected",
})
return
}

ctx.JSON(http.StatusOK, sorted)
h.Render(ctx, http.StatusOK, sorted)
}

//
Expand Down
59 changes: 59 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/onsi/gomega"
"net/http"
"testing"
)

func TestAccepted(t *testing.T) {
g := gomega.NewGomegaWithT(t)
h := BaseHandler{}
ctx := &gin.Context{
Request: &http.Request{
Header: http.Header{},
},
}
// plain
ctx.Request.Header[Accept] = []string{"a/b"}
g.Expect(h.Accepted(ctx, "a/b")).To(gomega.BeTrue())
ctx.Request.Header[Accept] = []string{"a/b"}
g.Expect(h.Accepted(ctx, "x/y")).To(gomega.BeFalse())
// Empty.
ctx.Request.Header[Accept] = []string{""}
g.Expect(h.Accepted(ctx, "x/y")).To(gomega.BeFalse())
ctx.Request.Header[Accept] = []string{""}
g.Expect(h.Accepted(ctx, "")).To(gomega.BeFalse())
ctx.Request.Header[Accept] = []string{"a/b"}
g.Expect(h.Accepted(ctx, "")).To(gomega.BeFalse())
// Multiple with spaces.
ctx.Request.Header[Accept] = []string{"x/y, a/b, c/d"}
g.Expect(h.Accepted(ctx, "a/b")).To(gomega.BeTrue())
// Multiple and parameters.
ctx.Request.Header[Accept] = []string{"x/y,a/b;q=1.0"}
g.Expect(h.Accepted(ctx, "a/b")).To(gomega.BeTrue())
// wildcards
ctx.Request.Header[Accept] = []string{"*/b"}
g.Expect(h.Accepted(ctx, "a/b")).To(gomega.BeTrue())
ctx.Request.Header[Accept] = []string{"*/b"}
g.Expect(h.Accepted(ctx, "a/b")).To(gomega.BeTrue())
ctx.Request.Header[Accept] = []string{"*/*"}
g.Expect(h.Accepted(ctx, "a/b")).To(gomega.BeTrue())
}

func TestAcceptedAny(t *testing.T) {
g := gomega.NewGomegaWithT(t)
h := BaseHandler{}
ctx := &gin.Context{
Request: &http.Request{
Header: http.Header{},
},
}
ctx.Request.Header[Accept] = []string{"a/b"}
g.Expect(h.Accepted(ctx, "a/b", "c/d")).To(gomega.BeTrue())
ctx.Request.Header[Accept] = []string{"a/b,c/d"}
g.Expect(h.Accepted(ctx, "a/b", "c/d")).To(gomega.BeTrue())
ctx.Request.Header[Accept] = []string{"a/b,c/d"}
g.Expect(h.Accepted(ctx, "e/f", "x/y")).To(gomega.BeFalse())
}
34 changes: 17 additions & 17 deletions api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (h ApplicationHandler) Get(ctx *gin.Context) {
r := Application{}
r.With(m, tags)

ctx.JSON(http.StatusOK, r)
h.Render(ctx, http.StatusOK, r)
}

// List godoc
Expand Down Expand Up @@ -141,7 +141,7 @@ func (h ApplicationHandler) List(ctx *gin.Context) {
resources = append(resources, r)
}

ctx.JSON(http.StatusOK, resources)
h.Render(ctx, http.StatusOK, resources)
}

// Create godoc
Expand All @@ -155,7 +155,7 @@ func (h ApplicationHandler) List(ctx *gin.Context) {
// @param application body api.Application true "Application data"
func (h ApplicationHandler) Create(ctx *gin.Context) {
r := &Application{}
err := ctx.BindJSON(r)
err := h.Bind(ctx, r)
if err != nil {
_ = ctx.Error(err)
return
Expand All @@ -182,7 +182,7 @@ func (h ApplicationHandler) Create(ctx *gin.Context) {

r.With(m, tags)

ctx.JSON(http.StatusCreated, r)
h.Render(ctx, http.StatusCreated, r)
}

// Delete godoc
Expand Down Expand Up @@ -224,7 +224,7 @@ func (h ApplicationHandler) Delete(ctx *gin.Context) {
// @param application body []uint true "List of id"
func (h ApplicationHandler) DeleteList(ctx *gin.Context) {
ids := []uint{}
err := ctx.BindJSON(&ids)
err := h.Bind(ctx, &ids)
if err != nil {
_ = ctx.Error(err)
return
Expand Down Expand Up @@ -259,7 +259,7 @@ func (h ApplicationHandler) DeleteList(ctx *gin.Context) {
func (h ApplicationHandler) Update(ctx *gin.Context) {
id := h.pk(ctx)
r := &Application{}
err := ctx.BindJSON(r)
err := h.Bind(ctx, r)
if err != nil {
_ = ctx.Error(err)
return
Expand Down Expand Up @@ -456,7 +456,7 @@ func (h ApplicationHandler) TagList(ctx *gin.Context) {
r.With(list[i].Tag.ID, list[i].Tag.Name, list[i].Source)
resources = append(resources, r)
}
ctx.JSON(http.StatusOK, resources)
h.Render(ctx, http.StatusOK, resources)
}

// TagAdd godoc
Expand All @@ -471,7 +471,7 @@ func (h ApplicationHandler) TagList(ctx *gin.Context) {
func (h ApplicationHandler) TagAdd(ctx *gin.Context) {
id := h.pk(ctx)
ref := &TagRef{}
err := ctx.BindJSON(ref)
err := h.Bind(ctx, ref)
if err != nil {
_ = ctx.Error(err)
return
Expand All @@ -492,7 +492,7 @@ func (h ApplicationHandler) TagAdd(ctx *gin.Context) {
_ = ctx.Error(err)
return
}
ctx.JSON(http.StatusCreated, ref)
h.Render(ctx, http.StatusCreated, ref)
}

// TagReplace godoc
Expand All @@ -508,7 +508,7 @@ func (h ApplicationHandler) TagAdd(ctx *gin.Context) {
func (h ApplicationHandler) TagReplace(ctx *gin.Context) {
id := h.pk(ctx)
refs := []TagRef{}
err := ctx.BindJSON(&refs)
err := h.Bind(ctx, &refs)
if err != nil {
_ = ctx.Error(err)
return
Expand Down Expand Up @@ -610,7 +610,7 @@ func (h ApplicationHandler) FactList(ctx *gin.Context) {
r.With(&list[i])
resources = append(resources, r)
}
ctx.JSON(http.StatusOK, resources)
h.Render(ctx, http.StatusOK, resources)
}

// FactGet godoc
Expand Down Expand Up @@ -643,7 +643,7 @@ func (h ApplicationHandler) FactGet(ctx *gin.Context) {
}
r := Fact{}
r.With(&list[0])
ctx.JSON(http.StatusOK, r)
h.Render(ctx, http.StatusOK, r)
}

// FactCreate godoc
Expand All @@ -660,7 +660,7 @@ func (h ApplicationHandler) FactGet(ctx *gin.Context) {
func (h ApplicationHandler) FactCreate(ctx *gin.Context) {
id := h.pk(ctx)
var v interface{}
err := ctx.BindJSON(&v)
err := h.Bind(ctx, &v)
if err != nil {
_ = ctx.Error(err)
return
Expand All @@ -683,7 +683,7 @@ func (h ApplicationHandler) FactCreate(ctx *gin.Context) {
}
r := &Fact{}
r.With(m)
ctx.JSON(http.StatusCreated, r)
h.Render(ctx, http.StatusCreated, r)
}

// FactPut godoc
Expand All @@ -700,7 +700,7 @@ func (h ApplicationHandler) FactCreate(ctx *gin.Context) {
func (h ApplicationHandler) FactPut(ctx *gin.Context) {
id := h.pk(ctx)
var v interface{}
err := ctx.BindJSON(&v)
err := h.Bind(ctx, &v)
if err != nil {
_ = ctx.Error(err)
return
Expand Down Expand Up @@ -786,7 +786,7 @@ func (h ApplicationHandler) StakeholdersUpdate(ctx *gin.Context) {
}

r := &Stakeholders{}
err := ctx.BindJSON(r)
err := h.Bind(ctx, r)
if err != nil {
_ = ctx.Error(err)
return
Expand Down Expand Up @@ -991,4 +991,4 @@ func (r *Stakeholders) contributors() (contributors []model.Stakeholder) {
})
}
return
}
}
6 changes: 3 additions & 3 deletions api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ func (h AuthHandler) AddRoutes(e *gin.Engine) {
// @router /auth/login [post]
func (h AuthHandler) Login(ctx *gin.Context) {
r := &Login{}
err := ctx.BindJSON(r)
err := h.Bind(ctx, r)
if err != nil {
_ = ctx.Error(err)
return
}
r.Token, err = auth.Remote.Login(r.User, r.Password)
if err != nil {
ctx.JSON(
h.Render(ctx,
http.StatusUnauthorized,
gin.H{
"error": err.Error(),
})
return
}
r.Password = "" // Clear out password from response
ctx.JSON(http.StatusCreated, r)
h.Render(ctx, http.StatusCreated, r)
}

//
Expand Down
Loading

0 comments on commit 1732757

Please sign in to comment.