Compare commits

...

7 Commits

Author SHA1 Message Date
Lennart Kats (databricks) d631bd964d
Merge 6ea53065cf into dedec58e41 2024-10-17 19:31:51 +02:00
Lennart Kats 6ea53065cf
Merge CleanupTarget back into SelectTarget 2024-10-17 19:31:21 +02:00
Pieter Noordhuis dedec58e41
Add behavioral tests for examples from the YAML spec (#1835)
## Changes

I took the examples from https://yaml.org/spec/1.2.2.

The required modifications to the loader are:
* Correctly parse floating point infinities and NaN
* Correctly parse octal numbers per the YAML 1.2 spec
* Treat "null" keys in a map as valid

## Tests

Existing and new unit tests pass.
2024-10-17 13:13:30 +00:00
Pieter Noordhuis e4d039a1aa
Handle normalization of `dyn.KindTime` into an any type (#1836)
## Changes

The issue reported in #1828 illustrates how using a YAML timestamp-like
value (a date in this case) causes an issue during conversion to and
from the typed configuration tree.

We use the `AsAny()` function on the `dyn.Value` when normalizing for
the `any` type. We only use the `any` type for variable values, because
they can assume every type. The `AsAny()` function returns a `time.Time`
for the time value during conversion **to** the typed configuration
tree. Upon conversion **from** the typed configuration tree back into
the dynamic configuration tree, we cannot distinguish a `time.Time`
struct from any other struct.

To address this, we use the underlying string value of the time value
when we normalize for the `any` type.

Fixes #1828.

## Tests

Existing unit tests pass
2024-10-17 10:00:40 +00:00
shreyas-goenka cc112961ce
Fix `TestAccFsMkdirWhenFileExistsAtPath` in isolated Azure environments (#1833)
## Changes
This test passes on normal `azure-prod` but started to fail on
`azure-prod-is`, which is the isolated version of azure-prod. This PR
patches the test to include the error returned from the cloud setup in
`azure-prod-is`.

## Tests
The test passes now on `azure-prod-is`.
2024-10-16 12:50:17 +00:00
Andrew Nester 0753dfe2f4
Fixed unmarshalling json input into `interface{}` type (#1832)
## Changes
Fixed unmarshalling json input into `interface{}` type

Commands like `api post` support free form request input, so it should
be unmarshaled correctly

## Tests
Added regression test + E2E test pass
2024-10-15 12:10:02 +00:00
shreyas-goenka ab20624206
Assert SDK version is consistent in the CLI generation process (#1814)
## Changes
Followup from
https://github.com/databricks/cli/pull/1809#discussion_r1790045086. User
will see the following error if the SDK version does not match during
CLI code generation.

```
--- FAIL: TestConsistentDatabricksSdkVersion (1.34s)
    info_test.go:53: 
                Error Trace:    /Users/shreyas.goenka/cli/internal/build/info_test.go:53
                Error:          Not equal: 
                                expected: "0c86ea6dbd9a730c24ff0d4e509603e476955ac5"
                                actual  : "6f6b1371e640f2dfeba72d365ac566368656f6b6"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -0c86ea6dbd9a730c24ff0d4e509603e476955ac5
                                +6f6b1371e640f2dfeba72d365ac566368656f6b6
                Test:           TestConsistentDatabricksSdkVersion
                Messages:       please update the SDK version before generating the CLI
```

## Tests
Manually asserted that:
1. Generating the CLI without the SDK bump causes an error.
2. Generating the CLI after the SDK bump works as expected.
3. The test works even when the SDK is pinned to a specific commit like
`v0.47.1-0.20241002195128-6cecc224cbf7`
2024-10-14 16:19:48 +00:00
49 changed files with 1491 additions and 94 deletions

View File

@ -11,6 +11,7 @@
"toolchain": {
"required": ["go"],
"post_generate": [
"go test -timeout 240s -run TestConsistentDatabricksSdkVersion github.com/databricks/cli/internal/build",
"go run ./bundle/internal/schema/*.go ./bundle/schema/jsonschema.json",
"echo 'bundle/internal/tf/schema/\\*.go linguist-generated=true' >> ./.gitattributes",
"echo 'go.sum linguist-generated=true' >> ./.gitattributes",

View File

@ -18,6 +18,9 @@ type Bundle struct {
// Target is set by the mutator that selects the target.
Target string `json:"target,omitempty" bundle:"readonly"`
// TargetConfig stores a snapshot of the target configuration when it was selected by SelectTarget.
TargetConfig *Target `json:"target_config,omitempty" bundle:"internal"`
// DEPRECATED. Left for backward compatibility with Target
Environment string `json:"environment,omitempty" bundle:"readonly"`

View File

@ -1,29 +0,0 @@
package mutator
import (
"context"
"fmt"
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/diag"
)
type cleanupTargets struct {
name string
}
// CleanupTargets cleans up configuration properties before the configuration
// is reported by the 'bundle summary' command.
func CleanupTargets() bundle.Mutator {
return &cleanupTargets{}
}
func (m *cleanupTargets) Name() string {
return fmt.Sprintf("Cleanup(%s)", m.name)
}
func (m *cleanupTargets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
b.Config.Targets = nil
b.Config.Environments = nil
return nil
}

View File

@ -179,10 +179,10 @@ func isRunAsSet(r config.Resources) bool {
}
func isExplicitRootSet(b *bundle.Bundle) bool {
if b.Config.Targets == nil {
if b.Config.Bundle.TargetConfig == nil {
return false
}
targetConfig := b.Config.Targets[b.Config.Bundle.Target]
targetConfig := b.Config.Bundle.TargetConfig
if targetConfig.Workspace == nil {
return false
}

View File

@ -354,11 +354,9 @@ func TestProcessTargetModeProductionOkWithRootPath(t *testing.T) {
require.Error(t, diags.Error())
// ... but we're okay if we specify a root path
b.Config.Targets = map[string]*config.Target{
"": {
Workspace: &config.Workspace{
RootPath: "some-root-path",
},
b.Config.Bundle.TargetConfig = &config.Target{
Workspace: &config.Workspace{
RootPath: "some-root-path",
},
}
diags = validateProductionMode(context.Background(), b, false)

View File

@ -15,6 +15,7 @@ type selectTarget struct {
}
// SelectTarget merges the specified target into the root configuration.
// After merging, it removes the 'Targets' section from the configuration.
func SelectTarget(name string) bundle.Mutator {
return &selectTarget{
name: name,
@ -31,7 +32,7 @@ func (m *selectTarget) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnosti
}
// Get specified target
_, ok := b.Config.Targets[m.name]
targetConfig, ok := b.Config.Targets[m.name]
if !ok {
return diag.Errorf("%s: no such target. Available targets: %s", m.name, strings.Join(maps.Keys(b.Config.Targets), ", "))
}
@ -43,11 +44,17 @@ func (m *selectTarget) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnosti
}
// Store specified target in configuration for reference.
b.Config.Bundle.TargetConfig = targetConfig
b.Config.Bundle.Target = m.name
// We do this for backward compatibility.
// TODO: remove when Environments section is not supported anymore.
b.Config.Bundle.Environment = b.Config.Bundle.Target
// Cleanup the original targets and environments sections since they
// show up in the JSON output of the 'summary' and 'validate' commands.
b.Config.Targets = nil
b.Config.Environments = nil
return nil
}

View File

@ -47,8 +47,8 @@ type Root struct {
// Targets can be used to differentiate settings and resources between
// bundle deployment targets (e.g. development, staging, production).
// If not specified, the code below initializes this field with a
// single default-initialized target called "default".
// Note that this field is set to 'nil' by the SelectTarget mutator;
// use Bundle.TargetConfig to access the selected target configuration.
Targets map[string]*Target `json:"targets,omitempty"`
// DEPRECATED. Left for backward compatibility with Targets

View File

@ -0,0 +1,33 @@
bundle:
name: issue_1828
variables:
# One entry for each of the underlying YAML (or [dyn.Kind]) types.
# The test confirms we can convert to and from the typed configuration without losing information.
map:
default:
foo: bar
sequence:
default:
- foo
- bar
string:
default: foo
bool:
default: true
int:
default: 42
float:
default: 3.14
time:
default: 2021-01-01
nil:
default:

View File

@ -0,0 +1,48 @@
package config_tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIssue1828(t *testing.T) {
b := load(t, "./issue_1828")
if assert.Contains(t, b.Config.Variables, "map") {
assert.Equal(t, map[string]any{
"foo": "bar",
}, b.Config.Variables["map"].Default)
}
if assert.Contains(t, b.Config.Variables, "sequence") {
assert.Equal(t, []any{
"foo",
"bar",
}, b.Config.Variables["sequence"].Default)
}
if assert.Contains(t, b.Config.Variables, "string") {
assert.Equal(t, "foo", b.Config.Variables["string"].Default)
}
if assert.Contains(t, b.Config.Variables, "bool") {
assert.Equal(t, true, b.Config.Variables["bool"].Default)
}
if assert.Contains(t, b.Config.Variables, "int") {
assert.Equal(t, 42, b.Config.Variables["int"].Default)
}
if assert.Contains(t, b.Config.Variables, "float") {
assert.Equal(t, 3.14, b.Config.Variables["float"].Default)
}
if assert.Contains(t, b.Config.Variables, "time") {
assert.Equal(t, "2021-01-01", b.Config.Variables["time"].Default)
}
if assert.Contains(t, b.Config.Variables, "nil") {
assert.Equal(t, nil, b.Config.Variables["nil"].Default)
}
}

View File

@ -8,7 +8,6 @@ import (
"path/filepath"
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config/mutator"
"github.com/databricks/cli/bundle/deploy/terraform"
"github.com/databricks/cli/bundle/phases"
"github.com/databricks/cli/cmd/bundle/utils"
@ -61,7 +60,7 @@ func newSummaryCommand() *cobra.Command {
}
}
diags = bundle.Apply(ctx, b, bundle.Seq(terraform.Load(), mutator.CleanupTargets()))
diags = bundle.Apply(ctx, b, terraform.Load())
if err := diags.Error(); err != nil {
return err
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config/mutator"
"github.com/databricks/cli/bundle/config/validate"
"github.com/databricks/cli/bundle/phases"
"github.com/databricks/cli/bundle/render"
@ -66,7 +65,6 @@ func newValidateCommand() *cobra.Command {
return nil
case flags.OutputJSON:
bundle.Apply(ctx, b, mutator.CleanupTargets())
return renderJsonOutput(cmd, b, diags)
default:
return fmt.Errorf("unknown output type %s", root.OutputType(cmd))

View File

@ -0,0 +1,73 @@
package build
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/mod/modfile"
)
// This test ensures that the OpenAPI SHA the CLI is being generated from matches
// the OpenAPI SHA of the Go SDK version used in the CLI. We should always upgrade
// the Go SDK version before generating the CLI because downstream generated assets
// like the bundle schema depend on the Go SDK itself.
func TestConsistentDatabricksSdkVersion(t *testing.T) {
// Read the go.mod file
b, err := os.ReadFile("../../go.mod")
require.NoError(t, err)
// Parse the go.mod file to get the databricks-sdk version
modFile, err := modfile.Parse("../../go.mod", b, nil)
require.NoError(t, err)
modulePath := "github.com/databricks/databricks-sdk-go"
var version string
for _, r := range modFile.Require {
if r.Mod.Path == modulePath {
version = r.Mod.Version
}
}
require.NotEmpty(t, version)
// Full path of the package. For example: github.com/databricks/databricks-sdk-go@v0.47.1-0.20241002195128-6cecc224cbf7
fullPath := fmt.Sprintf("%s@%s", modulePath, version)
type goListResponse struct {
Origin struct {
Hash string
}
}
// Using the go CLI query for the git hash corresponding to the databricks-sdk-go version
cmd := exec.Command("go", "list", "-m", "-json", "-mod=readonly", fullPath)
out, err := cmd.Output()
require.NoError(t, err)
parsedOutput := new(goListResponse)
err = json.Unmarshal(out, parsedOutput)
require.NoError(t, err)
hash := parsedOutput.Origin.Hash
require.NotEmpty(t, hash)
// Read the OpenAPI SHA from the Go SDK.
url := fmt.Sprintf("https://raw.githubusercontent.com/databricks/databricks-sdk-go/%s/.codegen/_openapi_sha", hash)
resp, err := http.Get(url)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
sdkSha, err := io.ReadAll(resp.Body)
require.NoError(t, err)
cliSha, err := os.ReadFile("../../.codegen/_openapi_sha")
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(string(cliSha)), strings.TrimSpace(string(sdkSha)), "please update the SDK version before generating the CLI")
}

View File

@ -112,8 +112,8 @@ func TestAccFsMkdirWhenFileExistsAtPath(t *testing.T) {
// assert mkdir fails
_, _, err = RequireErrorRun(t, "fs", "mkdir", path.Join(tmpDir, "hello"))
// Different cloud providers return different errors.
regex := regexp.MustCompile(`(^|: )Path is a file: .*$|(^|: )Cannot create directory .* because .* is an existing file\.$|(^|: )mkdirs\(hadoopPath: .*, permission: rwxrwxrwx\): failed$`)
// Different cloud providers or cloud configurations return different errors.
regex := regexp.MustCompile(`(^|: )Path is a file: .*$|(^|: )Cannot create directory .* because .* is an existing file\.$|(^|: )mkdirs\(hadoopPath: .*, permission: rwxrwxrwx\): failed$|(^|: )"The specified path already exists.".*$`)
assert.Regexp(t, regex, err.Error())
})

View File

@ -20,6 +20,7 @@ import (
"time"
"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/internal/acc"
"github.com/databricks/cli/libs/flags"
"github.com/databricks/cli/cmd"
@ -591,13 +592,10 @@ func setupWsfsExtensionsFiler(t *testing.T) (filer.Filer, string) {
}
func setupDbfsFiler(t *testing.T) (filer.Filer, string) {
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
_, wt := acc.WorkspaceTest(t)
w, err := databricks.NewWorkspaceClient()
require.NoError(t, err)
tmpDir := TemporaryDbfsDir(t, w)
f, err := filer.NewDbfsClient(w, tmpDir)
tmpDir := TemporaryDbfsDir(t, wt.W)
f, err := filer.NewDbfsClient(wt.W, tmpDir)
require.NoError(t, err)
return f, path.Join("dbfs:/", tmpDir)

View File

@ -398,6 +398,34 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
return dyn.NewValue(out, src.Locations()), diags
}
func (n normalizeOptions) normalizeInterface(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
func (n normalizeOptions) normalizeInterface(_ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
// Deal with every [dyn.Kind] here to ensure completeness.
switch src.Kind() {
case dyn.KindMap:
// Fall through
case dyn.KindSequence:
// Fall through
case dyn.KindString:
// Fall through
case dyn.KindBool:
// Fall through
case dyn.KindInt:
// Fall through
case dyn.KindFloat:
// Fall through
case dyn.KindTime:
// Conversion of a time value to an interface{}.
// The [dyn.Value.AsAny] equivalent for this kind is the [time.Time] struct.
// If we convert to a typed representation and back again, we cannot distinguish
// a [time.Time] struct from any other struct.
//
// Therefore, we normalize the time value to a string.
return dyn.NewValue(src.MustTime().String(), src.Locations()), nil
case dyn.KindNil:
// Fall through
default:
return dyn.InvalidValue, diag.Errorf("unsupported kind: %s", src.Kind())
}
return src, nil
}

View File

@ -858,23 +858,7 @@ func TestNormalizeAnchors(t *testing.T) {
}, vout.AsAny())
}
func TestNormalizeBoolToAny(t *testing.T) {
var typ any
vin := dyn.NewValue(false, []dyn.Location{{File: "file", Line: 1, Column: 1}})
vout, err := Normalize(&typ, vin)
assert.Len(t, err, 0)
assert.Equal(t, dyn.NewValue(false, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
}
func TestNormalizeIntToAny(t *testing.T) {
var typ any
vin := dyn.NewValue(10, []dyn.Location{{File: "file", Line: 1, Column: 1}})
vout, err := Normalize(&typ, vin)
assert.Len(t, err, 0)
assert.Equal(t, dyn.NewValue(10, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
}
func TestNormalizeSliceToAny(t *testing.T) {
func TestNormalizeAnyFromSlice(t *testing.T) {
var typ any
v1 := dyn.NewValue(1, []dyn.Location{{File: "file", Line: 1, Column: 1}})
v2 := dyn.NewValue(2, []dyn.Location{{File: "file", Line: 1, Column: 1}})
@ -883,3 +867,35 @@ func TestNormalizeSliceToAny(t *testing.T) {
assert.Len(t, err, 0)
assert.Equal(t, dyn.NewValue([]dyn.Value{v1, v2}, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
}
func TestNormalizeAnyFromString(t *testing.T) {
var typ any
vin := dyn.NewValue("string", []dyn.Location{{File: "file", Line: 1, Column: 1}})
vout, err := Normalize(&typ, vin)
assert.Len(t, err, 0)
assert.Equal(t, dyn.NewValue("string", []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
}
func TestNormalizeAnyFromBool(t *testing.T) {
var typ any
vin := dyn.NewValue(false, []dyn.Location{{File: "file", Line: 1, Column: 1}})
vout, err := Normalize(&typ, vin)
assert.Len(t, err, 0)
assert.Equal(t, dyn.NewValue(false, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
}
func TestNormalizeAnyFromInt(t *testing.T) {
var typ any
vin := dyn.NewValue(10, []dyn.Location{{File: "file", Line: 1, Column: 1}})
vout, err := Normalize(&typ, vin)
assert.Len(t, err, 0)
assert.Equal(t, dyn.NewValue(10, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
}
func TestNormalizeAnyFromTime(t *testing.T) {
var typ any
vin := dyn.NewValue(dyn.MustTime("2024-08-29"), []dyn.Location{{File: "file", Line: 1, Column: 1}})
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.NewValue("2024-08-29", vin.Locations()), vout)
}

View File

@ -0,0 +1,60 @@
package dynassert
import (
"fmt"
"strings"
"github.com/databricks/cli/libs/dyn"
)
// Dump returns the Go code to recreate the given value.
func Dump(v dyn.Value) string {
var sb strings.Builder
dump(v, &sb)
return sb.String()
}
func dump(v dyn.Value, sb *strings.Builder) {
sb.WriteString("dyn.NewValue(\n")
switch v.Kind() {
case dyn.KindMap:
sb.WriteString("map[string]dyn.Value{")
m := v.MustMap()
for _, p := range m.Pairs() {
sb.WriteString(fmt.Sprintf("\n%q: ", p.Key.MustString()))
dump(p.Value, sb)
sb.WriteByte(',')
}
sb.WriteString("\n},\n")
case dyn.KindSequence:
sb.WriteString("[]dyn.Value{\n")
for _, e := range v.MustSequence() {
dump(e, sb)
sb.WriteByte(',')
}
sb.WriteString("},\n")
case dyn.KindString:
sb.WriteString(fmt.Sprintf("%q,\n", v.MustString()))
case dyn.KindBool:
sb.WriteString(fmt.Sprintf("%t,\n", v.MustBool()))
case dyn.KindInt:
sb.WriteString(fmt.Sprintf("%d,\n", v.MustInt()))
case dyn.KindFloat:
sb.WriteString(fmt.Sprintf("%f,\n", v.MustFloat()))
case dyn.KindTime:
sb.WriteString(fmt.Sprintf("dyn.NewTime(%q),\n", v.MustTime().String()))
case dyn.KindNil:
sb.WriteString("nil,\n")
default:
panic(fmt.Sprintf("unhandled kind: %v", v.Kind()))
}
// Add location
sb.WriteString("[]dyn.Location{")
for _, l := range v.Locations() {
sb.WriteString(fmt.Sprintf("{File: %q, Line: %d, Column: %d},", l.File, l.Line, l.Column))
}
sb.WriteString("},\n")
sb.WriteString(")")
}

View File

@ -105,6 +105,9 @@ func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, erro
switch st {
case "!!str":
// OK
case "!!null":
// A literal unquoted "null" is treated as a null value by the YAML parser.
// However, when used as a key, it is treated as the string "null".
case "!!merge":
if merge != nil {
panic("merge node already set")
@ -115,10 +118,11 @@ func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, erro
return dyn.InvalidValue, errorf(loc, "invalid key tag: %v", st)
}
k, err := d.load(key)
if err != nil {
return dyn.InvalidValue, err
}
k := dyn.NewValue(key.Value, []dyn.Location{{
File: d.path,
Line: key.Line,
Column: key.Column,
}})
v, err := d.load(val)
if err != nil {
@ -173,6 +177,14 @@ func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, erro
return dyn.NewValue(out, []dyn.Location{loc}), nil
}
func newIntValue(i64 int64, loc dyn.Location) dyn.Value {
// Use regular int type instead of int64 if possible.
if i64 >= math.MinInt32 && i64 <= math.MaxInt32 {
return dyn.NewValue(int(i64), []dyn.Location{loc})
}
return dyn.NewValue(i64, []dyn.Location{loc})
}
func (d *loader) loadScalar(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
st := node.ShortTag()
switch st {
@ -188,18 +200,44 @@ func (d *loader) loadScalar(node *yaml.Node, loc dyn.Location) (dyn.Value, error
return dyn.InvalidValue, errorf(loc, "invalid bool value: %v", node.Value)
}
case "!!int":
i64, err := strconv.ParseInt(node.Value, 10, 64)
if err != nil {
return dyn.InvalidValue, errorf(loc, "invalid int value: %v", node.Value)
// Try to parse the an integer value in base 10.
// We trim leading zeros to avoid octal parsing of the "0" prefix.
// See "testdata/spec_example_2.19.yml" for background.
i64, err := strconv.ParseInt(strings.TrimLeft(node.Value, "0"), 10, 64)
if err == nil {
return newIntValue(i64, loc), nil
}
// Use regular int type instead of int64 if possible.
if i64 >= math.MinInt32 && i64 <= math.MaxInt32 {
return dyn.NewValue(int(i64), []dyn.Location{loc}), nil
// Let the [ParseInt] function figure out the base.
i64, err = strconv.ParseInt(node.Value, 0, 64)
if err == nil {
return newIntValue(i64, loc), nil
}
return dyn.NewValue(i64, []dyn.Location{loc}), nil
return dyn.InvalidValue, errorf(loc, "invalid int value: %v", node.Value)
case "!!float":
f64, err := strconv.ParseFloat(node.Value, 64)
if err != nil {
// Deal with infinity prefixes.
v := strings.ToLower(node.Value)
switch {
case strings.HasPrefix(v, "+"):
v = strings.TrimPrefix(v, "+")
f64 = math.Inf(1)
case strings.HasPrefix(v, "-"):
v = strings.TrimPrefix(v, "-")
f64 = math.Inf(-1)
default:
// No prefix.
f64 = math.Inf(1)
}
// Deal with infinity and NaN values.
switch v {
case ".inf":
return dyn.NewValue(f64, []dyn.Location{loc}), nil
case ".nan":
return dyn.NewValue(math.NaN(), []dyn.Location{loc}), nil
}
return dyn.InvalidValue, errorf(loc, "invalid float value: %v", node.Value)
}
return dyn.NewValue(f64, []dyn.Location{loc}), nil

View File

@ -0,0 +1,5 @@
# Example 2.1 Sequence of Scalars (ball players)
- Mark McGwire
- Sammy Sosa
- Ken Griffey

View File

@ -0,0 +1,10 @@
# Example 2.10 Node for “Sammy Sosa” appears twice in this document
---
hr:
- Mark McGwire
# Following node labeled SS
- &SS Sammy Sosa
rbi:
- *SS # Subsequent occurrence
- Ken Griffey

View File

@ -0,0 +1,10 @@
# Example 2.11 Mapping between Sequences
? - Detroit Tigers
- Chicago cubs
: - 2001-07-23
? [ New York Yankees,
Atlanta Braves ]
: [ 2001-07-02, 2001-08-12,
2001-08-14 ]

View File

@ -0,0 +1,10 @@
# Example 2.12 Compact Nested Mapping
---
# Products purchased
- item : Super Hoop
quantity: 1
- item : Basketball
quantity: 4
- item : Big Shoes
quantity: 1

View File

@ -0,0 +1,6 @@
# Example 2.13 In literals, newlines are preserved
# ASCII Art
--- |
\//||\/||
// || ||__

View File

@ -0,0 +1,6 @@
# Example 2.14 In the folded scalars, newlines become spaces
--- >
Mark McGwire's
year was crippled
by a knee injury.

View File

@ -0,0 +1,10 @@
# Example 2.15 Folded newlines are preserved for “more indented” and blank lines
--- >
Sammy Sosa completed another
fine season with great stats.
63 Home Runs
0.288 Batting Average
What a year!

View File

@ -0,0 +1,9 @@
# Example 2.16 Indentation determines scope
name: Mark McGwire
accomplishment: >
Mark set a major league
home run record in 1998.
stats: |
65 Home Runs
0.278 Batting Average

View File

@ -0,0 +1,9 @@
# Example 2.17 Quoted Scalars
unicode: "Sosa did fine.\u263A"
control: "\b1998\t1999\t2000\n"
hex esc: "\x0d\x0a is \r\n"
single: '"Howdy!" he cried.'
quoted: ' # Not a ''comment''.'
tie-fighter: '|\-*-/|'

View File

@ -0,0 +1,8 @@
# Example 2.18 Multi-line Flow Scalars
plain:
This unquoted scalar
spans many lines.
quoted: "So does this
quoted scalar.\n"

View File

@ -0,0 +1,15 @@
# Example 2.19 Integers
canonical: 12345
decimal: +12345
octal: 0o14
hexadecimal: 0xC
# Note: this example is not part of the spec but added for completeness.
#
# Octal numbers:
# - YAML 1.1: prefix is "0"
# - YAML 1.2: prefix is "0o"
# The "gopkg.in/yaml.v3" package accepts both for backwards compat.
# We accept only the YAML 1.2 prefix "0o".
octal11: 012345

View File

@ -0,0 +1,5 @@
# Example 2.2 Mapping Scalars to Scalars (player statistics)
hr: 65 # Home runs
avg: 0.278 # Batting average
rbi: 147 # Runs Batted In

View File

@ -0,0 +1,7 @@
# Example 2.20 Floating Point
canonical: 1.23015e+3
exponential: 12.3015e+02
fixed: 1230.15
negative infinity: -.inf
not a number: .nan

View File

@ -0,0 +1,5 @@
# Example 2.21 Miscellaneous
null:
booleans: [ true, false ]
string: '012345'

View File

@ -0,0 +1,6 @@
# Example 2.22 Timestamps
canonical: 2001-12-15T02:59:43.1Z
iso8601: 2001-12-14t21:59:43.10-05:00
spaced: 2001-12-14 21:59:43.10 -5
date: 2002-12-14

View File

@ -0,0 +1,15 @@
# Example 2.23 Various Explicit Tags
---
not-date: !!str 2002-04-28
picture: !!binary |
R0lGODlhDAAMAIQAAP//9/X
17unp5WZmZgAAAOfn515eXv
Pz7Y6OjuDg4J+fn5OTk6enp
56enmleECcgggoBADs=
application specific tag: !something |
The semantics of the tag
above may be different for
different documents.

View File

@ -0,0 +1,16 @@
# Example 2.24 Global Tags
%TAG ! tag:clarkevans.com,2002:
--- !shape
# Use the ! handle for presenting
# tag:clarkevans.com,2002:circle
- !circle
center: &ORIGIN {x: 73, y: 129}
radius: 7
- !line
start: *ORIGIN
finish: { x: 89, y: 102 }
- !label
start: *ORIGIN
color: 0xFFEEBB
text: Pretty vector drawing.

View File

@ -0,0 +1,9 @@
# Example 2.25 Unordered Sets
# Sets are represented as a
# Mapping where each key is
# associated with a null value
--- !!set
? Mark McGwire
? Sammy Sosa
? Ken Griffey

View File

@ -0,0 +1,9 @@
# Example 2.26 Ordered Mappings
# Ordered maps are represented as
# A sequence of mappings, with
# each mapping having one key
--- !!omap
- Mark McGwire: 65
- Sammy Sosa: 63
- Ken Griffey: 58

View File

@ -0,0 +1,31 @@
# Example 2.27 Invoice
--- !<tag:clarkevans.com,2002:invoice>
invoice: 34843
date : 2001-01-23
bill-to: &id001
given : Chris
family : Dumars
address:
lines: |
458 Walkman Dr.
Suite #292
city : Royal Oak
state : MI
postal : 48046
ship-to: *id001
product:
- sku : BL394D
quantity : 4
description : Basketball
price : 450.00
- sku : BL4438H
quantity : 1
description : Super Hoop
price : 2392.00
tax : 251.42
total: 4443.52
comments:
Late afternoon is best.
Backup contact is Nancy
Billsmer @ 338-4338.

View File

@ -0,0 +1,28 @@
# Example 2.28 Log File
---
Time: 2001-11-23 15:01:42 -5
User: ed
Warning:
This is an error message
for the log file
---
Time: 2001-11-23 15:02:31 -5
User: ed
Warning:
A slightly different error
message.
---
Date: 2001-11-23 15:03:17 -5
User: ed
Fatal:
Unknown variable "bar"
Stack:
- file: TopClass.py
line: 23
code: |
x = MoreObject("345\n")
- file: MoreClass.py
line: 58
code: |-
foo = bar

View File

@ -0,0 +1,10 @@
# Example 2.3 Mapping Scalars to Sequences (ball clubs in each league)
american:
- Boston Red Sox
- Detroit Tigers
- New York Yankees
national:
- New York Mets
- Chicago Cubs
- Atlanta Braves

View File

@ -0,0 +1,10 @@
# Example 2.4 Sequence of Mappings (players statistics)
-
name: Mark McGwire
hr: 65
avg: 0.278
-
name: Sammy Sosa
hr: 63
avg: 0.288

View File

@ -0,0 +1,5 @@
# Example 2.5 Sequence of Sequences
- [name , hr, avg ]
- [Mark McGwire, 65, 0.278]
- [Sammy Sosa , 63, 0.288]

View File

@ -0,0 +1,7 @@
# Example 2.6 Mapping of Mappings
Mark McGwire: {hr: 65, avg: 0.278}
Sammy Sosa: {
hr: 63,
avg: 0.288,
}

View File

@ -0,0 +1,12 @@
# Example 2.7 Two Documents in a Stream (each with a leading comment)
# Ranking of 1998 home runs
---
- Mark McGwire
- Sammy Sosa
- Ken Griffey
# Team ranking
---
- Chicago Cubs
- St Louis Cardinals

View File

@ -0,0 +1,12 @@
# Example 2.8 Play by Play Feed from a Game
---
time: 20:03:20
player: Sammy Sosa
action: strike (miss)
...
---
time: 20:03:47
player: Sammy Sosa
action: grand slam
...

View File

@ -0,0 +1,10 @@
# Example 2.9 Single Document with Two Comments
---
hr: # 1998 hr ranking
- Mark McGwire
- Sammy Sosa
# 1998 rbi ranking
rbi:
- Sammy Sosa
- Ken Griffey

View File

@ -0,0 +1,821 @@
package yamlloader_test
import (
"bytes"
"math"
"os"
"testing"
"github.com/databricks/cli/libs/dyn"
assert "github.com/databricks/cli/libs/dyn/dynassert"
"github.com/databricks/cli/libs/dyn/yamlloader"
"github.com/stretchr/testify/require"
)
const NL = "\n"
func loadExample(t *testing.T, file string) dyn.Value {
input, err := os.ReadFile(file)
require.NoError(t, err)
self, err := yamlloader.LoadYAML(file, bytes.NewBuffer(input))
require.NoError(t, err)
return self
}
func TestYAMLSpecExample_2_1(t *testing.T) {
file := "testdata/spec_example_2.1.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Mark McGwire", []dyn.Location{{File: file, Line: 3, Column: 3}}),
dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 4, Column: 3}}),
dyn.NewValue("Ken Griffey", []dyn.Location{{File: file, Line: 5, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_2(t *testing.T) {
file := "testdata/spec_example_2.2.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"hr": dyn.NewValue(65, []dyn.Location{{File: file, Line: 3, Column: 6}}),
"avg": dyn.NewValue(0.278, []dyn.Location{{File: file, Line: 4, Column: 6}}),
"rbi": dyn.NewValue(147, []dyn.Location{{File: file, Line: 5, Column: 6}}),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_3(t *testing.T) {
file := "testdata/spec_example_2.3.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"american": dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Boston Red Sox", []dyn.Location{{File: file, Line: 4, Column: 3}}),
dyn.NewValue("Detroit Tigers", []dyn.Location{{File: file, Line: 5, Column: 3}}),
dyn.NewValue("New York Yankees", []dyn.Location{{File: file, Line: 6, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 4, Column: 1}},
),
"national": dyn.NewValue(
[]dyn.Value{
dyn.NewValue("New York Mets", []dyn.Location{{File: file, Line: 8, Column: 3}}),
dyn.NewValue("Chicago Cubs", []dyn.Location{{File: file, Line: 9, Column: 3}}),
dyn.NewValue("Atlanta Braves", []dyn.Location{{File: file, Line: 10, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 8, Column: 1}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_4(t *testing.T) {
file := "testdata/spec_example_2.4.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
map[string]dyn.Value{
"name": dyn.NewValue("Mark McGwire", []dyn.Location{{File: file, Line: 4, Column: 9}}),
"hr": dyn.NewValue(65, []dyn.Location{{File: file, Line: 5, Column: 9}}),
"avg": dyn.NewValue(0.278, []dyn.Location{{File: file, Line: 6, Column: 9}}),
},
[]dyn.Location{{File: file, Line: 4, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"name": dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 8, Column: 9}}),
"hr": dyn.NewValue(63, []dyn.Location{{File: file, Line: 9, Column: 9}}),
"avg": dyn.NewValue(0.288, []dyn.Location{{File: file, Line: 10, Column: 9}}),
},
[]dyn.Location{{File: file, Line: 8, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_5(t *testing.T) {
file := "testdata/spec_example_2.5.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
[]dyn.Value{
dyn.NewValue("name", []dyn.Location{{File: file, Line: 3, Column: 4}}),
dyn.NewValue("hr", []dyn.Location{{File: file, Line: 3, Column: 18}}),
dyn.NewValue("avg", []dyn.Location{{File: file, Line: 3, Column: 22}}),
},
[]dyn.Location{{File: file, Line: 3, Column: 3}},
),
dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Mark McGwire", []dyn.Location{{File: file, Line: 4, Column: 4}}),
dyn.NewValue(65, []dyn.Location{{File: file, Line: 4, Column: 18}}),
dyn.NewValue(0.278, []dyn.Location{{File: file, Line: 4, Column: 22}}),
},
[]dyn.Location{{File: file, Line: 4, Column: 3}},
),
dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 5, Column: 4}}),
dyn.NewValue(63, []dyn.Location{{File: file, Line: 5, Column: 18}}),
dyn.NewValue(0.288, []dyn.Location{{File: file, Line: 5, Column: 22}}),
},
[]dyn.Location{{File: file, Line: 5, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_6(t *testing.T) {
file := "testdata/spec_example_2.6.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"Mark McGwire": dyn.NewValue(
map[string]dyn.Value{
"hr": dyn.NewValue(65, []dyn.Location{{File: file, Line: 3, Column: 20}}),
"avg": dyn.NewValue(0.278, []dyn.Location{{File: file, Line: 3, Column: 29}}),
},
[]dyn.Location{{File: file, Line: 3, Column: 15}},
),
"Sammy Sosa": dyn.NewValue(
map[string]dyn.Value{
"hr": dyn.NewValue(63, []dyn.Location{{File: file, Line: 5, Column: 9}}),
"avg": dyn.NewValue(0.288, []dyn.Location{{File: file, Line: 6, Column: 10}}),
},
[]dyn.Location{{File: file, Line: 4, Column: 13}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_7(t *testing.T) {
file := "testdata/spec_example_2.7.yml"
self := loadExample(t, file)
// Note: we do not support multiple documents in a single YAML file.
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
"Mark McGwire",
[]dyn.Location{{File: file, Line: 5, Column: 3}},
),
dyn.NewValue(
"Sammy Sosa",
[]dyn.Location{{File: file, Line: 6, Column: 3}},
),
dyn.NewValue(
"Ken Griffey",
[]dyn.Location{{File: file, Line: 7, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 5, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_8(t *testing.T) {
file := "testdata/spec_example_2.8.yml"
self := loadExample(t, file)
// Note: we do not support multiple documents in a single YAML file.
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"time": dyn.NewValue("20:03:20", []dyn.Location{{File: file, Line: 4, Column: 7}}),
"player": dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 5, Column: 9}}),
"action": dyn.NewValue("strike (miss)", []dyn.Location{{File: file, Line: 6, Column: 9}}),
},
[]dyn.Location{{File: file, Line: 4, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_9(t *testing.T) {
file := "testdata/spec_example_2.9.yml"
self := loadExample(t, file)
// Note: we do not support multiple documents in a single YAML file.
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"hr": dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Mark McGwire", []dyn.Location{{File: file, Line: 5, Column: 3}}),
dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 6, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 5, Column: 1}},
),
"rbi": dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 9, Column: 3}}),
dyn.NewValue("Ken Griffey", []dyn.Location{{File: file, Line: 10, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 9, Column: 1}},
),
},
[]dyn.Location{{File: file, Line: 4, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_10(t *testing.T) {
file := "testdata/spec_example_2.10.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"hr": dyn.NewValue(
[]dyn.Value{
dyn.NewValue("Mark McGwire", []dyn.Location{{File: file, Line: 5, Column: 3}}),
dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 7, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 5, Column: 1}},
),
"rbi": dyn.NewValue(
[]dyn.Value{
// The location for an anchored value refers to the anchor, not the reference.
// This is the same location as the anchor that appears in the "hr" mapping.
dyn.NewValue("Sammy Sosa", []dyn.Location{{File: file, Line: 7, Column: 3}}),
dyn.NewValue("Ken Griffey", []dyn.Location{{File: file, Line: 10, Column: 3}}),
},
[]dyn.Location{{File: file, Line: 9, Column: 1}},
),
},
[]dyn.Location{{File: file, Line: 4, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_11(t *testing.T) {
file := "testdata/spec_example_2.11.yml"
input, err := os.ReadFile(file)
require.NoError(t, err)
// Note: non-string mapping keys are not supported by "gopkg.in/yaml.v3".
_, err = yamlloader.LoadYAML(file, bytes.NewBuffer(input))
assert.ErrorContains(t, err, `: key is not a scalar`)
}
func TestYAMLSpecExample_2_12(t *testing.T) {
file := "testdata/spec_example_2.12.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
map[string]dyn.Value{
"item": dyn.NewValue("Super Hoop", []dyn.Location{{File: file, Line: 5, Column: 13}}),
"quantity": dyn.NewValue(1, []dyn.Location{{File: file, Line: 6, Column: 13}}),
},
[]dyn.Location{{File: file, Line: 5, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"item": dyn.NewValue("Basketball", []dyn.Location{{File: file, Line: 7, Column: 13}}),
"quantity": dyn.NewValue(4, []dyn.Location{{File: file, Line: 8, Column: 13}}),
},
[]dyn.Location{{File: file, Line: 7, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"item": dyn.NewValue("Big Shoes", []dyn.Location{{File: file, Line: 9, Column: 13}}),
"quantity": dyn.NewValue(1, []dyn.Location{{File: file, Line: 10, Column: 13}}),
},
[]dyn.Location{{File: file, Line: 9, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 5, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_13(t *testing.T) {
file := "testdata/spec_example_2.13.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
``+
`\//||\/||`+NL+
"// || ||__"+NL,
[]dyn.Location{{File: file, Line: 4, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_14(t *testing.T) {
file := "testdata/spec_example_2.14.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
`Mark McGwire's year was crippled by a knee injury.`+NL,
[]dyn.Location{{File: file, Line: 3, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_15(t *testing.T) {
file := "testdata/spec_example_2.15.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
``+
`Sammy Sosa completed another fine season with great stats.`+NL+
NL+
` 63 Home Runs`+NL+
` 0.288 Batting Average`+NL+
NL+
`What a year!`+NL,
[]dyn.Location{{File: file, Line: 3, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_16(t *testing.T) {
file := "testdata/spec_example_2.16.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"name": dyn.NewValue(
"Mark McGwire",
[]dyn.Location{{File: file, Line: 3, Column: 7}},
),
"accomplishment": dyn.NewValue(
`Mark set a major league home run record in 1998.`+NL,
[]dyn.Location{{File: file, Line: 4, Column: 17}},
),
"stats": dyn.NewValue(
``+
`65 Home Runs`+NL+
`0.278 Batting Average`+NL,
[]dyn.Location{{File: file, Line: 7, Column: 8}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_17(t *testing.T) {
file := "testdata/spec_example_2.17.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"unicode": dyn.NewValue(
`Sosa did fine.`+"\u263A",
[]dyn.Location{{File: file, Line: 3, Column: 10}},
),
"control": dyn.NewValue(
"\b1998\t1999\t2000\n",
[]dyn.Location{{File: file, Line: 4, Column: 10}},
),
"hex esc": dyn.NewValue(
"\x0d\x0a is \r\n",
[]dyn.Location{{File: file, Line: 5, Column: 10}},
),
"single": dyn.NewValue(
`"Howdy!" he cried.`,
[]dyn.Location{{File: file, Line: 7, Column: 9}},
),
"quoted": dyn.NewValue(
` # Not a 'comment'.`,
[]dyn.Location{{File: file, Line: 8, Column: 9}},
),
"tie-fighter": dyn.NewValue(
`|\-*-/|`,
[]dyn.Location{{File: file, Line: 9, Column: 14}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_18(t *testing.T) {
file := "testdata/spec_example_2.18.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"plain": dyn.NewValue(
`This unquoted scalar spans many lines.`,
[]dyn.Location{{File: file, Line: 4, Column: 3}},
),
"quoted": dyn.NewValue(
`So does this quoted scalar.`+NL,
[]dyn.Location{{File: file, Line: 7, Column: 9}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_19(t *testing.T) {
file := "testdata/spec_example_2.19.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"canonical": dyn.NewValue(
12345,
[]dyn.Location{{File: file, Line: 3, Column: 12}},
),
"decimal": dyn.NewValue(
12345,
[]dyn.Location{{File: file, Line: 4, Column: 10}},
),
"octal": dyn.NewValue(
12,
[]dyn.Location{{File: file, Line: 5, Column: 8}},
),
"hexadecimal": dyn.NewValue(
12,
[]dyn.Location{{File: file, Line: 6, Column: 14}},
),
"octal11": dyn.NewValue(
12345,
[]dyn.Location{{File: file, Line: 15, Column: 10}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_20(t *testing.T) {
file := "testdata/spec_example_2.20.yml"
self := loadExample(t, file)
// Equality assertion doesn't work with NaNs.
// See https://github.com/stretchr/testify/issues/624.
//
// Remove the NaN entry.
self, _ = dyn.Walk(self, func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
if f, ok := v.AsFloat(); ok && math.IsNaN(f) {
return dyn.InvalidValue, dyn.ErrDrop
}
return v, nil
})
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"canonical": dyn.NewValue(
1230.15,
[]dyn.Location{{File: file, Line: 3, Column: 12}},
),
"exponential": dyn.NewValue(
1230.15,
[]dyn.Location{{File: file, Line: 4, Column: 14}},
),
"fixed": dyn.NewValue(
1230.15,
[]dyn.Location{{File: file, Line: 5, Column: 8}},
),
"negative infinity": dyn.NewValue(
math.Inf(-1),
[]dyn.Location{{File: file, Line: 6, Column: 20}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_21(t *testing.T) {
file := "testdata/spec_example_2.21.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"null": dyn.NewValue(
nil,
[]dyn.Location{{File: file, Line: 3, Column: 6}},
),
"booleans": dyn.NewValue(
[]dyn.Value{
dyn.NewValue(true, []dyn.Location{{File: file, Line: 4, Column: 13}}),
dyn.NewValue(false, []dyn.Location{{File: file, Line: 4, Column: 19}}),
},
[]dyn.Location{{File: file, Line: 4, Column: 11}},
),
"string": dyn.NewValue(
"012345",
[]dyn.Location{{File: file, Line: 5, Column: 9}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_22(t *testing.T) {
file := "testdata/spec_example_2.22.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"canonical": dyn.NewValue(
dyn.MustTime("2001-12-15T02:59:43.1Z"),
[]dyn.Location{{File: file, Line: 3, Column: 12}},
),
"iso8601": dyn.NewValue(
dyn.MustTime("2001-12-14t21:59:43.10-05:00"),
[]dyn.Location{{File: file, Line: 4, Column: 10}},
),
"spaced": dyn.NewValue(
// This is parsed as a string, not a timestamp,
// both by "gopkg.in/yaml.v3" and by our implementation.
"2001-12-14 21:59:43.10 -5",
[]dyn.Location{{File: file, Line: 5, Column: 9}},
),
"date": dyn.NewValue(
dyn.MustTime("2002-12-14"),
[]dyn.Location{{File: file, Line: 6, Column: 7}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 1}},
), self)
}
func TestYAMLSpecExample_2_23(t *testing.T) {
file := "testdata/spec_example_2.23.yml"
input, err := os.ReadFile(file)
require.NoError(t, err)
// Note: the !!binary tag is not supported by us.
_, err = yamlloader.LoadYAML(file, bytes.NewBuffer(input))
assert.ErrorContains(t, err, `: unknown tag: !!binary`)
}
func TestYAMLSpecExample_2_24(t *testing.T) {
file := "testdata/spec_example_2.24.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
map[string]dyn.Value{
"center": dyn.NewValue(
map[string]dyn.Value{
"x": dyn.NewValue(73, []dyn.Location{{File: file, Line: 8, Column: 23}}),
"y": dyn.NewValue(129, []dyn.Location{{File: file, Line: 8, Column: 30}}),
},
[]dyn.Location{{File: file, Line: 8, Column: 11}},
),
"radius": dyn.NewValue(7, []dyn.Location{{File: file, Line: 9, Column: 11}}),
},
[]dyn.Location{{File: file, Line: 7, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"start": dyn.NewValue(
map[string]dyn.Value{
"x": dyn.NewValue(73, []dyn.Location{{File: file, Line: 8, Column: 23}}),
"y": dyn.NewValue(129, []dyn.Location{{File: file, Line: 8, Column: 30}}),
},
[]dyn.Location{{File: file, Line: 8, Column: 11}},
),
"finish": dyn.NewValue(
map[string]dyn.Value{
"x": dyn.NewValue(89, []dyn.Location{{File: file, Line: 12, Column: 16}}),
"y": dyn.NewValue(102, []dyn.Location{{File: file, Line: 12, Column: 23}}),
},
[]dyn.Location{{File: file, Line: 12, Column: 11}},
),
},
[]dyn.Location{{File: file, Line: 10, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"start": dyn.NewValue(
map[string]dyn.Value{
"x": dyn.NewValue(73, []dyn.Location{{File: file, Line: 8, Column: 23}}),
"y": dyn.NewValue(129, []dyn.Location{{File: file, Line: 8, Column: 30}}),
},
[]dyn.Location{{File: file, Line: 8, Column: 11}},
),
"color": dyn.NewValue(16772795, []dyn.Location{{File: file, Line: 15, Column: 10}}),
"text": dyn.NewValue("Pretty vector drawing.", []dyn.Location{{File: file, Line: 16, Column: 9}}),
},
[]dyn.Location{{File: file, Line: 13, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 4, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_25(t *testing.T) {
file := "testdata/spec_example_2.25.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"Mark McGwire": dyn.NewValue(nil, []dyn.Location{{File: file, Line: 8, Column: 1}}),
"Sammy Sosa": dyn.NewValue(nil, []dyn.Location{{File: file, Line: 9, Column: 1}}),
"Ken Griffey": dyn.NewValue(nil, []dyn.Location{{File: file, Line: 10, Column: 1}}),
},
[]dyn.Location{{File: file, Line: 6, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_26(t *testing.T) {
file := "testdata/spec_example_2.26.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
map[string]dyn.Value{
"Mark McGwire": dyn.NewValue(65, []dyn.Location{{File: file, Line: 7, Column: 17}}),
},
[]dyn.Location{{File: file, Line: 7, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"Sammy Sosa": dyn.NewValue(63, []dyn.Location{{File: file, Line: 8, Column: 15}}),
},
[]dyn.Location{{File: file, Line: 8, Column: 3}},
),
dyn.NewValue(
map[string]dyn.Value{
"Ken Griffey": dyn.NewValue(58, []dyn.Location{{File: file, Line: 9, Column: 16}}),
},
[]dyn.Location{{File: file, Line: 9, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 6, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_27(t *testing.T) {
file := "testdata/spec_example_2.27.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"invoice": dyn.NewValue(
34843,
[]dyn.Location{{File: file, Line: 4, Column: 10}},
),
"date": dyn.NewValue(
dyn.MustTime("2001-01-23"),
[]dyn.Location{{File: file, Line: 5, Column: 10}},
),
"bill-to": dyn.NewValue(
map[string]dyn.Value{
"given": dyn.NewValue(
"Chris",
[]dyn.Location{{File: file, Line: 7, Column: 12}},
),
"family": dyn.NewValue(
"Dumars",
[]dyn.Location{{File: file, Line: 8, Column: 12}},
),
"address": dyn.NewValue(
map[string]dyn.Value{
"lines": dyn.NewValue(
"458 Walkman Dr.\nSuite #292\n",
[]dyn.Location{{File: file, Line: 10, Column: 12}},
),
"city": dyn.NewValue(
"Royal Oak",
[]dyn.Location{{File: file, Line: 13, Column: 15}},
),
"state": dyn.NewValue(
"MI",
[]dyn.Location{{File: file, Line: 14, Column: 15}},
),
"postal": dyn.NewValue(
48046,
[]dyn.Location{{File: file, Line: 15, Column: 15}},
),
},
[]dyn.Location{{File: file, Line: 10, Column: 5}},
),
},
[]dyn.Location{{File: file, Line: 6, Column: 10}},
),
"ship-to": dyn.NewValue(
map[string]dyn.Value{
"given": dyn.NewValue(
"Chris",
[]dyn.Location{{File: file, Line: 7, Column: 12}},
),
"family": dyn.NewValue(
"Dumars",
[]dyn.Location{{File: file, Line: 8, Column: 12}},
),
"address": dyn.NewValue(
map[string]dyn.Value{
"lines": dyn.NewValue(
"458 Walkman Dr.\nSuite #292\n",
[]dyn.Location{{File: file, Line: 10, Column: 12}},
),
"city": dyn.NewValue(
"Royal Oak",
[]dyn.Location{{File: file, Line: 13, Column: 15}},
),
"state": dyn.NewValue(
"MI",
[]dyn.Location{{File: file, Line: 14, Column: 15}},
),
"postal": dyn.NewValue(
48046,
[]dyn.Location{{File: file, Line: 15, Column: 15}},
),
},
[]dyn.Location{{File: file, Line: 10, Column: 5}},
),
},
[]dyn.Location{{File: file, Line: 6, Column: 10}},
),
"product": dyn.NewValue(
[]dyn.Value{
dyn.NewValue(
map[string]dyn.Value{
"sku": dyn.NewValue(
"BL394D",
[]dyn.Location{{File: file, Line: 18, Column: 17}},
),
"quantity": dyn.NewValue(
4,
[]dyn.Location{{File: file, Line: 19, Column: 17}},
),
"description": dyn.NewValue(
"Basketball",
[]dyn.Location{{File: file, Line: 20, Column: 17}},
),
"price": dyn.NewValue(
450.0,
[]dyn.Location{{File: file, Line: 21, Column: 17}},
),
},
[]dyn.Location{{File: file, Line: 18, Column: 3}},
), dyn.NewValue(
map[string]dyn.Value{
"sku": dyn.NewValue(
"BL4438H",
[]dyn.Location{{File: file, Line: 22, Column: 17}},
),
"quantity": dyn.NewValue(
1,
[]dyn.Location{{File: file, Line: 23, Column: 17}},
),
"description": dyn.NewValue(
"Super Hoop",
[]dyn.Location{{File: file, Line: 24, Column: 17}},
),
"price": dyn.NewValue(
2392.0,
[]dyn.Location{{File: file, Line: 25, Column: 17}},
),
},
[]dyn.Location{{File: file, Line: 22, Column: 3}},
)},
[]dyn.Location{{File: file, Line: 18, Column: 1}},
),
"tax": dyn.NewValue(
251.42,
[]dyn.Location{{File: file, Line: 26, Column: 8}},
),
"total": dyn.NewValue(
4443.52,
[]dyn.Location{{File: file, Line: 27, Column: 8}},
),
"comments": dyn.NewValue(
"Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.",
[]dyn.Location{{File: file, Line: 29, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 3, Column: 5}},
), self)
}
func TestYAMLSpecExample_2_28(t *testing.T) {
file := "testdata/spec_example_2.28.yml"
self := loadExample(t, file)
assert.Equal(t, dyn.NewValue(
map[string]dyn.Value{
"Time": dyn.NewValue(
"2001-11-23 15:01:42 -5",
[]dyn.Location{{File: file, Line: 4, Column: 7}},
),
"User": dyn.NewValue(
"ed",
[]dyn.Location{{File: file, Line: 5, Column: 7}},
),
"Warning": dyn.NewValue(
"This is an error message for the log file",
[]dyn.Location{{File: file, Line: 7, Column: 3}},
),
},
[]dyn.Location{{File: file, Line: 4, Column: 1}},
), self)
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"reflect"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn/convert"
@ -63,11 +64,24 @@ func (j *JsonFlag) Unmarshal(v any) diag.Diagnostics {
return diags.Extend(diag.FromErr(err))
}
// Finally unmarshal the normalized data to the output.
// It will fill in the ForceSendFields field if the struct contains it.
err = marshal.Unmarshal(data, v)
if err != nil {
return diags.Extend(diag.FromErr(err))
kind := reflect.ValueOf(v).Kind()
if kind == reflect.Ptr {
kind = reflect.ValueOf(v).Elem().Kind()
}
if kind == reflect.Struct {
// Finally unmarshal the normalized data to the output.
// It will fill in the ForceSendFields field if the struct contains it.
err = marshal.Unmarshal(data, v)
if err != nil {
return diags.Extend(diag.FromErr(err))
}
} else {
// If the output is not a struct, just unmarshal the data to the output.
err = json.Unmarshal(data, v)
if err != nil {
return diags.Extend(diag.FromErr(err))
}
}
return diags

View File

@ -13,10 +13,6 @@ import (
"github.com/stretchr/testify/require"
)
type requestType struct {
Foo string `json:"foo"`
}
func TestJsonFlagEmpty(t *testing.T) {
var body JsonFlag
@ -35,13 +31,13 @@ func TestJsonFlagInline(t *testing.T) {
err := body.Set(`{"foo": "bar"}`)
assert.NoError(t, err)
var request requestType
var request any
diags := body.Unmarshal(&request)
assert.NoError(t, diags.Error())
assert.Empty(t, diags)
assert.Equal(t, "JSON (14 bytes)", body.String())
assert.Equal(t, requestType{"bar"}, request)
assert.Equal(t, map[string]any{"foo": "bar"}, request)
}
func TestJsonFlagError(t *testing.T) {
@ -50,7 +46,7 @@ func TestJsonFlagError(t *testing.T) {
err := body.Set(`{"foo":`)
assert.NoError(t, err)
var request requestType
var request any
diags := body.Unmarshal(&request)
assert.EqualError(t, diags.Error(), "error decoding JSON at (inline):1:8: unexpected end of JSON input")
assert.Equal(t, "JSON (7 bytes)", body.String())
@ -58,7 +54,7 @@ func TestJsonFlagError(t *testing.T) {
func TestJsonFlagFile(t *testing.T) {
var body JsonFlag
var request requestType
var request any
var fpath string
var payload = []byte(`{"foo": "bar"}`)
@ -78,7 +74,7 @@ func TestJsonFlagFile(t *testing.T) {
assert.NoError(t, diags.Error())
assert.Empty(t, diags)
assert.Equal(t, requestType{"bar"}, request)
assert.Equal(t, map[string]any{"foo": "bar"}, request)
}
const jsonData = `