mirror of
https://github.com/rclone/rclone
synced 2024-12-18 07:45:55 +01:00
e43b5ce5e5
This is possible now that we no longer support go1.12 and brings rclone into line with standard practices in the Go world. This also removes errors.New and errors.Errorf from lib/errors and prefers the stdlib errors package over lib/errors.
308 lines
8.2 KiB
Go
308 lines
8.2 KiB
Go
// Parameter parsing
|
|
|
|
package rc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
)
|
|
|
|
// Params is the input and output type for the Func
|
|
type Params map[string]interface{}
|
|
|
|
// ErrParamNotFound - this is returned from the Get* functions if the
|
|
// parameter isn't found along with a zero value of the requested
|
|
// item.
|
|
//
|
|
// Returning an error of this type from an rc.Func will cause the http
|
|
// method to return http.StatusBadRequest
|
|
type ErrParamNotFound string
|
|
|
|
// Error turns this error into a string
|
|
func (e ErrParamNotFound) Error() string {
|
|
return fmt.Sprintf("Didn't find key %q in input", string(e))
|
|
}
|
|
|
|
// IsErrParamNotFound returns whether err is ErrParamNotFound
|
|
func IsErrParamNotFound(err error) bool {
|
|
_, isNotFound := err.(ErrParamNotFound)
|
|
return isNotFound
|
|
}
|
|
|
|
// NotErrParamNotFound returns true if err != nil and
|
|
// !IsErrParamNotFound(err)
|
|
//
|
|
// This is for checking error returns of the Get* functions to ignore
|
|
// error not found returns and take the default value.
|
|
func NotErrParamNotFound(err error) bool {
|
|
return err != nil && !IsErrParamNotFound(err)
|
|
}
|
|
|
|
// ErrParamInvalid - this is returned from the Get* functions if the
|
|
// parameter is invalid.
|
|
//
|
|
//
|
|
// Returning an error of this type from an rc.Func will cause the http
|
|
// method to return http.StatusBadRequest
|
|
type ErrParamInvalid struct {
|
|
error
|
|
}
|
|
|
|
// NewErrParamInvalid returns new ErrParamInvalid from given error
|
|
func NewErrParamInvalid(err error) ErrParamInvalid {
|
|
return ErrParamInvalid{err}
|
|
}
|
|
|
|
// IsErrParamInvalid returns whether err is ErrParamInvalid
|
|
func IsErrParamInvalid(err error) bool {
|
|
_, isInvalid := err.(ErrParamInvalid)
|
|
return isInvalid
|
|
}
|
|
|
|
// Reshape reshapes one blob of data into another via json serialization
|
|
//
|
|
// out should be a pointer type
|
|
//
|
|
// This isn't a very efficient way of dealing with this!
|
|
func Reshape(out interface{}, in interface{}) error {
|
|
b, err := json.Marshal(in)
|
|
if err != nil {
|
|
return fmt.Errorf("Reshape failed to Marshal: %w", err)
|
|
}
|
|
err = json.Unmarshal(b, out)
|
|
if err != nil {
|
|
return fmt.Errorf("Reshape failed to Unmarshal: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Copy shallow copies the Params
|
|
func (p Params) Copy() (out Params) {
|
|
out = make(Params, len(p))
|
|
for k, v := range p {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Get gets a parameter from the input
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be nil.
|
|
func (p Params) Get(key string) (interface{}, error) {
|
|
value, ok := p[key]
|
|
if !ok {
|
|
return nil, ErrParamNotFound(key)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
// GetHTTPRequest gets a http.Request parameter associated with the request with the key "_request"
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be nil.
|
|
func (p Params) GetHTTPRequest() (*http.Request, error) {
|
|
key := "_request"
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request, ok := value.(*http.Request)
|
|
if !ok {
|
|
return nil, ErrParamInvalid{fmt.Errorf("expecting http.request value for key %q (was %T)", key, value)}
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// GetHTTPResponseWriter gets a http.ResponseWriter parameter associated with the request with the key "_response"
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be nil.
|
|
func (p Params) GetHTTPResponseWriter() (http.ResponseWriter, error) {
|
|
key := "_response"
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request, ok := value.(http.ResponseWriter)
|
|
if !ok {
|
|
return nil, ErrParamInvalid{fmt.Errorf("expecting http.ResponseWriter value for key %q (was %T)", key, value)}
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// GetString gets a string parameter from the input
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be "".
|
|
func (p Params) GetString(key string) (string, error) {
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
str, ok := value.(string)
|
|
if !ok {
|
|
return "", ErrParamInvalid{fmt.Errorf("expecting string value for key %q (was %T)", key, value)}
|
|
}
|
|
return str, nil
|
|
}
|
|
|
|
// GetInt64 gets an int64 parameter from the input
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be 0.
|
|
func (p Params) GetInt64(key string) (int64, error) {
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch x := value.(type) {
|
|
case int:
|
|
return int64(x), nil
|
|
case int64:
|
|
return x, nil
|
|
case float64:
|
|
if x > math.MaxInt64 || x < math.MinInt64 {
|
|
return 0, ErrParamInvalid{fmt.Errorf("key %q (%v) overflows int64 ", key, value)}
|
|
}
|
|
return int64(x), nil
|
|
case string:
|
|
i, err := strconv.ParseInt(x, 10, 0)
|
|
if err != nil {
|
|
return 0, ErrParamInvalid{fmt.Errorf("couldn't parse key %q (%v) as int64: %w", key, value, err)}
|
|
}
|
|
return i, nil
|
|
}
|
|
return 0, ErrParamInvalid{fmt.Errorf("expecting int64 value for key %q (was %T)", key, value)}
|
|
}
|
|
|
|
// GetFloat64 gets a float64 parameter from the input
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be 0.
|
|
func (p Params) GetFloat64(key string) (float64, error) {
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch x := value.(type) {
|
|
case float64:
|
|
return x, nil
|
|
case int:
|
|
return float64(x), nil
|
|
case int64:
|
|
return float64(x), nil
|
|
case string:
|
|
f, err := strconv.ParseFloat(x, 64)
|
|
if err != nil {
|
|
return 0, ErrParamInvalid{fmt.Errorf("couldn't parse key %q (%v) as float64: %w", key, value, err)}
|
|
}
|
|
return f, nil
|
|
}
|
|
return 0, ErrParamInvalid{fmt.Errorf("expecting float64 value for key %q (was %T)", key, value)}
|
|
}
|
|
|
|
// GetBool gets a boolean parameter from the input
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and the returned value will be false.
|
|
func (p Params) GetBool(key string) (bool, error) {
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
switch x := value.(type) {
|
|
case int:
|
|
return x != 0, nil
|
|
case int64:
|
|
return x != 0, nil
|
|
case float64:
|
|
return x != 0, nil
|
|
case bool:
|
|
return x, nil
|
|
case string:
|
|
b, err := strconv.ParseBool(x)
|
|
if err != nil {
|
|
return false, ErrParamInvalid{fmt.Errorf("couldn't parse key %q (%v) as bool: %w", key, value, err)}
|
|
}
|
|
return b, nil
|
|
}
|
|
return false, ErrParamInvalid{fmt.Errorf("expecting bool value for key %q (was %T)", key, value)}
|
|
}
|
|
|
|
// GetStruct gets a struct from key from the input into the struct
|
|
// pointed to by out. out must be a pointer type.
|
|
//
|
|
// If the parameter isn't found then error will be of type
|
|
// ErrParamNotFound and out will be unchanged.
|
|
func (p Params) GetStruct(key string, out interface{}) error {
|
|
value, err := p.Get(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = Reshape(out, value)
|
|
if err != nil {
|
|
if valueStr, ok := value.(string); ok {
|
|
// try to unmarshal as JSON if string
|
|
err = json.Unmarshal([]byte(valueStr), out)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
}
|
|
return ErrParamInvalid{fmt.Errorf("key %q: %w", key, err)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetStructMissingOK works like GetStruct but doesn't return an error
|
|
// if the key is missing
|
|
func (p Params) GetStructMissingOK(key string, out interface{}) error {
|
|
_, ok := p[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return p.GetStruct(key, out)
|
|
}
|
|
|
|
// GetDuration get the duration parameters from in
|
|
func (p Params) GetDuration(key string) (time.Duration, error) {
|
|
s, err := p.GetString(key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
duration, err := fs.ParseDuration(s)
|
|
if err != nil {
|
|
return 0, ErrParamInvalid{fmt.Errorf("parse duration: %w", err)}
|
|
}
|
|
return duration, nil
|
|
}
|
|
|
|
// Error creates the standard response for an errored rc call using an
|
|
// rc.Param from a path, input Params, error and a suggested HTTP
|
|
// response code.
|
|
//
|
|
// It returns a Params and an updated status code
|
|
func Error(path string, in Params, err error, status int) (Params, int) {
|
|
// Adjust the status code for some well known errors
|
|
switch {
|
|
case errors.Is(err, fs.ErrorDirNotFound) || errors.Is(err, fs.ErrorObjectNotFound):
|
|
status = http.StatusNotFound
|
|
case IsErrParamInvalid(err) || IsErrParamNotFound(err):
|
|
status = http.StatusBadRequest
|
|
}
|
|
result := Params{
|
|
"status": status,
|
|
"error": err.Error(),
|
|
"input": in,
|
|
"path": path,
|
|
}
|
|
return result, status
|
|
}
|