lbryio/chainquery

View on GitHub
lbrycrd/client.go

Summary

Maintainability
A
35 mins
Test Coverage
package lbrycrd

import (
    "bytes"
    "encoding/json"
    "fmt"
    "reflect"

    "github.com/lbryio/lbry.go/v2/extras/errors"
    upstream "github.com/lbryio/lbry.go/v2/lbrycrd"
    "github.com/mitchellh/mapstructure"
    "github.com/shopspring/decimal"
    "github.com/sirupsen/logrus"
)

//LBRYcrdClient is the client for communicating with LBRYcrd from Chainquery.
var LBRYcrdClient *upstream.Client

// LBRYcrdURL is the connection string for lbrycrd and is set from the configuration
var LBRYcrdURL string

// Init initializes a client with settings from the configuration of chainquery
func Init() *upstream.Client {
    chain, err := GetChainParams()
    if err != nil {
        panic(err)
    }
    lbrycrdClient, err := upstream.New(LBRYcrdURL, chain)
    if err != nil {
        logrus.Panic("Initializing LBRYcrd Client: ", err)
    }
    LBRYcrdClient = lbrycrdClient
    _, err = GetBlockCount()
    if err != nil {
        logrus.Panicf("Error connecting to lbrycrd: %+v", err)
    }
    return lbrycrdClient
}

func call(response interface{}, command string, params ...interface{}) error {
    result, err := callNoDecode(command, params...)
    if err != nil {
        return err
    }
    return decode(result, response)
}

func callNoDecode(command string, params ...interface{}) (interface{}, error) {
    //logrus.Debug("jsonrpc: " + command + " " + debugParams(params))
    var err error

    encodedParams := make([]json.RawMessage, len(params))
    for i, p := range params {
        encodedParams[i], err = json.Marshal(p)
        if err != nil {
            return nil, errors.Err(err)
        }
    }

    encodedRes, err := LBRYcrdClient.RawRequest(command, encodedParams)
    if err != nil {
        return nil, err
    }

    var res interface{}
    decoder := json.NewDecoder(bytes.NewReader(encodedRes))
    decoder.UseNumber()
    err = decoder.Decode(&res)
    return res, errors.Err(err)

}

func decode(data interface{}, targetStruct interface{}) error {
    config := &mapstructure.DecoderConfig{
        Metadata: nil,
        Result:   targetStruct,
        TagName:  "json",
        //WeaklyTypedInput: true,
        DecodeHook: fixDecodeProto,
    }
    decoder, err := mapstructure.NewDecoder(config)
    if err != nil {
        return errors.Wrap(err, 0)
    }
    err = decoder.Decode(data)
    if err != nil {
        return errors.Wrap(err, 0)
    }
    return nil
}

//ToDo - Can we just use the decodeInt function? Is it double for nothing?
func decodeNumber(data interface{}) (decimal.Decimal, error) {
    var number string
    switch d := data.(type) {
    case json.Number:
        number = d.String()
    case string:
        number = d
    case nil:
        number = "0"
    default:
        fmt.Printf("I don't know about type %T!\n", d)
        return decimal.Decimal{}, errors.Base("unexpected number type ")
    }

    dec, err := decimal.NewFromString(number)
    if err != nil {
        return decimal.Decimal{}, errors.Wrap(err, 0)
    }

    return dec, nil
}

type protoFunc func(interface{}) (interface{}, error)

type protoMap map[reflect.Type]protoFunc

var decodeMap = protoMap{
    reflect.TypeOf(uint64(0)):         decodeInt,
    reflect.TypeOf([]byte{}):          decodeBytes,
    reflect.TypeOf(decimal.Decimal{}): decodeFloat,
}

func fixDecodeProto(src, dest reflect.Type, data interface{}) (interface{}, error) {

    if f, ok := decodeMap[dest]; ok {
        return f(data)
    }

    return data, nil
}

func decodeInt(data interface{}) (interface{}, error) {
    if n, ok := data.(json.Number); ok {
        val, err := n.Int64()
        if err != nil {
            return nil, errors.Wrap(err, 0)
        } else if val < 0 {
            return nil, errors.Base("must be unsigned int")
        }
        return uint64(val), nil
    }
    return data, nil
}

func decodeFloat(data interface{}) (interface{}, error) {
    if n, ok := data.(json.Number); ok {
        val, err := n.Float64()
        if err != nil {
            return nil, errors.Wrap(err, 0)
        }
        return decimal.NewFromFloat(val), nil
    } else if s, ok := data.(string); ok {
        d, err := decimal.NewFromString(s)
        if err != nil {
            return nil, errors.Wrap(err, 0)
        }
        return d, nil
    }
    return data, nil
}

func decodeBytes(data interface{}) (interface{}, error) {
    if s, ok := data.(string); ok {
        return []byte(s), nil
    }
    return data, nil
}