Skip to content

Commit

Permalink
Add support for YAML input
Browse files Browse the repository at this point in the history
  • Loading branch information
flimzy committed Oct 15, 2020
1 parent 6c6136d commit cf02f61
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 9 deletions.
14 changes: 14 additions & 0 deletions cmd/kouchctl/cmd/put_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ func Test_put_RunE(t *testing.T) {
stdin: `{"foo":"bar"}`,
}
})
tests.Add("yaml data string", func(t *testing.T) interface{} {
s := testy.ServeResponseValidator(t, &http.Response{
Body: ioutil.NopCloser(strings.NewReader(`{"status":"ok"}`)),
}, func(t *testing.T, req *http.Request) {
defer req.Body.Close() // nolint:errcheck
if d := testy.DiffAsJSON(testy.Snapshot(t), req.Body); d != nil {
t.Error(d)
}
})

return cmdTest{
args: []string{"--debug", "put", s.URL + "/foo/bar", "--data", `foo: bar`},
}
})

tests.Run(t, func(t *testing.T, tt cmdTest) {
tt.Test(t)
Expand Down
81 changes: 73 additions & 8 deletions cmd/kouchctl/doc/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,104 @@
package doc

import (
"encoding/json"
"io"
"io/ioutil"
"os"
"strings"

"github.com/go-kivik/xkivik/v4/cmd/kouchctl/errors"
"github.com/icza/dyno"
"github.com/spf13/pflag"
"gopkg.in/yaml.v3"

"github.com/go-kivik/xkivik/v4/cmd/kouchctl/errors"
)

type Doc struct {
data string
file string
data string
file string
yamlData string
yamlFile string
}

func New() *Doc {
return &Doc{}
}

func (d *Doc) ConfigFlags(pf *pflag.FlagSet) {
pf.StringVarP(&d.data, "data", "d", "", "Document data. Should be valid JSON or YAML.")
pf.StringVarP(&d.data, "data", "d", "", "JSON document data.")
pf.StringVarP(&d.file, "data-file", "D", "", "Read document data from the named file. Use - for stdin.")
pf.StringVarP(&d.yamlData, "yaml-data", "y", "", "YAML document data.")
pf.StringVarP(&d.yamlFile, "yaml-data-file", "Y", "", "Read document data from the named YAML file. Use - for stdin.")
}

// jsonReader converts an io.Reader into a json.Marshaler.
type jsonReader struct{ io.Reader }

var _ json.Marshaler = (*jsonReader)(nil)

// MarshalJSON returns the reader's contents. If the reader is also an io.Closer,
// it is closed.
func (r *jsonReader) MarshalJSON() ([]byte, error) {
if c, ok := r.Reader.(io.Closer); ok {
defer c.Close() // nolint:errcheck
}
buf, err := ioutil.ReadAll(r)
return buf, errors.Code(errors.ErrIO, err)
}

// jsonObject turns an arbitrary object into a json.Marshaler.
type jsonObject struct {
i interface{}
}

var _ json.Marshaler = &jsonObject{}

func (o *jsonObject) MarshalJSON() ([]byte, error) {
return json.Marshal(o.i)
}

func (d *Doc) Data() (io.Reader, error) {
// Data returns a JSON-marshalable object.
func (d *Doc) Data() (json.Marshaler, error) {
if d.data != "" {
return strings.NewReader(d.data), nil
return json.RawMessage(d.data), nil
}
switch d.file {
case "-":
return os.Stdin, nil
return &jsonReader{os.Stdin}, nil
case "":
default:
f, err := os.Open(d.file)
return f, errors.Code(errors.ErrNoInput, err)
return &jsonReader{f}, errors.Code(errors.ErrNoInput, err)
}
if d.yamlData != "" {
return yaml2json(ioutil.NopCloser(strings.NewReader(d.yamlData)))
}
switch d.yamlFile {
case "-":
return yaml2json(os.Stdin)
case "":
default:
f, err := os.Open(d.yamlFile)
if err != nil {
return nil, errors.Code(errors.ErrNoInput, err)
}
return yaml2json(f)
}
return nil, errors.Code(errors.ErrUsage, "no document provided")
}

func yaml2json(r io.ReadCloser) (json.Marshaler, error) {
defer r.Close() // nolint:errcheck

buf, err := ioutil.ReadAll(r)
if err != nil {
return nil, errors.Code(errors.ErrIO, err)
}

var doc interface{}
if err := yaml.Unmarshal(buf, &doc); err != nil {
return nil, errors.Code(errors.ErrData, err)
}
return &jsonObject{dyno.ConvertMapI2MapS(doc)}, nil
}
100 changes: 100 additions & 0 deletions cmd/kouchctl/doc/doc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

// Package doc provides a wrapper for a JSON document, and related command line
// argument handling.

package doc

import (
"encoding/json"
"strings"
"testing"

"github.com/go-kivik/xkivik/v4/cmd/kouchctl/errors"
"github.com/spf13/pflag"
"gitlab.com/flimzy/testy"
)

func Test_Data(t *testing.T) {
type tt struct {
args []string
stdin string
status int
err string
}

tests := testy.NewTable()
tests.Add("no doc", tt{
status: errors.ErrUsage,
err: "no document provided",
})
tests.Add("stdin", tt{
args: []string{"--data-file", "-"},
stdin: `{"foo":"bar"}`,
})
tests.Add("string", tt{
args: []string{"--data", `{"xyz":123}`},
})
tests.Add("file", tt{
args: []string{"--data-file", "./testdata/doc.json"},
})
tests.Add("missing file", tt{
args: []string{"--data-file", "./testdata/missing.json"},
status: errors.ErrNoInput,
err: "open ./testdata/missing.json: no such file or directory",
})
tests.Add("yaml string", tt{
args: []string{"--yaml-data", `foo: bar`},
})
tests.Add("yaml stdin", tt{
args: []string{"--yaml-data-file", `-`},
stdin: "foo: 1234",
})
tests.Add("yaml file", tt{
args: []string{"--yaml-data-file", `./testdata/doc.yaml`},
})
tests.Add("yaml file missing", tt{
args: []string{"--yaml-data-file", `./testdata/missing.yaml`},
status: errors.ErrNoInput,
err: "open ./testdata/missing.yaml: no such file or directory",
})

tests.Run(t, func(t *testing.T, tt tt) {
doc := New()
flags := pflag.NewFlagSet("x", pflag.ContinueOnError)
doc.ConfigFlags(flags)

set := func(flag *pflag.Flag, value string) error {
return flags.Set(flag.Name, value)
}

if err := flags.ParseAll(tt.args, set); err != nil {
t.Fatal(err)
}

var r json.Marshaler
var err error
_, _ = testy.RedirIO(strings.NewReader(tt.stdin), func() {
r, err = doc.Data()
})

if status := errors.InspectErrorCode(err); status != tt.status {
t.Errorf("Unexpected error status. Want %d, got %d", tt.status, status)
}
testy.Error(t, tt.err, err)

if d := testy.DiffAsJSON(testy.Snapshot(t), r); d != nil {
t.Error(d)
}
})
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/Test_Data_file
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": false
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/Test_Data_stdin
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": "bar"
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/Test_Data_string
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xyz": 123
}
5 changes: 5 additions & 0 deletions cmd/kouchctl/doc/testdata/Test_Data_yaml_file
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"_id": "bar",
"_rev": "1-xxx",
"foo": "bar"
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/Test_Data_yaml_stdin
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": 1234
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/Test_Data_yaml_string
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": "bar"
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/doc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo":false
}
3 changes: 3 additions & 0 deletions cmd/kouchctl/doc/testdata/doc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_id: bar
_rev: 1-xxx
foo: bar
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ require (
github.com/cenkalti/backoff/v4 v4.1.0
github.com/go-kivik/couchdb/v4 v4.0.0-20201011105359-723a6da29d45
github.com/go-kivik/fsdb/v4 v4.0.0-20201011102818-b9ac3fbc1e55
github.com/go-kivik/kivik/v4 v4.0.0-20201011094523-ab1764d67e62
github.com/go-kivik/kivik/v4 v4.0.0-20201015190251-5d5c2f1c89fa
github.com/go-kivik/kivikmock/v4 v4.0.0-20201011105747-d4ebdf080861
github.com/go-kivik/kiviktest/v4 v4.0.0-20200818183706-ea0963c26090
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/go-kivik/fsdb/v4 v4.0.0-20201011102818-b9ac3fbc1e55/go.mod h1:CmwqaXq
github.com/go-kivik/kivik/v4 v4.0.0-20200818182325-bd69ec6a93ff/go.mod h1:O2fxy5xMXO7vaVr2gQG38sYTj/mHwg5Md/8kP7qUZQk=
github.com/go-kivik/kivik/v4 v4.0.0-20201011094523-ab1764d67e62 h1:BHpOPeyQQnx9SvGJDipUls95YTO5FTPFV/qik0NSfeo=
github.com/go-kivik/kivik/v4 v4.0.0-20201011094523-ab1764d67e62/go.mod h1:O2fxy5xMXO7vaVr2gQG38sYTj/mHwg5Md/8kP7qUZQk=
github.com/go-kivik/kivik/v4 v4.0.0-20201015190251-5d5c2f1c89fa h1:s1Q03Mwcx7EJNq+mgFpFoWfGwsx2r5jyUek7kNlYv0s=
github.com/go-kivik/kivik/v4 v4.0.0-20201015190251-5d5c2f1c89fa/go.mod h1:O2fxy5xMXO7vaVr2gQG38sYTj/mHwg5Md/8kP7qUZQk=
github.com/go-kivik/kivikmock/v4 v4.0.0-20201011105747-d4ebdf080861 h1:WY+j7hQwabB5o6lBMJ1R9KIvl/VO9m+XovMc/+hV1gw=
github.com/go-kivik/kivikmock/v4 v4.0.0-20201011105747-d4ebdf080861/go.mod h1:0VQY+92DYvYFN+1tOHTadHUI+eZj49WoTcIYcV4K5SU=
github.com/go-kivik/kiviktest/v4 v4.0.0-20200818183706-ea0963c26090 h1:yRNmZ4AYqrS0K7n74nkSDH9147RJgGJc3KaymMtzF6A=
Expand Down

0 comments on commit cf02f61

Please sign in to comment.