google / jsonapi

jsonapi.org style payload serializer and deserializer
http://godoc.org/github.com/google/jsonapi
MIT License
1.42k stars 210 forks source link

support top-level error documents #3

Open chr0n1x opened 9 years ago

chr0n1x commented 9 years ago

Couldn't find anything in the code that did this, sorry if I'm misreading stuff. I think the specs are describing something like:

{
    errors: [
        ...
    ]
}

??

shwoodard commented 9 years ago

@chr0n1x working on links, meta, and errors atm

chr0n1x commented 9 years ago

:+1: :smile:

chr0n1x commented 9 years ago

@shwoodard do you have an ETA? Or need help? Both?

shwoodard commented 9 years ago

@chr0n1x I have been working hard on how to specify the links hashes throughout the response. Are the errors objects more urgent to you? I could work on these first.

chr0n1x commented 9 years ago

actually, paginated results / links are a big deal. keep at it! :+1:

shwoodard commented 9 years ago

@chr0n1x Can you help test and document?

wolfie82 commented 9 years ago

Looking forward to this as well :smile:

Thanks for the library!

swr commented 9 years ago

Checking in to see if you need help testing/documenting links.

swr commented 9 years ago

@shwoodard Still looking for help? Tried your json_extras branch and looks like you are close.

shwoodard commented 9 years ago

@swr would love some help! ping me if still interested.!

swr commented 9 years ago

@shwoodard Should have some bandwidth this week.

elithrar commented 9 years ago

Given the JSON API specification surrounding errors (http://jsonapi.org/format/#errors) - noting that all fields are optional - is there a rough idea on how this would be implemented?

Examples in the spec are here: http://jsonapi.org/examples/#error-objects-basics - the major challenges I see are automating the source / pointer detail - other fields like status / code / title would typically be driven by the package user's application.

{
  "errors": [
    {
      "status": "422",
      "source": { "pointer": "/data/attributes/first-name" },
      "title":  "Invalid Attribute",
      "detail": "First name must contain at least three characters."
    }
  ]
}
chr0n1x commented 9 years ago

@elithrar I would say that sticking to the spec and just doing a recursive reflect on struct fields & values would be enough for a first rev. I believe that anything more should be left to the user, as the spec seems to imply.

chr0n1x commented 9 years ago

@shwoodard sorry about the lack of response, my schedule has been fairly packed :(

ariejan commented 9 years ago

Ola, another Gopher weighing in. What can I do to get this into master?

jixwanwang commented 8 years ago

Any updates on this?

ippy04 commented 8 years ago

:+1:

shwoodard commented 8 years ago

Commencing work on this right after supporting embedded structs.

rishirajdev commented 8 years ago

what will be the reason for jsonapi to through EOF err jsonapi.UnMarshalPayload(c.Request.Body,user) is returning EOF

c.Request.Body contains the json which is marshalled using jsonapi

Please help me with this

elithrar commented 8 years ago

Show your code. You will typically see an EOF when you are trying to Unmarshal an empty body. On Wed, May 4, 2016 at 3:36 AM Rishiraj Shengule notifications@github.com wrote:

what will be the reason for jsonapi to through EOF err jsonapi.UnMarshalPayload() is returning EOF

Please help me with this

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/shwoodard/jsonapi/issues/3#issuecomment-216824647

rishirajdev commented 8 years ago

Thanks for your reply Below is the request I am sending (marshalled JSON using JSONAPI)

{
    "data": {
        "type": "users",
        "id": "1",
        "attributes": {
            "company": "Torant",
            "contact": "12345678",
            "description": "hdgfsdfg",
            "email": "abc@abc.com",
            "first-name": "ABC",
            "job-title": "xyz",
            "last-name": "DEF",
            "secContact": "434545665",
            "username": "abc123",
            "yearsOfExp": "5"
        },
        "relationships": {
            "address": {
                "data": [{
                    "type": "address",
                    "id": "2"
                }]
            }
        }
    },
    "included": [{
        "type": "address",
        "id": "2",
        "attributes": {
            "city": "PU",
            "country": "INDIA",
            "state": "MH",
            "street": "ahbfshdf",
            "zipcode": "12345"
        }
    }]
}

Below is the struct that I am using in my code:

type Address struct {
    Id      string `jsonapi:"primary,address"`
    Country string `jsonapi:"attr,country" `
    State   string `jsonapi:"attr,state" `
    City    string `jsonapi:"attr,city" `
    Street  string `jsonapi:"attr,street" `
    Zipcode string `jsonapi:"attr,zipcode"`
}

type UserVO struct {
    ID               string     `jsonapi:"primary,users"`
    FirstName        string     `jsonapi:"attr,first-name"`
    LastName         string     `jsonapi:"attr,last-name"`
    Username         string     `jsonapi:"attr,username"`
    Contact          string     `jsonapi:"attr,contact"`
    SecondaryContact string     `jsonapi:"attr,secContact"`
    Email            string     `jsonapi:"attr,email"`
    Company          string     `jsonapi:"attr,company"`
    Jobtitle         string     `jsonapi:"attr,job-title"`
    YearsOfExp       string     `jsonapi:"attr,yearsOfExp"`
    Description      string     `jsonapi:"attr,description"`
    Address          []*Address `jsonapi:"relation,address"`
}

and here is the function:

func (c Users) Register() revel.Result {
    user := new(models.UserVO)
    user.ID = "12345"

    buf := bytes.NewBuffer(nil)

    io.Copy(buf, c.Request.Body)

    fmt.Println(buf.String())

    if err := jsonapi.UnmarshalPayload(c.Request.Body, user); err != nil {
        // will be replace by JSONAPI
        c.Response.Status = http.StatusExpectationFailed
        fmt.Println(err)
        return c.RenderText("could not parse request")

    }
    c.Response.Status = http.StatusCreated
    return c.RenderJson(user)
}
aren55555 commented 8 years ago

@rishirajdev I can't seem to replicate your error; here is the go file I'm working with:

package issue

import (
    "bytes"
    "testing"

    "github.com/shwoodard/jsonapi"
)

type Address struct {
    Id      string `jsonapi:"primary,address"`
    Country string `jsonapi:"attr,country" `
    State   string `jsonapi:"attr,state" `
    City    string `jsonapi:"attr,city" `
    Street  string `jsonapi:"attr,street" `
    Zipcode string `jsonapi:"attr,zipcode"`
}

type UserVO struct {
    ID               string     `jsonapi:"primary,users"`
    FirstName        string     `jsonapi:"attr,first-name"`
    LastName         string     `jsonapi:"attr,last-name"`
    Username         string     `jsonapi:"attr,username"`
    Contact          string     `jsonapi:"attr,contact"`
    SecondaryContact string     `jsonapi:"attr,secContact"`
    Email            string     `jsonapi:"attr,email"`
    Company          string     `jsonapi:"attr,company"`
    Jobtitle         string     `jsonapi:"attr,job-title"`
    YearsOfExp       string     `jsonapi:"attr,yearsOfExp"`
    Description      string     `jsonapi:"attr,description"`
    Address          []*Address `jsonapi:"relation,address"`
}

func TestPayload(t *testing.T) {
    payload := bytes.NewBufferString(`
  {
      "data": {
          "type": "users",
          "id": "1",
          "attributes": {
              "company": "Torant",
              "contact": "12345678",
              "description": "hdgfsdfg",
              "email": "abc@abc.com",
              "first-name": "ABC",
              "job-title": "xyz",
              "last-name": "DEF",
              "secContact": "434545665",
              "username": "abc123",
              "yearsOfExp": "5"
          },
          "relationships": {
              "address": {
                  "data": [{
                      "type": "address",
                      "id": "2"
                  }]
              }
          }
      },
      "included": [{
          "type": "address",
          "id": "2",
          "attributes": {
              "city": "PU",
              "country": "INDIA",
              "state": "MH",
              "street": "ahbfshdf",
              "zipcode": "12345"
          }
      }]
  }`)

    user := &UserVO{}
    if err := jsonapi.UnmarshalPayload(payload, user); err != nil {
        t.Fatal(err)
    }

    // Random Validations
    if user.ID != "1" || user.Company != "Torant" || user.Address[0].Id != "2" || user.Address[0].Country != "INDIA" {
        t.Fatal("Fields in unmarshaled payload were not as expected")
    }
}
ginty commented 8 years ago

See #52 for a proposed update if interested in pagination

svperfecta commented 7 years ago

Hey dudes - #57 and #58

svperfecta commented 7 years ago

Now that we're on track for Links (wahoo!)

Regarding Error objects.. Perhaps the right way to handle this would be something like MarshalManyError or MarshalOneError. This example (from examples/app.go) then goes from this:

if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
    http.Error(w, err.Error(), 500)
}

To this:

if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
    jsonapiRuntime.MarshalOneError(w, err.Error(), 500)
}

Which would produce:

Status: 500

{
    errors: [{ title: "Some error occured"}]
}

Something like that?

JSONAPI error responses are always arrays (at least, as I understand it).

thedodd commented 7 years ago

You are correct, @genexp. According to the spec, errors must always be an array of error objects:

A document MUST contain at least one of the following top-level members:

  • data: the document’s “primary data”
  • errors: an array of error objects
  • meta: a meta object that contains non-standard meta-information.

The members data and errors MUST NOT coexist in the same document.

I would say that, at a minimum, we should include the title field in the error, as well as the detail field. The spec describes pretty well what the difference between these two fields should be.

thedodd commented 7 years ago

All, please see PR #38. It is currently in a state which may completely address the items discussed here. A few notes to keep in mind:

Given those two points, I've created the type ErrorObject struct which implements Error as well as a few other interfaces which allow it to be used seamlessly with the new func MarshalErrors(w io.Writer, errs []error) error. This func's call signature is quite similar to our other Marshal... funcs.

There is also a series of public interfaces to be used by package consumers. These interfaces define a way of implementing error objects which will work seamlessly with MarshalErrors.

@chr0n1x this implements the items you've mentioned throughout this issue.

@elithrar this implements the items that you mentioned above as well — though not the "source": { "pointer": "/data/attributes/first-name" }, system yet, I've left that as a TODO for later, as this PR is fairly large already!

@genexp this implements the items that you've mentioned above as well. Namely the jsonapiRuntime.MarshalOneError(w, err.Error(), 500) bits, with only a few deviations. Also, in the name of allowing this package to be used in as many different frameworks as possible, it looks like setting of a response status is typically left out of this package's routines. It can always be done by the user before calling this method.

Let me know if you have any feedback.

shwoodard commented 7 years ago

Looks like @thedodd is close. See above.

svperfecta commented 7 years ago

Very nice!

thedodd commented 7 years ago

I believe that we can close this issue now, since the work in #38 has been merged. Thoughts?

jgillich commented 7 years ago

@theodd Is there a way to unmarshal errors? not sure if I'm missing something.

svperfecta commented 7 years ago

Note: Shoud probably see #110 which is not specifically related to errors, but deals with other top level objects that we currently cannot really include.