Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format errors as JSON-API #46

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions vfs/directory.go
Expand Up @@ -8,7 +8,8 @@ import (
"github.com/spf13/afero"
)

type dirAttributes struct {
// DirAttributes is a struct with the attributes of a directory
type DirAttributes struct {
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Expand All @@ -24,7 +25,7 @@ type DirDoc struct {
// Directory revision
DRev string `json:"_rev,omitempty"`
// Directory attributes
Attrs *dirAttributes `json:"attributes"`
Attrs *DirAttributes `json:"attributes"`
// Parent folder identifier
FolderID string `json:"folderID"`
// Directory path on VFS
Expand Down Expand Up @@ -62,10 +63,9 @@ func (d *DirDoc) SetRev(rev string) {
// ToJSONApi implements temporary interface JSONApier to serialize
// the directory document
func (d *DirDoc) ToJSONApi() ([]byte, error) {
qid := d.DID
data := map[string]interface{}{
"type": d.DocType(),
"id": qid,
"id": d.ID(),
"rev": d.Rev(),
"attributes": d.Attrs,
}
Expand All @@ -88,7 +88,7 @@ func CreateDirectory(m *DocAttributes, fs afero.Fs, dbPrefix string) (doc *DirDo
}

createDate := time.Now()
attrs := &dirAttributes{
attrs := &DirAttributes{
Name: m.name,
CreatedAt: createDate,
UpdatedAt: createDate,
Expand Down
24 changes: 0 additions & 24 deletions vfs/errors.go
Expand Up @@ -2,7 +2,6 @@ package vfs

import (
"errors"
"net/http"
"os"
)

Expand All @@ -26,26 +25,3 @@ var (
// match the calculated one
ErrContentLengthMismatch = errors.New("Content length does not match")
)

// HTTPStatus returns the HTTP status code associated to a given
// error. If the error is not part of vfs errors, the code returned is
// 0.
func HTTPStatus(err error) (code int) {
switch err {
case ErrDocAlreadyExists:
code = http.StatusConflict
case ErrDocDoesNotExist:
code = http.StatusNotFound
case ErrParentDoesNotExist:
code = http.StatusNotFound
case ErrDocTypeInvalid:
code = http.StatusUnprocessableEntity
case ErrIllegalFilename:
code = http.StatusUnprocessableEntity
case ErrInvalidHash:
code = http.StatusPreconditionFailed
case ErrContentLengthMismatch:
code = http.StatusUnprocessableEntity
}
return
}
13 changes: 7 additions & 6 deletions vfs/file.go
Expand Up @@ -14,7 +14,8 @@ import (
"github.com/spf13/afero"
)

type fileAttributes struct {
// FileAttributes is a struct with the attributes of a file
type FileAttributes struct {
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Expand All @@ -34,7 +35,7 @@ type FileDoc struct {
// File revision
FRev string `json:"_rev,omitempty"`
// File attributes
Attrs *fileAttributes `json:"attributes"`
Attrs *FileAttributes `json:"attributes"`
// Parent folder identifier
FolderID string `json:"folderID"`
// File path on VFS
Expand Down Expand Up @@ -127,17 +128,17 @@ func ServeFileContent(fileDoc *FileDoc, req *http.Request, w http.ResponseWriter
// Etag.
//
// The content disposition is attached
func ServeFileContentByPath(pth string, req *http.Request, w http.ResponseWriter, fs afero.Fs) (err error) {
func ServeFileContentByPath(pth string, req *http.Request, w http.ResponseWriter, fs afero.Fs) error {
fileInfo, err := fs.Stat(pth)
if err != nil {
return
return ErrDocDoesNotExist
}

name := path.Base(pth)
w.Header().Set("Content-Disposition", "attachment; filename="+name)

serveContent(req, w, fs, pth, name, fileInfo.ModTime())
return
return nil
}

func serveContent(req *http.Request, w http.ResponseWriter, fs afero.Fs, pth, name string, modtime time.Time) (err error) {
Expand All @@ -164,7 +165,7 @@ func CreateFileAndUpload(m *DocAttributes, fs afero.Fs, dbPrefix string, body io
}

createDate := time.Now()
attrs := &fileAttributes{
attrs := &FileAttributes{
Name: m.name,
CreatedAt: createDate,
UpdatedAt: createDate,
Expand Down
11 changes: 5 additions & 6 deletions web/data/data.go
Expand Up @@ -6,7 +6,6 @@ import (
"net/http"

"github.com/cozy/cozy-stack/couchdb"
"github.com/cozy/cozy-stack/web/errors"
"github.com/cozy/cozy-stack/web/middlewares"
"github.com/gin-gonic/gin"
)
Expand All @@ -15,7 +14,7 @@ func validDoctype(c *gin.Context) {
// TODO extends me to verificate characters allowed in db name.
doctype := c.Param("doctype")
if doctype == "" {
c.AbortWithError(http.StatusBadRequest, errors.InvalidDoctype(doctype))
c.AbortWithError(http.StatusBadRequest, invalidDoctypeErr(doctype))
} else {
c.Set("doctype", doctype)
}
Expand All @@ -32,7 +31,7 @@ func getDoc(c *gin.Context) {
var out couchdb.JSONDoc
err := couchdb.GetDoc(prefix, doctype, docid, &out)
if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
c.AbortWithError(HTTPStatus(err), err)
return
}
out.Type = doctype
Expand All @@ -53,7 +52,7 @@ func createDoc(c *gin.Context) {

err := couchdb.CreateDoc(prefix, doc)
if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
c.AbortWithError(HTTPStatus(err), err)
return
}

Expand Down Expand Up @@ -91,7 +90,7 @@ func updateDoc(c *gin.Context) {

err := couchdb.UpdateDoc(prefix, doc)
if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
c.AbortWithError(HTTPStatus(err), err)
return
}

Expand Down Expand Up @@ -119,7 +118,7 @@ func deleteDoc(c *gin.Context) {

tombrev, err := couchdb.Delete(prefix, doctype, docid, rev)
if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
c.AbortWithError(HTTPStatus(err), err)
return
}

Expand Down
8 changes: 4 additions & 4 deletions web/data/data_test.go
Expand Up @@ -11,7 +11,6 @@ import (
"os"
"testing"

"github.com/cozy/cozy-stack/web/errors"
"github.com/cozy/cozy-stack/web/middlewares"
"github.com/gin-gonic/gin"
"github.com/sourcegraph/checkup"
Expand Down Expand Up @@ -79,20 +78,21 @@ func injectInstance(instance *middlewares.Instance) gin.HandlerFunc {
}

func TestMain(m *testing.M) {

// First we make sure couchdb is started
couchdb, err := checkup.HTTPChecker{URL: CouchURL}.Check()
if err != nil || couchdb.Status() != checkup.Healthy {
fmt.Println("This test need couchdb to run.")
os.Exit(1)
}

router := gin.New()
gin.SetMode(gin.TestMode)
instance := &middlewares.Instance{
Domain: Host,
StorageURL: "mem://test",
}
router.Use(errors.Handler())

router := gin.New()
router.Use(middlewares.ErrorHandler())
router.Use(injectInstance(instance))
Routes(router.Group("/data"))
ts = httptest.NewServer(router)
Expand Down
30 changes: 30 additions & 0 deletions web/data/errors.go
@@ -0,0 +1,30 @@
package data

import (
"fmt"
"net/http"
"os"

"github.com/cozy/cozy-stack/couchdb"
)

// HTTPStatus gives the http status for given error
func HTTPStatus(err error) (code int) {
if os.IsNotExist(err) {
code = http.StatusNotFound
} else if os.IsExist(err) {
code = http.StatusConflict
} else if couchErr, isCouchErr := err.(*couchdb.Error); isCouchErr {
code = couchErr.StatusCode
}

if code == 0 {
code = http.StatusInternalServerError
}

return
}

func invalidDoctypeErr(doctype string) error {
return fmt.Errorf("Invalid doctype '%s'", doctype)
}
63 changes: 0 additions & 63 deletions web/errors/errors.go

This file was deleted.

17 changes: 8 additions & 9 deletions web/files/files.go
Expand Up @@ -11,7 +11,6 @@ import (
"strings"

"github.com/cozy/cozy-stack/vfs"
"github.com/cozy/cozy-stack/web/errors"
"github.com/cozy/cozy-stack/web/jsonapi"
"github.com/cozy/cozy-stack/web/middlewares"
"github.com/gin-gonic/gin"
Expand All @@ -31,7 +30,7 @@ func CreationHandler(c *gin.Context) {
dbPrefix := instance.GetDatabasePrefix()
storage, err := instance.GetStorageProvider()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
jsonapi.AbortWithError(c, jsonapi.InternalServerError(err))
return
}

Expand All @@ -44,13 +43,13 @@ func CreationHandler(c *gin.Context) {
givenMD5, err = parseMD5Hash(md5Str)
}
if err != nil {
c.AbortWithError(http.StatusUnprocessableEntity, err)
jsonapi.AbortWithError(c, jsonapi.InvalidParameter("Content-MD5", err))
return
}

size, err := parseContentLength(header.Get("Content-Length"))
if err != nil {
c.AbortWithError(http.StatusUnprocessableEntity, err)
jsonapi.AbortWithError(c, jsonapi.InvalidParameter("Content-Length", err))
return
}

Expand All @@ -67,7 +66,7 @@ func CreationHandler(c *gin.Context) {
)

if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
jsonapi.AbortWithError(c, jsonapi.WrapVfsError(err))
return
}

Expand All @@ -80,13 +79,13 @@ func CreationHandler(c *gin.Context) {
}

if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
jsonapi.AbortWithError(c, jsonapi.WrapVfsError(err))
return
}

data, err := doc.ToJSONApi()
if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
jsonapi.AbortWithError(c, jsonapi.WrapVfsError(err))
return
}

Expand All @@ -108,7 +107,7 @@ func ReadHandler(c *gin.Context) {
dbPrefix := instance.GetDatabasePrefix()
storage, err := instance.GetStorageProvider()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
jsonapi.AbortWithError(c, jsonapi.InternalServerError(err))
return
}

Expand All @@ -128,7 +127,7 @@ func ReadHandler(c *gin.Context) {
}

if err != nil {
c.AbortWithError(errors.HTTPStatus(err), err)
jsonapi.AbortWithError(c, jsonapi.WrapVfsError(err))
return
}
}
Expand Down