Skip to content

Commit

Permalink
chore: Extract inline (http and client) errors to errors.go (sourcene…
Browse files Browse the repository at this point in the history
…twork#967)

* Move unexpected type error to client

* Make existing http errors public

* Extract http errors to errors.go

* Extract some cli errors

* Extract client errors
  • Loading branch information
AndrewSisley authored Dec 15, 2022
1 parent a00f3c0 commit 6a4a25f
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 57 deletions.
17 changes: 14 additions & 3 deletions api/http/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,29 @@ package http

import (
"context"
"errors"
"fmt"
"net/http"
"os"
"strings"

"github.com/sourcenetwork/defradb/errors"
)

var env = os.Getenv("DEFRA_ENV")

// Errors returnable from this package.
//
// This list is incomplete and undefined errors may also be returned.
// Errors returned from this package may be tested against these errors with errors.Is.
var (
errNoListener = errors.New("cannot serve with no listener")
errSchema = errors.New("base must start with the http or https scheme")
ErrNoListener = errors.New("cannot serve with no listener")
ErrSchema = errors.New("base must start with the http or https scheme")
ErrDatabaseNotAvailable = errors.New("no database available")
ErrFormNotSupported = errors.New("content type application/x-www-form-urlencoded not yet supported")
ErrBodyEmpty = errors.New("body cannot be empty")
ErrMissingGQLQuery = errors.New("missing GraphQL query")
ErrPeerIdUnavailable = errors.New("no peer ID available. P2P might be disabled")
ErrStreamingUnsupported = errors.New("streaming unsupported")
)

// ErrorResponse is the GQL top level object holding error items for the response payload.
Expand Down
2 changes: 1 addition & 1 deletion api/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func sendJSON(ctx context.Context, rw http.ResponseWriter, v any, code int) {
func dbFromContext(ctx context.Context) (client.DB, error) {
db, ok := ctx.Value(ctxDB{}).(client.DB)
if !ok {
return nil, errors.New("no database available")
return nil, ErrDatabaseNotAvailable
}

return db, nil
Expand Down
10 changes: 5 additions & 5 deletions api/http/handlerfuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func execGQLHandler(rw http.ResponseWriter, req *http.Request) {
handleErr(
req.Context(),
rw,
errors.New("content type application/x-www-form-urlencoded not yet supported"),
ErrFormNotSupported,
http.StatusBadRequest,
)
return
Expand All @@ -121,7 +121,7 @@ func execGQLHandler(rw http.ResponseWriter, req *http.Request) {

default:
if req.Body == nil {
handleErr(req.Context(), rw, errors.New("body cannot be empty"), http.StatusBadRequest)
handleErr(req.Context(), rw, ErrBodyEmpty, http.StatusBadRequest)
return
}
body, err := io.ReadAll(req.Body)
Expand All @@ -135,7 +135,7 @@ func execGQLHandler(rw http.ResponseWriter, req *http.Request) {

// if at this point query is still empty, return an error
if query == "" {
handleErr(req.Context(), rw, errors.New("missing GraphQL query"), http.StatusBadRequest)
handleErr(req.Context(), rw, ErrMissingGQLQuery, http.StatusBadRequest)
return
}

Expand Down Expand Up @@ -251,7 +251,7 @@ func getBlockHandler(rw http.ResponseWriter, req *http.Request) {
func peerIDHandler(rw http.ResponseWriter, req *http.Request) {
peerID, ok := req.Context().Value(ctxPeerID{}).(string)
if !ok || peerID == "" {
handleErr(req.Context(), rw, errors.New("no peer ID available. P2P might be disabled"), http.StatusNotFound)
handleErr(req.Context(), rw, ErrPeerIdUnavailable, http.StatusNotFound)
return
}

Expand All @@ -268,7 +268,7 @@ func peerIDHandler(rw http.ResponseWriter, req *http.Request) {
func subscriptionHandler(pub *events.Publisher[events.Update], rw http.ResponseWriter, req *http.Request) {
flusher, ok := rw.(http.Flusher)
if !ok {
handleErr(req.Context(), rw, errors.New("streaming unsupported"), http.StatusInternalServerError)
handleErr(req.Context(), rw, ErrStreamingUnsupported, http.StatusInternalServerError)
return
}

Expand Down
2 changes: 1 addition & 1 deletion api/http/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func setRoutes(h *handler) *handler {
// The base must start with a http or https.
func JoinPaths(base string, paths ...string) (*url.URL, error) {
if !strings.HasPrefix(base, "http") {
return nil, errSchema
return nil, ErrSchema
}

u, err := url.Parse(base)
Expand Down
4 changes: 2 additions & 2 deletions api/http/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ func TestJoinPathsWithBase(t *testing.T) {

func TestJoinPathsWithNoBase(t *testing.T) {
_, err := JoinPaths("", BlocksPath, "cid_of_some_sort")
assert.ErrorIs(t, errSchema, err)
assert.ErrorIs(t, ErrSchema, err)
}

func TestJoinPathsWithBaseWithoutHttpPrefix(t *testing.T) {
_, err := JoinPaths("localhost:9181", BlocksPath, "cid_of_some_sort")
assert.ErrorIs(t, errSchema, err)
assert.ErrorIs(t, ErrSchema, err)
}

func TestJoinPathsWithNoPaths(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion api/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func (s *Server) listenWithTLS(ctx context.Context) error {
// Run calls Serve with the receiver's listener
func (s *Server) Run(ctx context.Context) error {
if s.listener == nil {
return errNoListener
return ErrNoListener
}

if s.certManager != nil {
Expand Down
2 changes: 1 addition & 1 deletion api/http/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestNewServerAndRunWithoutListener(t *testing.T) {
ctx := context.Background()
s := NewServer(nil, WithAddress(":0"))
if ok := assert.NotNil(t, s); ok {
assert.Equal(t, errNoListener, s.Run(ctx))
assert.Equal(t, ErrNoListener, s.Run(ctx))
}
}

Expand Down
12 changes: 6 additions & 6 deletions cli/addreplicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

"github.com/sourcenetwork/defradb/errors"
defraClient "github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/logging"
netclient "github.com/sourcenetwork/defradb/net/api/client"
)
Expand All @@ -33,12 +33,12 @@ for the p2p data sync system.`,
if err := cmd.Usage(); err != nil {
return err
}
return errors.New("must specify two arguments: collection and peer")
return NewErrMissingArgs(len(args), 2)
}
collection := args[0]
peerAddr, err := ma.NewMultiaddr(args[1])
if err != nil {
return errors.Wrap("could not parse peer address", err)
return defraClient.NewErrParsingFailed(err, "peer")
}

log.FeedbackInfo(
Expand All @@ -52,20 +52,20 @@ for the p2p data sync system.`,
cred := insecure.NewCredentials()
client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred))
if err != nil {
return errors.Wrap("failed to create RPC client", err)
return NewErrFailedToCreateRPCClient(err)
}

rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration()
if err != nil {
return errors.Wrap("failed to parse RPC timeout duration", err)
return defraClient.NewErrParsingFailed(err, "RPC timeout duration")
}

ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration)
defer cancel()

pid, err := client.AddReplicator(ctx, collection, peerAddr)
if err != nil {
return errors.Wrap("failed to add replicator, request failed", err)
return NewErrFailedToAddReplicator(err)
}
log.FeedbackInfo(ctx, "Successfully added replicator", logging.NewKV("PID", pid))
return nil
Expand Down
18 changes: 8 additions & 10 deletions cli/blocks_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,61 +11,59 @@
package cli

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

"github.com/spf13/cobra"

httpapi "github.com/sourcenetwork/defradb/api/http"
"github.com/sourcenetwork/defradb/errors"
)

var getCmd = &cobra.Command{
Use: "get [CID]",
Short: "Get a block by its CID from the blockstore.",
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("missing argument: CID")
return NewErrMissingArg("CID")
}
cid := args[0]

endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.BlocksPath, cid)
if err != nil {
return errors.Wrap("failed to join endpoint", err)
return NewErrFailedToJoinEndpoint(err)
}

res, err := http.Get(endpoint.String())
if err != nil {
return errors.Wrap("failed to send get request", err)
return NewErrFailedToSendRequest(err)
}

defer func() {
if e := res.Body.Close(); e != nil {
err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err)
err = NewErrFailedToReadResponseBody(err)
}
}()

response, err := io.ReadAll(res.Body)
if err != nil {
return errors.Wrap("failed to read response body", err)
return NewErrFailedToReadResponseBody(err)
}

stdout, err := os.Stdout.Stat()
if err != nil {
return errors.Wrap("failed to stat stdout", err)
return NewErrFailedToStatStdOut(err)
}
if isFileInfoPipe(stdout) {
cmd.Println(string(response))
} else {
graphlErr, err := hasGraphQLErrors(response)
if err != nil {
return errors.Wrap("failed to handle GraphQL errors", err)
return NewErrFailedToHandleGQLErrors(err)
}
indentedResult, err := indentJSON(response)
if err != nil {
return errors.Wrap("failed to pretty print response", err)
return NewErrFailedToPrettyPrintResponse(err)
}
if graphlErr {
log.FeedbackError(cmd.Context(), indentedResult)
Expand Down
83 changes: 83 additions & 0 deletions cli/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2022 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package cli

import "github.com/sourcenetwork/defradb/errors"

const (
errMissingArg string = "missing arguement"
errMissingArgs string = "missing arguements"
errFailedToCreateRPCClient string = "failed to create RPC client"
errFailedToAddReplicator string = "failed to add replicator, request failed"
errFailedToJoinEndpoint string = "failed to join endpoint"
errFailedToSendRequest string = "failed to send request"
errFailedToReadResponseBody string = "failed to read response body"
errFailedToStatStdOut string = "failed to stat stdout"
errFailedToHandleGQLErrors string = "failed to handle GraphQL errors"
errFailedToPrettyPrintResponse string = "failed to pretty print response"
)

// Errors returnable from this package.
//
// This list is incomplete and undefined errors may also be returned.
// Errors returned from this package may be tested against these errors with errors.Is.
var (
ErrMissingArg = errors.New(errMissingArg)
ErrMissingArgs = errors.New(errMissingArgs)
ErrFailToWrapRPCClient = errors.New(errFailedToCreateRPCClient)
ErrFailedToAddReplicator = errors.New(errFailedToAddReplicator)
ErrFailedToJoinEndpoint = errors.New(errFailedToJoinEndpoint)
ErrFailedToSendRequest = errors.New(errFailedToSendRequest)
ErrFailedToReadResponseBody = errors.New(errFailedToReadResponseBody)
ErrFailedToStatStdOut = errors.New(errFailedToStatStdOut)
ErrFailedToHandleGQLErrors = errors.New(errFailedToHandleGQLErrors)
ErrFailedToPrettyPrintResponse = errors.New(errFailedToPrettyPrintResponse)
)

func NewErrMissingArg(name string) error {
return errors.New(errMissingArg, errors.NewKV("Name", name))
}

func NewErrMissingArgs(count int, provided int) error {
return errors.New(errMissingArgs, errors.NewKV("Required", count), errors.NewKV("Provided", provided))
}

func NewErrFailedToCreateRPCClient(inner error) error {
return errors.Wrap(errFailedToCreateRPCClient, inner)
}

func NewErrFailedToAddReplicator(inner error) error {
return errors.Wrap(errFailedToAddReplicator, inner)
}

func NewErrFailedToJoinEndpoint(inner error) error {
return errors.Wrap(errFailedToJoinEndpoint, inner)
}

func NewErrFailedToSendRequest(inner error) error {
return errors.Wrap(errFailedToSendRequest, inner)
}

func NewErrFailedToReadResponseBody(inner error) error {
return errors.Wrap(errFailedToReadResponseBody, inner)
}

func NewErrFailedToStatStdOut(inner error) error {
return errors.Wrap(errFailedToStatStdOut, inner)
}

func NewErrFailedToHandleGQLErrors(inner error) error {
return errors.Wrap(errFailedToHandleGQLErrors, inner)
}

func NewErrFailedToPrettyPrintResponse(inner error) error {
return errors.Wrap(errFailedToPrettyPrintResponse, inner)
}
6 changes: 2 additions & 4 deletions client/dockey.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import (
"github.com/ipfs/go-cid"
mbase "github.com/multiformats/go-multibase"
uuid "github.com/satori/go.uuid"

"github.com/sourcenetwork/defradb/errors"
)

// DocKey versions.
Expand Down Expand Up @@ -56,7 +54,7 @@ func NewDocKeyV0(dataCID cid.Cid) DocKey {
func NewDocKeyFromString(key string) (DocKey, error) {
parts := strings.SplitN(key, "-", 2)
if len(parts) != 2 {
return DocKey{}, errors.New("malformed DocKey, missing either version or cid")
return DocKey{}, ErrMalformedDocKey
}
versionStr := parts[0]
_, data, err := mbase.Decode(versionStr)
Expand All @@ -69,7 +67,7 @@ func NewDocKeyFromString(key string) (DocKey, error) {
return DocKey{}, err
}
if _, ok := ValidDocKeyVersions[uint16(version)]; !ok {
return DocKey{}, errors.New("invalid DocKey version")
return DocKey{}, ErrInvalidDocKeyVersion
}

uuid, err := uuid.FromString(parts[1])
Expand Down
7 changes: 2 additions & 5 deletions client/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,12 @@ package client

import (
"encoding/json"
"fmt"
"strings"
"sync"

"github.com/fxamacker/cbor/v2"
"github.com/ipfs/go-cid"
mh "github.com/multiformats/go-multihash"

"github.com/sourcenetwork/defradb/errors"
)

// This is the main implementation starting point for accessing the internal Document API
Expand Down Expand Up @@ -89,7 +86,7 @@ func NewDocFromMap(data map[string]any) (*Document, error) {
delete(data, "_key") // remove the key so it isn't parsed further
kstr, ok := k.(string)
if !ok {
return nil, errors.New("provided _key in document must be a string type")
return nil, NewErrUnexpectedType[string]("data[_key]", k)
}
if doc.key, err = NewDocKeyFromString(kstr); err != nil {
return nil, err
Expand Down Expand Up @@ -341,7 +338,7 @@ func (doc *Document) setAndParseType(field string, value any) error {
}

default:
return errors.New(fmt.Sprintf("Unhandled type in raw JSON: %v => %T", field, val))
return NewErrUnhandledType(field, val)
}
return nil
}
Expand Down
Loading

0 comments on commit 6a4a25f

Please sign in to comment.