From a49bbd74c77362da0ad955499e2c865ad83cbfed Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Mon, 20 Feb 2017 10:26:49 +0000 Subject: [PATCH] Enable REST output as JSON and JSONAPI --- util/show/show.go | 234 ++++++++++++++++++++++++++++++++++++++++++++++ web/routes.go | 46 ++------- 2 files changed, 244 insertions(+), 36 deletions(-) create mode 100644 util/show/show.go diff --git a/util/show/show.go b/util/show/show.go new file mode 100644 index 00000000..75a5565e --- /dev/null +++ b/util/show/show.go @@ -0,0 +1,234 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package show + +import ( + "github.com/abcum/fibre" + "github.com/abcum/surreal/db" + "github.com/abcum/surreal/sql" + "github.com/abcum/surreal/util/data" + "github.com/abcum/surreal/util/lang" +) + +type Display int8 + +const ( + One Display = iota + Many +) + +type Method int8 + +const ( + Select Method = iota + Create + Update + Delete + Modify + Trace +) + +func Output(c *fibre.Context, t string, d Display, m Method, res []*db.Response, err error) error { + + switch err.(type) { + case *sql.ParseError: + return fibre.NewHTTPError(400) // Will happen if invalid json + case *sql.BlankError: + return fibre.NewHTTPError(403) // Will happen if invalid auth + case *sql.QueryError: + return fibre.NewHTTPError(403) // Should not happen + case *sql.PermsError: + return fibre.NewHTTPError(403) // Should not happen + case *sql.EmptyError: + return fibre.NewHTTPError(500) // Should not happen + } + + if len(res) == 0 { + return fibre.NewHTTPError(500) // Should not happen + } + + switch c.Type() { + case "application/json": + return OutputRest(c, t, d, m, res, err) + case "application/cbor": + return OutputRest(c, t, d, m, res, err) + case "application/msgpack": + return OutputRest(c, t, d, m, res, err) + case "application/vnd.api+json": + return OutputJson(c, t, d, m, res, err) + } + + return nil + +} + +// -------------------------------------------------- +// Endpoints for manipulating multiple records +// -------------------------------------------------- + +// OutputBase outputs the response data directly from the SQL +// query reponse without any manipulation or alteration. +func OutputBase(c *fibre.Context, t string, d Display, m Method, res []*db.Response, err error) error { + + var ret *db.Response + + switch ret = res[0]; ret.Status { + case "OK": + return c.Send(200, ret) + case "ERR_DB": + return fibre.NewHTTPError(503) + case "ERR_KV": + return fibre.NewHTTPError(409) + default: + return fibre.NewHTTPError(400) + } + +} + +// OutputRest outputs the json response data according to the specification +// available at http://emberjs.com/api/data/classes/DS.RESTAdapter.html and +// according to http://stackoverflow.com/questions/14922623. +func OutputRest(c *fibre.Context, t string, d Display, m Method, res []*db.Response, err error) error { + + var ret *db.Response + + switch ret = res[0]; ret.Status { + case "OK": + break + case "ERR_DB": + return fibre.NewHTTPError(503) + case "ERR_KV": + return fibre.NewHTTPError(409) + default: + return fibre.NewHTTPError(400) + } + + if len(res[0].Result) == 0 { + return c.Send(204, nil) + } + + switch m { + case Delete: + return c.Send(204, nil) + case Create: + return c.Send(201, map[string]interface{}{ + lang.Singularize(t): cleanRestOne(res[0].Result[0]), + }) + } + + switch d { + case One: + return c.Send(200, map[string]interface{}{ + lang.Singularize(t): cleanRestOne(res[0].Result[0]), + }) + case Many: + return c.Send(200, map[string]interface{}{ + lang.Pluralize(t): cleanRestAll(res[0].Result), + }) + } + + return c.Send(200, nil) + +} + +func cleanRestAll(vals []interface{}) (all []*data.Doc) { + + for _, val := range vals { + all = append(all, cleanRestOne(val)) + } + + return + +} + +func cleanRestOne(val interface{}) (one *data.Doc) { + + one = data.Consume(val) + one.Iff(one.Get("meta.id").Data(), "id") + + return + +} + +// OutputJson outputs the json response data according to the specification +// available at http://jsonapi.org/format/. Currently linked data does not +// adhere to the specification, but is intead displayed inside the attribute +// object. Links and embedded paths are also not implemented. +func OutputJson(c *fibre.Context, t string, d Display, m Method, res []*db.Response, err error) error { + + var ret *db.Response + + switch ret = res[0]; ret.Status { + case "OK": + break + case "ERR_DB": + return fibre.NewHTTPError(503) + case "ERR_KV": + return fibre.NewHTTPError(409) + default: + return fibre.NewHTTPError(400) + } + + if len(res[0].Result) == 0 { + return c.Send(204, nil) + } + + switch m { + case Delete: + return c.Send(204, nil) + case Create: + return c.Send(201, map[string]interface{}{ + "data": cleanJsonOne(res[0].Result[0]), + }) + } + + switch d { + case One: + return c.Send(200, map[string]interface{}{ + "data": cleanJsonOne(res[0].Result[0]), + }) + case Many: + return c.Send(200, map[string]interface{}{ + "data": cleanJsonAll(res[0].Result), + }) + } + + return c.Send(200, nil) + +} + +func cleanJsonAll(vals []interface{}) (all []*data.Doc) { + + for _, val := range vals { + all = append(all, cleanJsonOne(val)) + } + + return + +} + +func cleanJsonOne(val interface{}) (one *data.Doc) { + + one = data.New() + old := data.Consume(val) + one.Set(old.Get("meta.id").Data(), "id") + one.Set(old.Get("meta.tb").Data(), "type") + one.Set(old.Data(), "attributes") + one.Del("attributes.id") + one.Del("attributes.meta") + + return + +} diff --git a/web/routes.go b/web/routes.go index bed174fc..59c6c414 100644 --- a/web/routes.go +++ b/web/routes.go @@ -22,35 +22,9 @@ import ( "github.com/abcum/fibre/mw" "github.com/abcum/surreal/db" "github.com/abcum/surreal/sql" + "github.com/abcum/surreal/util/show" ) -func output(c *fibre.Context, err error, res []*db.Response) error { - - if err != nil { - return fibre.NewHTTPError(500) - } - - if len(res) == 0 { - return fibre.NewHTTPError(500) - } - - switch ret := res[0]; ret.Status { - case "OK": - return c.Send(200, ret.Result) - case "ERR_DB": - return fibre.NewHTTPError(503) - case "ERR_TX": - return fibre.NewHTTPError(500) - case "ERR_KV": - return fibre.NewHTTPError(409) - case "ERR_CK": - return fibre.NewHTTPError(403) - default: - return fibre.NewHTTPError(400) - } - -} - func limit(c *fibre.Context, i int64) int64 { if s := c.Query("limit"); len(s) > 0 { if x, err := strconv.ParseInt(s, 10, 64); err == nil { @@ -220,7 +194,7 @@ func routes(s *fibre.Fibre) { "start": start(c, 0), }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.Many, show.Select, res, err) }) @@ -239,7 +213,7 @@ func routes(s *fibre.Fibre) { "data": data, }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.Many, show.Create, res, err) }) @@ -251,7 +225,7 @@ func routes(s *fibre.Fibre) { "class": sql.NewTable(c.Param("class")), }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.Many, show.Delete, res, err) }) @@ -272,7 +246,7 @@ func routes(s *fibre.Fibre) { "trace": trace(c, time.Now()), }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.One, show.Select, res, err) }) @@ -291,7 +265,7 @@ func routes(s *fibre.Fibre) { "data": data, }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.One, show.Create, res, err) }) @@ -310,7 +284,7 @@ func routes(s *fibre.Fibre) { "data": data, }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.One, show.Update, res, err) }) @@ -329,7 +303,7 @@ func routes(s *fibre.Fibre) { "data": data, }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.One, show.Modify, res, err) }) @@ -341,7 +315,7 @@ func routes(s *fibre.Fibre) { "thing": sql.NewThing(c.Param("class"), c.Param("id")), }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.One, show.Trace, res, err) }) @@ -353,7 +327,7 @@ func routes(s *fibre.Fibre) { "thing": sql.NewThing(c.Param("class"), c.Param("id")), }) - return output(c, err, res) + return show.Output(c, c.Param("class"), show.One, show.Delete, res, err) })