mirror of
https://github.com/ipfs/kubo.git
synced 2025-07-04 13:27:14 +08:00
79
commands/cli/parse.go
Normal file
79
commands/cli/parse.go
Normal file
@ -0,0 +1,79 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jbenet/go-ipfs/commands"
|
||||
)
|
||||
|
||||
// Parse parses the input commandline string (cmd, flags, and args).
|
||||
// returns the corresponding command Request object.
|
||||
func Parse(input []string, root *commands.Command) (commands.Request, error) {
|
||||
path, input := parsePath(input, root)
|
||||
opts, args, err := parseOptions(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return commands.NewRequest(path, opts, args, nil), nil
|
||||
}
|
||||
|
||||
// parsePath gets the command path from the command line input
|
||||
func parsePath(input []string, root *commands.Command) ([]string, []string) {
|
||||
cmd := root
|
||||
i := 0
|
||||
|
||||
for _, blob := range input {
|
||||
if strings.HasPrefix(blob, "-") {
|
||||
break
|
||||
}
|
||||
|
||||
cmd := cmd.Subcommand(blob)
|
||||
if cmd == nil {
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
return input[:i], input[i:]
|
||||
}
|
||||
|
||||
// parseOptions parses the raw string values of the given options
|
||||
// returns the parsed options as strings, along with the CLI args
|
||||
func parseOptions(input []string) (map[string]interface{}, []string, error) {
|
||||
opts := make(map[string]interface{})
|
||||
args := []string{}
|
||||
|
||||
for i := 0; i < len(input); i++ {
|
||||
blob := input[i]
|
||||
|
||||
if strings.HasPrefix(blob, "-") {
|
||||
name := blob[1:]
|
||||
value := ""
|
||||
|
||||
// support single and double dash
|
||||
if strings.HasPrefix(name, "-") {
|
||||
name = name[1:]
|
||||
}
|
||||
|
||||
if strings.Contains(name, "=") {
|
||||
split := strings.SplitN(name, "=", 2)
|
||||
name = split[0]
|
||||
value = split[1]
|
||||
}
|
||||
|
||||
if _, ok := opts[name]; ok {
|
||||
return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
|
||||
}
|
||||
|
||||
opts[name] = value
|
||||
|
||||
} else {
|
||||
args = append(args, blob)
|
||||
}
|
||||
}
|
||||
|
||||
return opts, args, nil
|
||||
}
|
47
commands/cli/parse_test.go
Normal file
47
commands/cli/parse_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/jbenet/go-ipfs/commands"
|
||||
)
|
||||
|
||||
func TestOptionParsing(t *testing.T) {
|
||||
cmd := &commands.Command{
|
||||
Options: []commands.Option{
|
||||
commands.Option{Names: []string{"b"}, Type: commands.String},
|
||||
},
|
||||
Subcommands: map[string]*commands.Command{
|
||||
"test": &commands.Command{},
|
||||
},
|
||||
}
|
||||
|
||||
opts, input, err := parseOptions([]string{"--beep", "-boop=lol", "test2", "-c", "beep", "--foo=5"})
|
||||
/*for k, v := range opts {
|
||||
fmt.Printf("%s: %s\n", k, v)
|
||||
}
|
||||
fmt.Printf("%s\n", input)*/
|
||||
if err != nil {
|
||||
t.Error("Should have passed")
|
||||
}
|
||||
if len(opts) != 4 || opts["beep"] != "" || opts["boop"] != "lol" || opts["c"] != "" || opts["foo"] != "5" {
|
||||
t.Error("Returned options were defferent than expected: %v", opts)
|
||||
}
|
||||
if len(input) != 2 || input[0] != "test2" || input[1] != "beep" {
|
||||
t.Error("Returned input was different than expected: %v", input)
|
||||
}
|
||||
|
||||
_, _, err = parseOptions([]string{"-beep=1", "-boop=2", "-beep=3"})
|
||||
if err == nil {
|
||||
t.Error("Should have failed (duplicate option name)")
|
||||
}
|
||||
|
||||
path, args := parsePath([]string{"test", "beep", "boop"}, cmd)
|
||||
if len(path) != 1 || path[0] != "test" {
|
||||
t.Error("Returned path was defferent than expected: %v", path)
|
||||
}
|
||||
if len(args) != 2 || args[0] != "beep" || args[1] != "boop" {
|
||||
t.Error("Returned args were different than expected: %v", args)
|
||||
}
|
||||
}
|
122
commands/command.go
Normal file
122
commands/command.go
Normal file
@ -0,0 +1,122 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
u "github.com/jbenet/go-ipfs/util"
|
||||
)
|
||||
|
||||
var log = u.Logger("command")
|
||||
|
||||
// Function is the type of function that Commands use.
|
||||
// It reads from the Request, and writes results to the Response.
|
||||
type Function func(Request, Response)
|
||||
|
||||
// Command is a runnable command, with input arguments and options (flags).
|
||||
// It can also have Subcommands, to group units of work into sets.
|
||||
type Command struct {
|
||||
Help string
|
||||
Options []Option
|
||||
Run Function
|
||||
Subcommands map[string]*Command
|
||||
}
|
||||
|
||||
// ErrNotCallable signals a command that cannot be called.
|
||||
var ErrNotCallable = errors.New("This command can't be called directly. Try one of its subcommands.")
|
||||
|
||||
// Call invokes the command for the given Request
|
||||
func (c *Command) Call(req Request) Response {
|
||||
res := NewResponse(req)
|
||||
|
||||
cmds, err := c.Resolve(req.Path())
|
||||
if err != nil {
|
||||
res.SetError(err, ErrClient)
|
||||
return res
|
||||
}
|
||||
cmd := cmds[len(cmds)-1]
|
||||
|
||||
if cmd.Run == nil {
|
||||
res.SetError(ErrNotCallable, ErrClient)
|
||||
return res
|
||||
}
|
||||
|
||||
options, err := c.GetOptions(req.Path())
|
||||
if err != nil {
|
||||
res.SetError(err, ErrClient)
|
||||
return res
|
||||
}
|
||||
|
||||
err = req.ConvertOptions(options)
|
||||
if err != nil {
|
||||
res.SetError(err, ErrClient)
|
||||
return res
|
||||
}
|
||||
|
||||
cmd.Run(req, res)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Resolve gets the subcommands at the given path
|
||||
func (c *Command) Resolve(path []string) ([]*Command, error) {
|
||||
cmds := make([]*Command, len(path)+1)
|
||||
cmds[0] = c
|
||||
|
||||
cmd := c
|
||||
for i, name := range path {
|
||||
cmd = cmd.Subcommand(name)
|
||||
|
||||
if cmd == nil {
|
||||
pathS := strings.Join(path[0:i], "/")
|
||||
return nil, fmt.Errorf("Undefined command: '%s'", pathS)
|
||||
}
|
||||
|
||||
cmds[i+1] = cmd
|
||||
}
|
||||
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
// Get resolves and returns the Command addressed by path
|
||||
func (c *Command) Get(path []string) (*Command, error) {
|
||||
cmds, err := c.Resolve(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmds[len(cmds)-1], nil
|
||||
}
|
||||
|
||||
// GetOptions gets the options in the given path of commands
|
||||
func (c *Command) GetOptions(path []string) (map[string]Option, error) {
|
||||
options := make([]Option, len(c.Options))
|
||||
|
||||
cmds, err := c.Resolve(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, globalCommand)
|
||||
|
||||
for _, cmd := range cmds {
|
||||
options = append(options, cmd.Options...)
|
||||
}
|
||||
|
||||
optionsMap := make(map[string]Option)
|
||||
for _, opt := range options {
|
||||
for _, name := range opt.Names {
|
||||
if _, found := optionsMap[name]; found {
|
||||
return nil, fmt.Errorf("Option name '%s' used multiple times", name)
|
||||
}
|
||||
|
||||
optionsMap[name] = opt
|
||||
}
|
||||
}
|
||||
|
||||
return optionsMap, nil
|
||||
}
|
||||
|
||||
// Subcommand returns the subcommand with the given id
|
||||
func (c *Command) Subcommand(id string) *Command {
|
||||
return c.Subcommands[id]
|
||||
}
|
146
commands/command_test.go
Normal file
146
commands/command_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
package commands
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOptionValidation(t *testing.T) {
|
||||
cmd := Command{
|
||||
Options: []Option{
|
||||
Option{[]string{"b", "beep"}, Int},
|
||||
Option{[]string{"B", "boop"}, String},
|
||||
},
|
||||
Run: func(req Request, res Response) {},
|
||||
}
|
||||
|
||||
req := NewEmptyRequest()
|
||||
req.SetOption("foo", 5)
|
||||
res := cmd.Call(req)
|
||||
if res.Error() == nil {
|
||||
t.Error("Should have failed (unrecognized option)")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("beep", 5)
|
||||
req.SetOption("b", 10)
|
||||
res = cmd.Call(req)
|
||||
if res.Error() == nil {
|
||||
t.Error("Should have failed (duplicate options)")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("beep", "foo")
|
||||
res = cmd.Call(req)
|
||||
if res.Error() == nil {
|
||||
t.Error("Should have failed (incorrect type)")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("beep", 5)
|
||||
res = cmd.Call(req)
|
||||
if res.Error() != nil {
|
||||
t.Error(res.Error(), "Should have passed")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("beep", 5)
|
||||
req.SetOption("boop", "test")
|
||||
res = cmd.Call(req)
|
||||
if res.Error() != nil {
|
||||
t.Error("Should have passed")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("b", 5)
|
||||
req.SetOption("B", "test")
|
||||
res = cmd.Call(req)
|
||||
if res.Error() != nil {
|
||||
t.Error("Should have passed")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption(EncShort, "json")
|
||||
res = cmd.Call(req)
|
||||
if res.Error() != nil {
|
||||
t.Error("Should have passed")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("b", "100")
|
||||
res = cmd.Call(req)
|
||||
if res.Error() != nil {
|
||||
t.Error("Should have passed")
|
||||
}
|
||||
|
||||
req = NewEmptyRequest()
|
||||
req.SetOption("b", ":)")
|
||||
res = cmd.Call(req)
|
||||
if res.Error == nil {
|
||||
t.Error(res.Error, "Should have failed (string value not convertible to int)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistration(t *testing.T) {
|
||||
noop := func(req Request, res Response) {}
|
||||
|
||||
cmdA := &Command{
|
||||
Options: []Option{
|
||||
Option{[]string{"beep"}, Int},
|
||||
},
|
||||
Run: noop,
|
||||
}
|
||||
|
||||
cmdB := &Command{
|
||||
Options: []Option{
|
||||
Option{[]string{"beep"}, Int},
|
||||
},
|
||||
Run: noop,
|
||||
Subcommands: map[string]*Command{
|
||||
"a": cmdA,
|
||||
},
|
||||
}
|
||||
|
||||
cmdC := &Command{
|
||||
Options: []Option{
|
||||
Option{[]string{"encoding"}, String},
|
||||
},
|
||||
Run: noop,
|
||||
}
|
||||
|
||||
res := cmdB.Call(NewRequest([]string{"a"}, nil, nil, nil))
|
||||
if res.Error() == nil {
|
||||
t.Error("Should have failed (option name collision)")
|
||||
}
|
||||
|
||||
res = cmdC.Call(NewEmptyRequest())
|
||||
if res.Error() == nil {
|
||||
t.Error("Should have failed (option name collision with global options)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolving(t *testing.T) {
|
||||
cmdC := &Command{}
|
||||
cmdB := &Command{
|
||||
Subcommands: map[string]*Command{
|
||||
"c": cmdC,
|
||||
},
|
||||
}
|
||||
cmdB2 := &Command{}
|
||||
cmdA := &Command{
|
||||
Subcommands: map[string]*Command{
|
||||
"b": cmdB,
|
||||
"B": cmdB2,
|
||||
},
|
||||
}
|
||||
cmd := &Command{
|
||||
Subcommands: map[string]*Command{
|
||||
"a": cmdA,
|
||||
},
|
||||
}
|
||||
|
||||
cmds, err := cmd.Resolve([]string{"a", "b", "c"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(cmds) != 4 || cmds[0] != cmd || cmds[1] != cmdA || cmds[2] != cmdB || cmds[3] != cmdC {
|
||||
t.Error("Returned command path is different than expected", cmds)
|
||||
}
|
||||
}
|
39
commands/option.go
Normal file
39
commands/option.go
Normal file
@ -0,0 +1,39 @@
|
||||
package commands
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Types of Command options
|
||||
const (
|
||||
Invalid = reflect.Invalid
|
||||
Bool = reflect.Bool
|
||||
Int = reflect.Int
|
||||
Uint = reflect.Uint
|
||||
Float = reflect.Float64
|
||||
String = reflect.String
|
||||
)
|
||||
|
||||
// Option is used to specify a field that will be provided by a consumer
|
||||
type Option struct {
|
||||
Names []string // a list of unique names to
|
||||
Type reflect.Kind // value must be this type
|
||||
|
||||
// TODO: add more features(?):
|
||||
//Default interface{} // the default value (ignored if `Required` is true)
|
||||
//Required bool // whether or not the option must be provided
|
||||
}
|
||||
|
||||
// Flag names
|
||||
const (
|
||||
EncShort = "enc"
|
||||
EncLong = "encoding"
|
||||
)
|
||||
|
||||
// options that are used by this package
|
||||
var globalOptions = []Option{
|
||||
Option{[]string{EncShort, EncLong}, String},
|
||||
}
|
||||
|
||||
// the above array of Options, wrapped in a Command
|
||||
var globalCommand = &Command{
|
||||
Options: globalOptions,
|
||||
}
|
143
commands/request.go
Normal file
143
commands/request.go
Normal file
@ -0,0 +1,143 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type optMap map[string]interface{}
|
||||
|
||||
// Request represents a call to a command from a consumer
|
||||
type Request interface {
|
||||
Path() []string
|
||||
Option(name string) (interface{}, bool)
|
||||
SetOption(name string, val interface{})
|
||||
Arguments() []string
|
||||
Stream() io.Reader
|
||||
SetStream(io.Reader)
|
||||
|
||||
ConvertOptions(options map[string]Option) error
|
||||
}
|
||||
|
||||
type request struct {
|
||||
path []string
|
||||
options optMap
|
||||
arguments []string
|
||||
in io.Reader
|
||||
}
|
||||
|
||||
// Path returns the command path of this request
|
||||
func (r *request) Path() []string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
// Option returns the value of the option for given name.
|
||||
func (r *request) Option(name string) (interface{}, bool) {
|
||||
val, err := r.options[name]
|
||||
return val, err
|
||||
}
|
||||
|
||||
// SetOption sets the value of the option for given name.
|
||||
func (r *request) SetOption(name string, val interface{}) {
|
||||
r.options[name] = val
|
||||
}
|
||||
|
||||
// Arguments returns the arguments slice
|
||||
func (r *request) Arguments() []string {
|
||||
return r.arguments
|
||||
}
|
||||
|
||||
// Stream returns the input stream Reader
|
||||
func (r *request) Stream() io.Reader {
|
||||
return r.in
|
||||
}
|
||||
|
||||
// SetStream sets the value of the input stream Reader
|
||||
func (r *request) SetStream(in io.Reader) {
|
||||
r.in = in
|
||||
}
|
||||
|
||||
type converter func(string) (interface{}, error)
|
||||
|
||||
var converters = map[reflect.Kind]converter{
|
||||
Bool: func(v string) (interface{}, error) {
|
||||
if v == "" {
|
||||
return true, nil
|
||||
}
|
||||
return strconv.ParseBool(v)
|
||||
},
|
||||
Int: func(v string) (interface{}, error) {
|
||||
return strconv.ParseInt(v, 0, 32)
|
||||
},
|
||||
Uint: func(v string) (interface{}, error) {
|
||||
return strconv.ParseInt(v, 0, 32)
|
||||
},
|
||||
Float: func(v string) (interface{}, error) {
|
||||
return strconv.ParseFloat(v, 64)
|
||||
},
|
||||
}
|
||||
|
||||
func (r *request) ConvertOptions(options map[string]Option) error {
|
||||
converted := make(map[string]interface{})
|
||||
|
||||
for k, v := range r.options {
|
||||
opt, ok := options[k]
|
||||
if !ok {
|
||||
return fmt.Errorf("Unrecognized option: '%s'", k)
|
||||
}
|
||||
|
||||
kind := reflect.TypeOf(v).Kind()
|
||||
var value interface{}
|
||||
|
||||
if kind != opt.Type {
|
||||
if kind == String {
|
||||
convert := converters[opt.Type]
|
||||
val, err := convert(v.(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not convert string value '%s' to type '%s'",
|
||||
v, opt.Type.String())
|
||||
}
|
||||
value = val
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
|
||||
k, opt.Type.String(), kind.String())
|
||||
}
|
||||
} else {
|
||||
value = v
|
||||
}
|
||||
|
||||
for _, name := range opt.Names {
|
||||
if _, ok := r.options[name]; name != k && ok {
|
||||
return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
|
||||
k, name)
|
||||
}
|
||||
|
||||
converted[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
r.options = converted
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewEmptyRequest initializes an empty request
|
||||
func NewEmptyRequest() Request {
|
||||
return NewRequest(nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
// NewRequest returns a request initialized with given arguments
|
||||
func NewRequest(path []string, opts optMap, args []string, in io.Reader) Request {
|
||||
if path == nil {
|
||||
path = make([]string, 0)
|
||||
}
|
||||
if opts == nil {
|
||||
opts = make(map[string]interface{})
|
||||
}
|
||||
if args == nil {
|
||||
args = make([]string, 0)
|
||||
}
|
||||
return &request{path, opts, args, in}
|
||||
}
|
127
commands/response.go
Normal file
127
commands/response.go
Normal file
@ -0,0 +1,127 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrorType signfies a category of errors
|
||||
type ErrorType uint
|
||||
|
||||
// ErrorTypes convey what category of error ocurred
|
||||
const (
|
||||
ErrNormal ErrorType = iota // general errors
|
||||
ErrClient // error was caused by the client, (e.g. invalid CLI usage)
|
||||
// TODO: add more types of errors for better error-specific handling
|
||||
)
|
||||
|
||||
// Error is a struct for marshalling errors
|
||||
type Error struct {
|
||||
Message string
|
||||
Code ErrorType
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%d error: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// EncodingType defines a supported encoding
|
||||
type EncodingType string
|
||||
|
||||
// Supported EncodingType constants.
|
||||
const (
|
||||
JSON = "json"
|
||||
XML = "xml"
|
||||
// TODO: support more encoding types
|
||||
)
|
||||
|
||||
// Marshaller is a function used by coding types.
|
||||
// TODO this should just be a `coding.Codec`
|
||||
type Marshaller func(v interface{}) ([]byte, error)
|
||||
|
||||
var marshallers = map[EncodingType]Marshaller{
|
||||
JSON: json.Marshal,
|
||||
XML: xml.Marshal,
|
||||
}
|
||||
|
||||
// Response is the result of a command request. Handlers write to the response,
|
||||
// setting Error or Value. Response is returned to the client.
|
||||
type Response interface {
|
||||
Request() Request
|
||||
|
||||
// Set/Return the response Error
|
||||
SetError(err error, code ErrorType)
|
||||
Error() error
|
||||
|
||||
// Sets/Returns the response value
|
||||
SetValue(interface{})
|
||||
Value() interface{}
|
||||
|
||||
// Marshal marshals out the response into a buffer. It uses the EncodingType
|
||||
// on the Request to chose a Marshaller (Codec).
|
||||
Marshal() ([]byte, error)
|
||||
}
|
||||
|
||||
type response struct {
|
||||
req Request
|
||||
err *Error
|
||||
value interface{}
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func (r *response) Request() Request {
|
||||
return r.req
|
||||
}
|
||||
|
||||
func (r *response) Value() interface{} {
|
||||
return r.value
|
||||
}
|
||||
|
||||
func (r *response) SetValue(v interface{}) {
|
||||
r.value = v
|
||||
}
|
||||
|
||||
func (r *response) Stream() io.Writer {
|
||||
return r.out
|
||||
}
|
||||
|
||||
func (r *response) Error() error {
|
||||
if r.err == nil {
|
||||
return nil
|
||||
}
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *response) SetError(err error, code ErrorType) {
|
||||
r.err = &Error{Message: err.Error(), Code: code}
|
||||
}
|
||||
|
||||
func (r *response) Marshal() ([]byte, error) {
|
||||
if r.err == nil && r.value == nil {
|
||||
return nil, fmt.Errorf("No error or value set, there is nothing to marshal")
|
||||
}
|
||||
|
||||
enc, ok := r.req.Option(EncShort)
|
||||
if !ok || enc.(string) == "" {
|
||||
return nil, fmt.Errorf("No encoding type was specified")
|
||||
}
|
||||
encType := EncodingType(strings.ToLower(enc.(string)))
|
||||
|
||||
marshaller, ok := marshallers[encType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
|
||||
}
|
||||
|
||||
if r.err != nil {
|
||||
return marshaller(r.err)
|
||||
}
|
||||
return marshaller(r.value)
|
||||
}
|
||||
|
||||
// NewResponse returns a response to match given Request
|
||||
func NewResponse(req Request) Response {
|
||||
return &response{req: req}
|
||||
}
|
52
commands/response_test.go
Normal file
52
commands/response_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestOutput struct {
|
||||
Foo, Bar string
|
||||
Baz int
|
||||
}
|
||||
|
||||
func TestMarshalling(t *testing.T) {
|
||||
req := NewEmptyRequest()
|
||||
|
||||
res := NewResponse(req)
|
||||
res.SetValue(TestOutput{"beep", "boop", 1337})
|
||||
|
||||
// get command global options so we can set the encoding option
|
||||
cmd := Command{}
|
||||
options, err := cmd.GetOptions(nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = res.Marshal()
|
||||
if err == nil {
|
||||
t.Error("Should have failed (no encoding type specified in request)")
|
||||
}
|
||||
|
||||
req.SetOption(EncShort, JSON)
|
||||
req.ConvertOptions(options)
|
||||
|
||||
bytes, err := res.Marshal()
|
||||
if err != nil {
|
||||
t.Error(err, "Should have passed")
|
||||
}
|
||||
output := string(bytes)
|
||||
if output != "{\"Foo\":\"beep\",\"Bar\":\"boop\",\"Baz\":1337}" {
|
||||
t.Error("Incorrect JSON output")
|
||||
}
|
||||
|
||||
res.SetError(fmt.Errorf("You broke something!"), ErrClient)
|
||||
bytes, err = res.Marshal()
|
||||
if err != nil {
|
||||
t.Error("Should have passed")
|
||||
}
|
||||
output = string(bytes)
|
||||
if output != "{\"Message\":\"You broke something!\",\"Code\":1}" {
|
||||
t.Error("Incorrect JSON output")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user