mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
Cloud 1359 errors across http (#2279)
* WIP: json marshal coded errors for http * WIP: trying to see how best to implement the http-error tests * finish stubbing out the Schemar methods in the test * using json to move CodedErrors across boundaries and associated tests * implemented feedback and fixes --------- Co-authored-by: Travis Turner <travis@molecula.com>
This commit is contained in:
parent
933767ec07
commit
d7c6258f16
7 changed files with 337 additions and 52 deletions
|
|
@ -71,8 +71,7 @@ func (c *Client) CreateDatabase(ctx context.Context, qdb *dax.QualifiedDatabase)
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -96,8 +95,7 @@ func (c *Client) DropDatabase(ctx context.Context, qdbid dax.QualifiedDatabaseID
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -122,8 +120,7 @@ func (c *Client) DatabaseByID(ctx context.Context, qdbid dax.QualifiedDatabaseID
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return nil, errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return nil, errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qdb *dax.QualifiedDatabase
|
||||
|
|
@ -158,8 +155,7 @@ func (c *Client) DatabaseByName(ctx context.Context, orgID dax.OrganizationID, n
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return nil, errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return nil, errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qdb *dax.QualifiedDatabase
|
||||
|
|
@ -194,8 +190,7 @@ func (c *Client) Databases(ctx context.Context, orgID dax.OrganizationID, ids ..
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return nil, errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return nil, errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qdbs []*dax.QualifiedDatabase
|
||||
|
|
@ -237,8 +232,7 @@ func (c *Client) SetDatabaseOption(ctx context.Context, qdbid dax.QualifiedDatab
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -277,8 +271,7 @@ func (c *Client) Table(ctx context.Context, qtid dax.QualifiedTableID) (*dax.Qua
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return nil, errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return nil, errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qtable *dax.QualifiedTable
|
||||
|
|
@ -314,8 +307,7 @@ func (c *Client) TableID(ctx context.Context, qdbid dax.QualifiedDatabaseID, nam
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return dflt, errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return dflt, errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qtid dax.QualifiedTableID
|
||||
|
|
@ -350,8 +342,7 @@ func (c *Client) Tables(ctx context.Context, qdbid dax.QualifiedDatabaseID, ids
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return nil, errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return nil, errors.Wrapf(errors.UnmarshalJSON(resp.Body), "Status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qtables []*dax.QualifiedTable
|
||||
|
|
@ -380,8 +371,7 @@ func (c *Client) CreateTable(ctx context.Context, qtbl *dax.QualifiedTable) erro
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -405,8 +395,7 @@ func (c *Client) DropTable(ctx context.Context, qtid dax.QualifiedTableID) error
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -434,8 +423,7 @@ func (c *Client) CreateField(ctx context.Context, qtid dax.QualifiedTableID, fld
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -464,8 +452,7 @@ func (c *Client) DropField(ctx context.Context, qtid dax.QualifiedTableID, fldNa
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -645,8 +632,7 @@ func (c *Client) RegisterNode(ctx context.Context, node *dax.Node) error {
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("registration request to %s status code: %d: %s", url, resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "registration request to %s status code: %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -676,8 +662,7 @@ func (c *Client) CheckInNode(ctx context.Context, node *dax.Node) error {
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return errors.Errorf("status code: %d: %s", resp.StatusCode, b)
|
||||
return errors.Wrapf(errors.UnmarshalJSON(resp.Body), "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -267,6 +267,10 @@ func (c *Controller) RegisterNode(ctx context.Context, n *dax.Node) error {
|
|||
// from its list (perhaps due to a network fault) and therefore the node needs
|
||||
// to be re-registered.
|
||||
func (c *Controller) CheckInNode(ctx context.Context, n *dax.Node) error {
|
||||
if n == nil || n.Address == "" {
|
||||
return NewErrNodeKeyInvalid("")
|
||||
}
|
||||
|
||||
tx, err := c.BoltDB.BeginTx(ctx, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "beginning tx")
|
||||
|
|
@ -737,6 +741,10 @@ func (c *Controller) SetDatabaseOption(ctx context.Context, qdbid dax.QualifiedD
|
|||
}
|
||||
|
||||
func (c *Controller) Databases(ctx context.Context, orgID dax.OrganizationID, ids ...dax.DatabaseID) ([]*dax.QualifiedDatabase, error) {
|
||||
if orgID == "" {
|
||||
return nil, dax.NewErrOrganizationIDDoesNotExist(orgID)
|
||||
}
|
||||
|
||||
tx, err := c.BoltDB.BeginTx(ctx, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "beginning tx")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/featurebasedb/featurebase/v3/dax"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/controller"
|
||||
"github.com/featurebasedb/featurebase/v3/errors"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
|
|
@ -80,7 +81,7 @@ func (s *server) postCreateDatabase(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err := s.controller.CreateDatabase(ctx, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ func (s *server) postDropDatabase(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err := s.controller.DropDatabase(ctx, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +125,7 @@ func (s *server) postDatabaseByID(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
resp, err := s.controller.DatabaseByID(ctx, qdbid)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +149,7 @@ func (s *server) postDatabaseByName(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
resp, err := s.controller.DatabaseByName(ctx, req.OrganizationID, req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +181,7 @@ func (s *server) postDatabases(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
resp, err := s.controller.Databases(ctx, req.OrganizationID, ids...)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +209,7 @@ func (s *server) patchDatabaseOptions(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if err := s.controller.SetDatabaseOption(r.Context(), req.QualifiedDatabaseID, req.Option, req.Value); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -237,7 +238,7 @@ func (s *server) postCreateTable(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err := s.controller.CreateTable(ctx, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +262,7 @@ func (s *server) postTable(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
resp, err := s.controller.TableByID(ctx, qtid)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +287,7 @@ func (s *server) postTableID(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
qtbl, err := s.controller.TableByName(ctx, req.QualifiedDatabaseID, req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
qtid := qtbl.QualifiedID()
|
||||
|
|
@ -312,7 +313,7 @@ func (s *server) postDropTable(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err := s.controller.DropTable(ctx, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -334,7 +335,7 @@ func (s *server) postCreateField(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err := s.controller.CreateField(ctx, qtid, req.Field)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -361,7 +362,7 @@ func (s *server) postDropField(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err := s.controller.DropField(ctx, qtid, req.Field)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -389,7 +390,7 @@ func (s *server) postTables(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
resp, err := s.controller.Tables(ctx, qdbid, ids...)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -614,7 +615,7 @@ func (s *server) postRegisterNode(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if err := s.controller.RegisterNode(ctx, node); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +700,7 @@ func (s *server) postCheckInNode(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if err := s.controller.CheckInNode(ctx, node); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
http.Error(w, errors.MarshalJSON(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
ErrOrganizationIDDoesNotExist errors.Code = "OrganizationIDDoesNotExist"
|
||||
|
||||
ErrDatabaseIDExists errors.Code = "DatabaseIDExists"
|
||||
ErrDatabaseIDDoesNotExist errors.Code = "DatabaseIDDoesNotExist"
|
||||
ErrDatabaseNameDoesNotExist errors.Code = "DatabaseNameDoesNotExist"
|
||||
|
|
@ -29,6 +31,13 @@ const (
|
|||
// The following are helper functions for constructing coded errors containing
|
||||
// relevant information about the specific error.
|
||||
|
||||
func NewErrOrganizationIDDoesNotExist(orgID OrganizationID) error {
|
||||
return errors.New(
|
||||
ErrOrganizationIDDoesNotExist,
|
||||
fmt.Sprintf("Organization ID '%s' does not exist", orgID),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrDatabaseIDExists(qdbid QualifiedDatabaseID) error {
|
||||
return errors.New(
|
||||
ErrDatabaseIDExists,
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ import (
|
|||
|
||||
featurebase "github.com/featurebasedb/featurebase/v3"
|
||||
"github.com/featurebasedb/featurebase/v3/dax"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/controller"
|
||||
controllerclient "github.com/featurebasedb/featurebase/v3/dax/controller/client"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/controller/schemar"
|
||||
queryerclient "github.com/featurebasedb/featurebase/v3/dax/queryer/client"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/server"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/server/test"
|
||||
"github.com/featurebasedb/featurebase/v3/errors"
|
||||
"github.com/featurebasedb/featurebase/v3/logger"
|
||||
"github.com/featurebasedb/featurebase/v3/sql3/test/defs"
|
||||
goerrors "github.com/pkg/errors"
|
||||
|
|
@ -706,6 +709,143 @@ func TestDAXIntegration(t *testing.T) {
|
|||
assert.Equal(t, partitions2, nodes[2].Partitions)
|
||||
}
|
||||
})
|
||||
|
||||
// These tests are to test the traversal of CodedErrors across HTTP
|
||||
// The client is also wrapped to maintain the integrity of the interface to prevent any
|
||||
// additional methods added to the client without the appropriate tests for CodedErrors
|
||||
t.Run("HTTPError", func(t *testing.T) {
|
||||
mc := test.MustRunManagedCommand(t)
|
||||
defer mc.Close()
|
||||
|
||||
svcmgr := mc.Manage()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("controller", func(t *testing.T) {
|
||||
// Set up Controller client.
|
||||
client := newWrappedControllerClient(controllerclient.New(svcmgr.Controller.Address(), svcmgr.Logger))
|
||||
|
||||
t.Run("Registrar", func(t *testing.T) {
|
||||
t.Run("RegisterNode", func(t *testing.T) {
|
||||
node := &dax.Node{}
|
||||
err := client.RegisterNode(ctx, node)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, controller.ErrCodeNodeKeyInvalid))
|
||||
}
|
||||
})
|
||||
t.Run("CheckInNode", func(t *testing.T) {
|
||||
node := &dax.Node{}
|
||||
err := client.CheckInNode(ctx, node)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, controller.ErrCodeNodeKeyInvalid))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Schemar", func(t *testing.T) {
|
||||
|
||||
qtid := dax.QualifiedTableID{
|
||||
ID: "BAD",
|
||||
}
|
||||
qtbl := dax.QualifiedTable{}
|
||||
qdbid := dax.QualifiedDatabaseID{}
|
||||
tbfld := &dax.Field{}
|
||||
|
||||
t.Run("CreateDatabase", func(t *testing.T) {
|
||||
err := client.CreateDatabase(ctx, nil)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, schemar.ErrCodeDatabaseNameInvalid))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DropDatabase", func(t *testing.T) {
|
||||
err := client.DropDatabase(ctx, qdbid)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrDatabaseIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DatabaseByName", func(t *testing.T) {
|
||||
_, err := client.DatabaseByName(ctx, "", "")
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrDatabaseNameDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DatabaseByID", func(t *testing.T) {
|
||||
_, err := client.DatabaseByID(ctx, qdbid)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrDatabaseIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SetDatabaseOption", func(t *testing.T) {
|
||||
err := client.SetDatabaseOption(ctx, qdbid, "", "")
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrDatabaseIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Databases", func(t *testing.T) {
|
||||
_, err := client.Databases(ctx, "", dbID)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrOrganizationIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateTable", func(t *testing.T) {
|
||||
err := client.CreateTable(ctx, &qtbl)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, schemar.ErrCodeTableNameInvalid))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DropTable", func(t *testing.T) {
|
||||
err := client.DropTable(ctx, qtid)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrTableIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TableByName", func(t *testing.T) {
|
||||
req := dax.QualifiedTableID{}
|
||||
_, err := client.TableByName(ctx, qdbid, req.Name)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrTableNameDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TableByID", func(t *testing.T) {
|
||||
_, err := client.TableByID(ctx, qtid)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrTableIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
|
||||
//Todo: make it so "Tables" doesn't return all tables if error is present
|
||||
t.Run("Tables", func(t *testing.T) {
|
||||
// _, err := client.Tables(ctx, qdbid)
|
||||
// //the error is also nil
|
||||
// if err != nil {
|
||||
// assert.True(t, errors.Is(err, ""))
|
||||
// }
|
||||
})
|
||||
|
||||
t.Run("CreateField", func(t *testing.T) {
|
||||
err := client.CreateField(ctx, qtid, tbfld)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, schemar.ErrCodeFieldNameInvalid))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DropField", func(t *testing.T) {
|
||||
err := client.DropField(ctx, qtid, tbfld.Name)
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, errors.Is(err, dax.ErrTableIDDoesNotExist))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func dirIsEmpty(t *testing.T, name string) bool {
|
||||
|
|
|
|||
93
dax/test/dax/wrappers_test.go
Normal file
93
dax/test/dax/wrappers_test.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package dax_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/featurebasedb/featurebase/v3/dax"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/computer"
|
||||
controllerclient "github.com/featurebasedb/featurebase/v3/dax/controller/client"
|
||||
)
|
||||
|
||||
var _ computer.Registrar = (*wrappedControllerClient)(nil)
|
||||
var _ dax.Schemar = (*wrappedControllerClient)(nil)
|
||||
|
||||
// wrappedControllerClient is a wrapper around the controller client which we
|
||||
// use in tests to ensure that all of the methods for the computer.Registrar
|
||||
// interface are covered by these tests. The idea being that if someone modifes
|
||||
// the interface to include a new `Foo()` method, this test will no longer
|
||||
// compile, and the developer will be directected here to add the appropriate
|
||||
// tests. There's probably a more automated way to do this wil the `reflect`
|
||||
// package, but that seems overly compilicated right now.
|
||||
type wrappedControllerClient struct {
|
||||
cli *controllerclient.Client
|
||||
}
|
||||
|
||||
func newWrappedControllerClient(cli *controllerclient.Client) *wrappedControllerClient {
|
||||
return &wrappedControllerClient{
|
||||
cli: cli,
|
||||
}
|
||||
}
|
||||
|
||||
// Registrar
|
||||
|
||||
func (w *wrappedControllerClient) RegisterNode(ctx context.Context, node *dax.Node) error {
|
||||
return w.cli.RegisterNode(ctx, node)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) CheckInNode(ctx context.Context, node *dax.Node) error {
|
||||
return w.cli.CheckInNode(ctx, node)
|
||||
}
|
||||
|
||||
// Schemar
|
||||
|
||||
func (w *wrappedControllerClient) CreateDatabase(ctx context.Context, qdb *dax.QualifiedDatabase) error {
|
||||
return w.cli.CreateDatabase(ctx, qdb)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) DropDatabase(ctx context.Context, qdbid dax.QualifiedDatabaseID) error {
|
||||
return w.cli.DropDatabase(ctx, qdbid)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) DatabaseByName(ctx context.Context, orgID dax.OrganizationID, dbname dax.DatabaseName) (*dax.QualifiedDatabase, error) {
|
||||
return w.cli.DatabaseByName(ctx, orgID, dbname)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) DatabaseByID(ctx context.Context, qdbid dax.QualifiedDatabaseID) (*dax.QualifiedDatabase, error) {
|
||||
return w.cli.DatabaseByID(ctx, qdbid)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) SetDatabaseOption(ctx context.Context, qdbid dax.QualifiedDatabaseID, option string, value string) error {
|
||||
return w.cli.SetDatabaseOption(ctx, qdbid, option, value)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) Databases(ctx context.Context, orgID dax.OrganizationID, dbIDs ...dax.DatabaseID) ([]*dax.QualifiedDatabase, error) {
|
||||
return w.cli.Databases(ctx, orgID, dbIDs...)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) CreateTable(ctx context.Context, qtbl *dax.QualifiedTable) error {
|
||||
return w.cli.CreateTable(ctx, qtbl)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) DropTable(ctx context.Context, qtid dax.QualifiedTableID) error {
|
||||
return w.cli.DropTable(ctx, qtid)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) TableByName(ctx context.Context, qdbid dax.QualifiedDatabaseID, tname dax.TableName) (*dax.QualifiedTable, error) {
|
||||
return w.cli.TableByName(ctx, qdbid, tname)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) TableByID(ctx context.Context, qtid dax.QualifiedTableID) (*dax.QualifiedTable, error) {
|
||||
return w.cli.TableByID(ctx, qtid)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) Tables(ctx context.Context, qdbid dax.QualifiedDatabaseID, tids ...dax.TableID) ([]*dax.QualifiedTable, error) {
|
||||
return w.cli.Tables(ctx, qdbid, tids...)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) CreateField(ctx context.Context, qtid dax.QualifiedTableID, fld *dax.Field) error {
|
||||
return w.cli.CreateField(ctx, qtid, fld)
|
||||
}
|
||||
|
||||
func (w *wrappedControllerClient) DropField(ctx context.Context, qtid dax.QualifiedTableID, fname dax.FieldName) error {
|
||||
return w.cli.DropField(ctx, qtid, fname)
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -12,8 +15,8 @@ type Code string
|
|||
|
||||
func New(code Code, message string) error {
|
||||
return errors.WithStack(codedError{
|
||||
code: code,
|
||||
message: message,
|
||||
Code: code,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ func Errorf(format string, args ...interface{}) error {
|
|||
// an error Code instead of an error.
|
||||
func Is(err error, target Code) bool {
|
||||
match := codedError{
|
||||
code: target,
|
||||
Code: target,
|
||||
}
|
||||
return errors.Is(err, match)
|
||||
}
|
||||
|
|
@ -65,12 +68,13 @@ func Wrapf(err error, fmt string, args ...interface{}) error {
|
|||
// codedError is the fundamental type used by this package to provide coded
|
||||
// errors.
|
||||
type codedError struct {
|
||||
code Code
|
||||
message string
|
||||
Code Code `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Wrapped string `json:"wrapped,omitempty"`
|
||||
}
|
||||
|
||||
func (ce codedError) Error() string {
|
||||
return ce.message
|
||||
return ce.Message
|
||||
}
|
||||
|
||||
// func (ce codedError) As(target interface{}) bool {
|
||||
|
|
@ -78,7 +82,7 @@ func (ce codedError) Error() string {
|
|||
// }
|
||||
|
||||
func (ce codedError) Is(err error) bool {
|
||||
if e, ok := err.(codedError); ok && ce.code == e.code {
|
||||
if e, ok := err.(codedError); ok && ce.Code == e.Code {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
@ -87,3 +91,48 @@ func (ce codedError) Is(err error) bool {
|
|||
const (
|
||||
ErrUncoded Code = "Uncoded"
|
||||
)
|
||||
|
||||
// MarshalJSON returns the provided error as a json object (as a string)
|
||||
// representing a codedError. If err is not already a codedError, the json
|
||||
// object will still represent a codedError but its `code` value will be empty.
|
||||
// Note: an empty code here is intentional and is different from code
|
||||
// `errors.Uncoded` which is a valid code; it just means the developer returned
|
||||
// a codedError but didn't bother to choose (or create) a useful error code.
|
||||
func MarshalJSON(err error) string {
|
||||
cause := Cause(err)
|
||||
|
||||
var out *codedError
|
||||
|
||||
switch v := cause.(type) {
|
||||
case codedError:
|
||||
v.Wrapped = err.Error()
|
||||
out = &v
|
||||
default:
|
||||
out = &codedError{
|
||||
Message: cause.Error(),
|
||||
Wrapped: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal the codedError to json as output.
|
||||
j, jerr := json.Marshal(out)
|
||||
if jerr != nil {
|
||||
return out.Error()
|
||||
}
|
||||
|
||||
return string(j)
|
||||
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts the byte slice into a codedError. If the bytes can't
|
||||
// unmarshal to a codedError, a normal error will be returned containing the
|
||||
// string value of the byte slice.
|
||||
func UnmarshalJSON(r io.Reader) error {
|
||||
b, _ := io.ReadAll(r)
|
||||
|
||||
out := &codedError{}
|
||||
if err := json.Unmarshal(b, out); err != nil {
|
||||
return errors.New(string(b))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue