mirror of
https://github.com/Bornholm/formidable.git
synced 2025-07-19 03:51:37 +02:00
feat: aggregate defaults and values
- Output merged defaults and values - Add "check" command
This commit is contained in:
49
internal/command/check.go
Normal file
49
internal/command/check.go
Normal file
@ -0,0 +1,49 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Check() *cli.Command {
|
||||
flags := []cli.Flag{}
|
||||
|
||||
flags = append(flags, commonFlags()...)
|
||||
|
||||
return &cli.Command{
|
||||
Name: "check",
|
||||
Usage: "Check values with the given schema",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
schema, err := loadSchema(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load schema")
|
||||
}
|
||||
|
||||
_, values, err := loadData(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load data")
|
||||
}
|
||||
|
||||
if err := schema.Validate(values); err != nil {
|
||||
if _, ok := err.(*jsonschema.ValidationError); ok {
|
||||
fmt.Printf("%#v\n", err)
|
||||
|
||||
return errors.New("invalid values")
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "could not validate values")
|
||||
}
|
||||
|
||||
if err := outputValues(ctx, values); err != nil {
|
||||
return errors.Wrap(err, "could not output updated values")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
87
internal/command/check_test.go
Normal file
87
internal/command/check_test.go
Normal file
@ -0,0 +1,87 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type ExpectFunc func(t *testing.T, cmd *cli.Command, err error)
|
||||
|
||||
type checkCommandTestCase struct {
|
||||
Name string
|
||||
SchemaFile string
|
||||
DefaultFile string
|
||||
ValuesFile string
|
||||
Expect ExpectFunc
|
||||
}
|
||||
|
||||
var checkCommandTestCases = []checkCommandTestCase{
|
||||
{
|
||||
Name: "ok",
|
||||
SchemaFile: "file://testdata/check/schema.json",
|
||||
DefaultFile: "file://testdata/check/defaults.json",
|
||||
ValuesFile: "file://testdata/check/values-ok.json",
|
||||
Expect: expectNoError,
|
||||
},
|
||||
{
|
||||
Name: "nok",
|
||||
SchemaFile: "file://testdata/check/schema.json",
|
||||
DefaultFile: "file://testdata/check/defaults.json",
|
||||
ValuesFile: "file://testdata/check/values-nok.json",
|
||||
Expect: expectError,
|
||||
},
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range checkCommandTestCases {
|
||||
func(tc *checkCommandTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||
cmd := Check()
|
||||
|
||||
for _, f := range cmd.Flags {
|
||||
if err := f.Apply(flags); err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
}
|
||||
|
||||
err := flags.Parse([]string{
|
||||
"check",
|
||||
"--schema", tc.SchemaFile,
|
||||
"--defaults", tc.DefaultFile,
|
||||
"--values", tc.ValuesFile,
|
||||
"--output", "null://local?format=json",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, flags, nil)
|
||||
|
||||
err = cmd.Run(ctx)
|
||||
|
||||
tc.Expect(t, cmd, err)
|
||||
})
|
||||
}(&tc)
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoError(t *testing.T, cmd *cli.Command, err error) {
|
||||
if err != nil {
|
||||
t.Error(errors.Wrap(err, "the command result in an unexpected error"))
|
||||
}
|
||||
}
|
||||
|
||||
func expectError(t *testing.T, cmd *cli.Command, err error) {
|
||||
if err == nil {
|
||||
t.Error(errors.New("an error should have been returned"))
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
encjson "encoding/json"
|
||||
|
||||
@ -20,6 +21,7 @@ import (
|
||||
"forge.cadoles.com/wpetit/formidable/internal/data/updater/null"
|
||||
"forge.cadoles.com/wpetit/formidable/internal/data/updater/stdout"
|
||||
"forge.cadoles.com/wpetit/formidable/internal/def"
|
||||
"forge.cadoles.com/wpetit/formidable/internal/merge"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -113,6 +115,46 @@ func loadDefaults(ctx *cli.Context) (interface{}, error) {
|
||||
return defaults, nil
|
||||
}
|
||||
|
||||
func loadData(ctx *cli.Context) (defaults interface{}, values interface{}, err error) {
|
||||
values, err = loadValues(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not load values")
|
||||
}
|
||||
|
||||
defaults, err = loadDefaults(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not load defaults")
|
||||
}
|
||||
|
||||
merged, err := getMatchingZeroValue(values)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if defaults != nil {
|
||||
if err := merge.Merge(&merged, defaults, values); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not merge values")
|
||||
}
|
||||
|
||||
values = merged
|
||||
}
|
||||
|
||||
return defaults, values, nil
|
||||
}
|
||||
|
||||
func getMatchingZeroValue(values interface{}) (interface{}, error) {
|
||||
valuesKind := reflect.TypeOf(values).Kind()
|
||||
|
||||
switch valuesKind {
|
||||
case reflect.Map:
|
||||
return make(map[string]interface{}, 0), nil
|
||||
case reflect.Slice:
|
||||
return make([]interface{}, 0), nil
|
||||
default:
|
||||
return nil, errors.Errorf("unexpected type '%T'", values)
|
||||
}
|
||||
}
|
||||
|
||||
func loadSchema(ctx *cli.Context) (*jsonschema.Schema, error) {
|
||||
schemaFlag := ctx.String("schema")
|
||||
|
||||
|
@ -55,14 +55,9 @@ func Edit() *cli.Command {
|
||||
return errors.Wrap(err, "could not load schema")
|
||||
}
|
||||
|
||||
values, err := loadValues(ctx)
|
||||
defaults, values, err := loadData(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load values")
|
||||
}
|
||||
|
||||
defaults, err := loadDefaults(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load defaults")
|
||||
return errors.Wrap(err, "could not load data")
|
||||
}
|
||||
|
||||
srvCtx, srvCancel := context.WithCancel(ctx.Context)
|
||||
|
@ -26,9 +26,9 @@ func Get() *cli.Command {
|
||||
return errors.Wrap(err, "could not load schema")
|
||||
}
|
||||
|
||||
values, err := loadValues(ctx)
|
||||
_, values, err := loadData(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load values")
|
||||
return errors.Wrap(err, "could not load data")
|
||||
}
|
||||
|
||||
if err := schema.Validate(values); err != nil {
|
||||
|
@ -8,5 +8,6 @@ func Root() []*cli.Command {
|
||||
Set(),
|
||||
Get(),
|
||||
Delete(),
|
||||
Check(),
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ func Set() *cli.Command {
|
||||
return errors.Wrap(err, "could not load schema")
|
||||
}
|
||||
|
||||
values, err := loadValues(ctx)
|
||||
_, values, err := loadData(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load values")
|
||||
return errors.Wrap(err, "could not load data")
|
||||
}
|
||||
|
||||
rawPointer := ctx.Args().Get(0)
|
||||
|
5
internal/command/testdata/check/defaults.json
vendored
Normal file
5
internal/command/testdata/check/defaults.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"foo": {
|
||||
"bar": "test"
|
||||
}
|
||||
}
|
17
internal/command/testdata/check/schema.json
vendored
Normal file
17
internal/command/testdata/check/schema.json
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["bar"]
|
||||
}
|
||||
},
|
||||
"required": ["foo"],
|
||||
"additionalProperties": true
|
||||
}
|
5
internal/command/testdata/check/values-nok.json
vendored
Normal file
5
internal/command/testdata/check/values-nok.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"foo": {
|
||||
"bar": false
|
||||
}
|
||||
}
|
3
internal/command/testdata/check/values-ok.json
vendored
Normal file
3
internal/command/testdata/check/values-ok.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"test": 1
|
||||
}
|
Reference in New Issue
Block a user