github.com/decred/politeia@v1.4.0/politeiawww/legacy/ticketvote/error.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package ticketvote
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"runtime/debug"
    13  	"time"
    14  
    15  	pdv2 "github.com/decred/politeia/politeiad/api/v2"
    16  	pdclient "github.com/decred/politeia/politeiad/client"
    17  	v1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1"
    18  	"github.com/decred/politeia/util"
    19  )
    20  
    21  func respondWithError(w http.ResponseWriter, r *http.Request, format string, err error) {
    22  	// Check if the client dropped the connection
    23  	if err := r.Context().Err(); err == context.Canceled {
    24  		log.Infof("%v %v %v %v client aborted connection",
    25  			util.RemoteAddr(r), r.Method, r.URL, r.Proto)
    26  
    27  		// Client dropped the connection. There is no need to
    28  		// respond further.
    29  		return
    30  	}
    31  
    32  	// Check for expected error types
    33  	var (
    34  		ue  v1.UserErrorReply
    35  		pde pdclient.RespError
    36  	)
    37  	switch {
    38  	case errors.As(err, &ue):
    39  		// Ticketvote user error
    40  		m := fmt.Sprintf("Ticketvote user error: %v %v %v",
    41  			util.RemoteAddr(r), ue.ErrorCode, v1.ErrorCodes[ue.ErrorCode])
    42  		if ue.ErrorContext != "" {
    43  			m += fmt.Sprintf(": %v", ue.ErrorContext)
    44  		}
    45  		log.Infof(m)
    46  		util.RespondWithJSON(w, http.StatusBadRequest,
    47  			v1.UserErrorReply{
    48  				ErrorCode:    ue.ErrorCode,
    49  				ErrorContext: ue.ErrorContext,
    50  			})
    51  		return
    52  
    53  	case errors.As(err, &pde):
    54  		// Politeiad error
    55  		handlePDError(w, r, format, pde)
    56  
    57  	default:
    58  		// Internal server error. Log it and return a 500.
    59  		t := time.Now().Unix()
    60  		e := fmt.Sprintf(format, err)
    61  		log.Errorf("%v %v %v %v Internal error %v: %v",
    62  			util.RemoteAddr(r), r.Method, r.URL, r.Proto, t, e)
    63  
    64  		// If this is a pkg/errors error then we can pull the
    65  		// stack trace out of the error, otherwise, we use the
    66  		// stack trace for this function.
    67  		stack, ok := util.StackTrace(err)
    68  		if !ok {
    69  			stack = string(debug.Stack())
    70  		}
    71  
    72  		log.Errorf("Stacktrace (NOT A REAL CRASH): %v", stack)
    73  
    74  		util.RespondWithJSON(w, http.StatusInternalServerError,
    75  			v1.ServerErrorReply{
    76  				ErrorCode: t,
    77  			})
    78  		return
    79  	}
    80  }
    81  
    82  func handlePDError(w http.ResponseWriter, r *http.Request, format string, pde pdclient.RespError) {
    83  	var (
    84  		pluginID   = pde.ErrorReply.PluginID
    85  		errCode    = pde.ErrorReply.ErrorCode
    86  		errContext = pde.ErrorReply.ErrorContext
    87  	)
    88  	e := convertPDErrorCode(errCode)
    89  	switch {
    90  	case pluginID != "":
    91  		// politeiad plugin error. Log it and return a 400.
    92  		m := fmt.Sprintf("%v Plugin error: %v %v",
    93  			util.RemoteAddr(r), pluginID, errCode)
    94  		if errContext != "" {
    95  			m += fmt.Sprintf(": %v", errContext)
    96  		}
    97  		log.Infof(m)
    98  		util.RespondWithJSON(w, http.StatusBadRequest,
    99  			v1.PluginErrorReply{
   100  				PluginID:     pluginID,
   101  				ErrorCode:    errCode,
   102  				ErrorContext: errContext,
   103  			})
   104  		return
   105  
   106  	case e != v1.ErrorCodeInvalid:
   107  		// User error from politeiad that corresponds to a records user
   108  		// error. Log it and return a 400.
   109  		m := fmt.Sprintf("%v Ticketvote user error: %v %v",
   110  			util.RemoteAddr(r), e, v1.ErrorCodes[e])
   111  		if errContext != "" {
   112  			m += fmt.Sprintf(": %v", errContext)
   113  		}
   114  		log.Infof(m)
   115  		util.RespondWithJSON(w, http.StatusBadRequest,
   116  			v1.UserErrorReply{
   117  				ErrorCode:    e,
   118  				ErrorContext: errContext,
   119  			})
   120  		return
   121  
   122  	default:
   123  		// politeiad error does not correspond to a user error. Log it
   124  		// and return a 500.
   125  		ts := time.Now().Unix()
   126  		log.Errorf("%v %v %v %v Internal error %v: error code "+
   127  			"from politeiad: %v", util.RemoteAddr(r), r.Method, r.URL,
   128  			r.Proto, ts, errCode)
   129  
   130  		util.RespondWithJSON(w, http.StatusInternalServerError,
   131  			v1.ServerErrorReply{
   132  				ErrorCode: ts,
   133  			})
   134  		return
   135  	}
   136  }
   137  
   138  func convertPDErrorCode(errCode uint32) v1.ErrorCodeT {
   139  	// This list is only populated with politeiad errors that we expect
   140  	// for the ticketvote plugin commands. Any politeiad errors not
   141  	// included in this list will cause politeiawww to 500.
   142  	switch pdv2.ErrorCodeT(errCode) {
   143  	case pdv2.ErrorCodeRecordNotFound:
   144  		return v1.ErrorCodeRecordNotFound
   145  	case pdv2.ErrorCodeTokenInvalid:
   146  		return v1.ErrorCodeTokenInvalid
   147  	case pdv2.ErrorCodeRecordLocked:
   148  		return v1.ErrorCodeRecordLocked
   149  	case pdv2.ErrorCodeDuplicatePayload:
   150  		return v1.ErrorCodeDuplicatePayload
   151  	}
   152  	return v1.ErrorCodeInvalid
   153  }