Skip to content

Commit

Permalink
feat: implement JSON Patch for Software resource
Browse files Browse the repository at this point in the history
  • Loading branch information
bfabio committed Dec 5, 2023
1 parent 62a3bc6 commit 5096568
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 11 deletions.
39 changes: 28 additions & 11 deletions internal/handlers/software.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ type Software struct {
}

var (
errLoadNotFound = errors.New("Software was not found")
errLoad = errors.New("error while loading Software")
errLoadNotFound = errors.New("Software was not found")
errLoad = errors.New("error while loading Software")
errMalformedJsonPatch = errors.New("malformed JSON Patch")

Check warning on line 35 in internal/handlers/software.go

View workflow job for this annotation

GitHub Actions / linters

var-naming: var errMalformedJsonPatch should be errMalformedJSONPatch (revive)
)

func NewSoftware(db *gorm.DB) *Software {
Expand Down Expand Up @@ -174,7 +175,6 @@ func (p *Software) PostSoftware(ctx *fiber.Ctx) error {
func (p *Software) PatchSoftware(ctx *fiber.Ctx) error { //nolint:funlen,cyclop
const errMsg = "can't update Software"

softwareReq := common.SoftwarePatch{}
software := models.Software{}

if err := loadSoftware(p.db, &software, ctx.Params("id")); err != nil {
Expand All @@ -185,20 +185,37 @@ func (p *Software) PatchSoftware(ctx *fiber.Ctx) error { //nolint:funlen,cyclop
return common.Error(fiber.StatusInternalServerError, errMsg, err.Error())
}

if err := common.ValidateRequestEntity(ctx, &softwareReq, errMsg); err != nil {
return err //nolint:wrapcheck
}

softwareJSON, err := json.Marshal(&software)
if err != nil {
return common.Error(fiber.StatusInternalServerError, errMsg, err.Error())
}

updatedJSON, err := jsonpatch.MergePatch(softwareJSON, ctx.Body())
if err != nil {
return common.Error(fiber.StatusInternalServerError, errMsg, err.Error())
}
var updatedJSON []byte
switch ctx.Get(fiber.HeaderContentType) {

Check failure on line 194 in internal/handlers/software.go

View workflow job for this annotation

GitHub Actions / linters

switch statements should only be cuddled with variables switched (wsl)
case "application/json-patch+json":
patch, err := jsonpatch.DecodePatch(ctx.Body())
if err != nil {
return common.Error(fiber.StatusBadRequest, errMsg, errMalformedJsonPatch.Error())
}

updatedJSON, err = patch.Apply(softwareJSON)
if err != nil {
return common.Error(fiber.StatusUnprocessableEntity, errMsg, err.Error())
}

// application/merge-patch+json by default
default:
softwareReq := common.SoftwarePatch{}
if err := common.ValidateRequestEntity(ctx, &softwareReq, errMsg); err != nil {
return err //nolint:wrapcheck
}

updatedJSON, err = jsonpatch.MergePatch(softwareJSON, ctx.Body())
if err != nil {
return common.Error(fiber.StatusInternalServerError, errMsg, err.Error())
}
}

Check failure on line 218 in internal/handlers/software.go

View workflow job for this annotation

GitHub Actions / linters

File is not `gofmt`-ed with `-s` (gofmt)
var updatedSoftware models.Software

err = json.Unmarshal(updatedJSON, &updatedSoftware)
Expand Down
85 changes: 85 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,91 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Greater(t, updated, created)
},
},
{
description: "PATCH a software resource with JSON Patch - replace",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
body: `[{"op": "replace", "path": "/publiccodeYml", "value": "new publiccode data"}]`,
headers: map[string][]string{
"Authorization": {goodToken},
"Content-Type": {"application/json-patch+json"},
},

expectedCode: 200,
expectedContentType: "application/json",
validateFunc: func(t *testing.T, response map[string]interface{}) {
assert.Equal(t, true, response["active"])
assert.Equal(t, "https://18-a.example.org/code/repo", response["url"])

assert.IsType(t, []interface{}{}, response["aliases"])

aliases := response["aliases"].([]interface{})
assert.Equal(t, 1, len(aliases))

assert.Equal(t, "https://18-b.example.org/code/repo", aliases[0])

assert.Equal(t, "new publiccode data", response["publiccodeYml"])
assert.Equal(t, "59803fb7-8eec-4fe5-a354-8926009c364a", response["id"])

created, err := time.Parse(time.RFC3339, response["createdAt"].(string))
assert.Nil(t, err)

updated, err := time.Parse(time.RFC3339, response["updatedAt"].(string))
assert.Nil(t, err)

assert.Greater(t, updated, created)
},
},
// {
// description: "PATCH a software resource with JSON Patch - add",
// query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
// body: `[{"op": "add", "path": "/aliases", "value": "https://18-c.example.org"]`,
// headers: map[string][]string{
// "Authorization": {goodToken},
// "Content-Type": {"application/json-patch+json"},
// },

// expectedCode: 200,
// expectedContentType: "application/json",
// validateFunc: func(t *testing.T, response map[string]interface{}) {
// assert.Equal(t, true, response["active"])
// assert.Equal(t, "https://18-a.example.org/code/repo", response["url"])

// assert.IsType(t, []interface{}{}, response["aliases"])

// aliases := response["aliases"].([]interface{})
// assert.Equal(t, 2, len(aliases))

// assert.Equal(t, "https://18-b.example.org/code/repo", aliases[0])
// assert.Equal(t, "https://18-c.example.org/code/repo", aliases[1])

// assert.Equal(t, "publiccodedata", response["publiccodeYml"])
// assert.Equal(t, "59803fb7-8eec-4fe5-a354-8926009c364a", response["id"])

// created, err := time.Parse(time.RFC3339, response["createdAt"].(string))
// assert.Nil(t, err)

// updated, err := time.Parse(time.RFC3339, response["updatedAt"].(string))
// assert.Nil(t, err)

// assert.Greater(t, updated, created)
// },
// },
{
description: "PATCH a software resource with JSON Patch as Content-Type, but non JSON Patch payload",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
body: `{"publiccodeYml": "publiccodedata", "url": "https://software-new.example.org"}`,
headers: map[string][]string{
"Authorization": {goodToken},
"Content-Type": {"application/json-patch+json"},
},

expectedCode: 400,
expectedContentType: "application/problem+json",
validateFunc: func(t *testing.T, response map[string]interface{}) {
assert.Equal(t, `can't update Software`, response["title"])
assert.Equal(t, "malformed JSON Patch", response["detail"])
},
},
{
description: "PATCH software using an already taken URL as url",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
Expand Down

0 comments on commit 5096568

Please sign in to comment.