diff --git a/README.md b/README.md index 0076d83..83e0ec4 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,105 @@ supports relationships out of the box and will even side load them in your response into an "included" array--that contains associated objects. +## Introduction + +jsonapi uses StructField tags to annotate the structs fields that you +already have and use in your app and then reads and writes jsonapi.org +output based on the instructions you give the library in your jsonapi +tags. Let's take an example. In your app, +you most likely have structs that look similar to these, + + +```go +type Blog struct { + Id int `json:"id"` + Title string `json:"title"` + Posts []*Post `json:"posts"` + CurrentPost *Post `json:"current_post"` + CurrentPostId int `json:"current_post_id"` + CreatedAt time.Time `json:"created_at"` + ViewCount int `json:"view_count"` +} + +type Post struct { + Id int `json:"id"` + BlogId int `json:"blog_id"` + Title string `json:"title"` + Body string `json:"body"` + Comments []*Comment `json:"comments"` +} + +type Comment struct { + Id int `json:"id"` + PostId int `json:"post_id"` + Body string `json:"body"` +} +``` + +These structs may or may not resemble the layout of your database. But +these are the ones that you want to use right? You wouldn't want to use +structs like those that jsonapi sends because it is very hard to get at all of +your data easily. + + +## Tags Example + +You want jsonapi.org style inputs and ouputs but you want to keep your +structs that you already have. Use the jsonapi lib with the "jsonapi" +tag on your struct fields along with its Marshal and Unmarshal methods +to construct and read your responses and replies, respectively. Here's +an example of the structs above using jsonapi tags, + +```go +type Blog struct { + Id int `jsonapi:"primary,blogs"` + Title string `jsonapi:"attr,title"` + Posts []*Post `jsonapi:"relation,posts"` + CurrentPost *Post `jsonapi:"relation,current_post"` + CurrentPostId int `jsonapi:"attr,current_post_id"` + CreatedAt time.Time `jsonapi:"attr,created_at"` + ViewCount int `jsonapi:"attr,view_count"` +} + +type Post struct { + Id int `jsonapi:"primary,posts"` + BlogId int `jsonapi:"attr,blog_id"` + Title string `jsonapi:"attr,title"` + Body string `jsonapi:"attr,body"` + Comments []*Comment `jsonapi:"relation,comments"` +} + +type Comment struct { + Id int `jsonapi:"primary,comments"` + PostId int `jsonapi:"attr,post_id"` + Body string `jsonapi:"attr,body"` +} +``` + +## Handler Examples + +Now you have your structs prepared to be seralized or materialized. +What about the rest? + +### Create + +```go +func CreateBlog(w http.ResponseWriter, r *http.Request) { + blog := new(Blog) + + if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil { + http.Error(w, err.Error(), 500) + return + } + + // ...do stuff with your blog... + + w.WriteHeader(201) + w.Header().Set("Content-Type", "application/vnd.api+json") + + if err := jsonapi.MarshalOnePayload(w, blog); err != nil { + http.Error(w, err.Error(), 500) + } +} +``` diff --git a/examples/app.go b/examples/app.go new file mode 100644 index 0000000..d935e49 --- /dev/null +++ b/examples/app.go @@ -0,0 +1,161 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "regexp" + "time" + + "github.com/shwoodard/jsonapi" +) + +func main() { + http.HandleFunc("/blogs", func(w http.ResponseWriter, r *http.Request) { + if !regexp.MustCompile(`application/vnd\.api\+json`).Match([]byte(r.Header.Get("Accept"))) { + http.Error(w, "Not Acceptable", 406) + return + } + + if r.Method == "POST" { + createBlog(w, r) + } else { + listBlogs(w, r) + } + }) + + blog := testBlogForCreate() + payload, _ := jsonapi.MarshalOnePayloadEmbedded(blog) + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + req, _ := http.NewRequest("POST", "/blogs", in) + + req.Header.Set("Accept", "application/vnd.api+json") + + w := httptest.NewRecorder() + + http.DefaultServeMux.ServeHTTP(w, req) + + buf := new(bytes.Buffer) + io.Copy(buf, w.Body) + + fmt.Println("============ jsonapi response from create ===========\n") + fmt.Println(buf.String()) + fmt.Println("============== end raw jsonapi response =============") + + responseBlog := new(Blog) + + jsonapi.UnmarshalPayload(buf, responseBlog) + + out := bytes.NewBuffer(nil) + json.NewEncoder(out).Encode(responseBlog) + + fmt.Println("\n================ Viola! Converted back our Blog struct =================\n") + fmt.Printf("%s\n", out.Bytes()) + fmt.Println("================ end marshal materialized Blog struct =================") +} + +func createBlog(w http.ResponseWriter, r *http.Request) { + blog := new(Blog) + + if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil { + http.Error(w, err.Error(), 500) + return + } + + // ...do stuff with your blog... + + w.WriteHeader(201) + w.Header().Set("Content-Type", "application/vnd.api+json") + + if err := jsonapi.MarshalOnePayload(w, blog); err != nil { + http.Error(w, err.Error(), 500) + } +} + +func listBlogs(w http.ResponseWriter, r *http.Request) { +} + +type Blog struct { + Id int `jsonapi:"primary,blogs"` + Title string `jsonapi:"attr,title"` + Posts []*Post `jsonapi:"relation,posts"` + CurrentPost *Post `jsonapi:"relation,current_post"` + CurrentPostId int `jsonapi:"attr,current_post_id"` + CreatedAt time.Time `jsonapi:"attr,created_at"` + ViewCount int `jsonapi:"attr,view_count"` +} + +type Post struct { + Id int `jsonapi:"primary,posts"` + BlogId int `jsonapi:"attr,blog_id"` + Title string `jsonapi:"attr,title"` + Body string `jsonapi:"attr,body"` + Comments []*Comment `jsonapi:"relation,comments"` +} + +type Comment struct { + Id int `jsonapi:"primary,comments"` + PostId int `jsonapi:"attr,post_id"` + Body string `jsonapi:"attr,body"` +} + +func testBlogForCreate() *Blog { + return &Blog{ + Id: 1, + Title: "Title 1", + CreatedAt: time.Now(), + Posts: []*Post{ + &Post{ + Id: 1, + Title: "Foo", + Body: "Bar", + Comments: []*Comment{ + &Comment{ + Id: 1, + Body: "foo", + }, + &Comment{ + Id: 2, + Body: "bar", + }, + }, + }, + &Post{ + Id: 2, + Title: "Fuubar", + Body: "Bas", + Comments: []*Comment{ + &Comment{ + Id: 1, + Body: "foo", + }, + &Comment{ + Id: 3, + Body: "bas", + }, + }, + }, + }, + CurrentPost: &Post{ + Id: 1, + Title: "Foo", + Body: "Bar", + Comments: []*Comment{ + &Comment{ + Id: 1, + Body: "foo", + }, + &Comment{ + Id: 2, + Body: "bar", + }, + }, + }, + } +} diff --git a/response_test.go b/response_test.go index 6b8e06b..ba3a4c7 100644 --- a/response_test.go +++ b/response_test.go @@ -8,25 +8,28 @@ import ( "time" ) -type Comment struct { - Id int `jsonapi:"primary,comments"` - Body string `jsonapi:"attr,body"` +type Blog struct { + Id int `jsonapi:"primary,blogs"` + Title string `jsonapi:"attr,title"` + Posts []*Post `jsonapi:"relation,posts"` + CurrentPost *Post `jsonapi:"relation,current_post"` + CurrentPostId int `jsonapi:"attr,current_post_id"` + CreatedAt time.Time `jsonapi:"attr,created_at"` + ViewCount int `jsonapi:"attr,view_count"` } type Post struct { Id int `jsonapi:"primary,posts"` + BlogId int `jsonapi:"attr,blog_id"` Title string `jsonapi:"attr,title"` Body string `jsonapi:"attr,body"` Comments []*Comment `jsonapi:"relation,comments"` } -type Blog struct { - Id int `jsonapi:"primary,blogs"` - Title string `jsonapi:"attr,title"` - Posts []*Post `jsonapi:"relation,posts"` - CurrentPost *Post `jsonapi:"relation,current_post"` - CreatedAt time.Time `jsonapi:"attr,created_at"` - ViewCount int `jsonapi:"attr,view_count"` +type Comment struct { + Id int `jsonapi:"primary,comments"` + PostId int `jsonapi:"attr,post_id"` + Body string `jsonapi:"attr,body"` } type Blogs []*Blog