Skip to content

Commit e5d746d

Browse files
committed
Adopt goccy/go-yaml
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent 6f5d914 commit e5d746d

27 files changed

+255
-228
lines changed

cli/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import (
2424
"strconv"
2525
"strings"
2626

27+
"github.com/goccy/go-yaml"
2728
"github.com/sirupsen/logrus"
28-
"gopkg.in/yaml.v3"
2929

3030
"github.com/compose-spec/compose-go/v2/consts"
3131
"github.com/compose-spec/compose-go/v2/dotenv"

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"os"
2525

2626
"github.com/compose-spec/compose-go/v2/cli"
27-
"gopkg.in/yaml.v3"
27+
"github.com/goccy/go-yaml"
2828
)
2929

3030
func main() {

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/docker/go-connections v0.4.0
88
github.com/docker/go-units v0.5.0
99
github.com/go-viper/mapstructure/v2 v2.0.0
10+
github.com/goccy/go-yaml v1.17.1
1011
github.com/google/go-cmp v0.5.9
1112
github.com/mattn/go-shellwords v1.0.12
1213
github.com/opencontainers/go-digest v1.0.0
@@ -15,7 +16,6 @@ require (
1516
github.com/xeipuuv/gojsonschema v1.2.0
1617
github.com/xhit/go-str2duration/v2 v2.1.0
1718
golang.org/x/sync v0.3.0
18-
gopkg.in/yaml.v3 v3.0.1
1919
gotest.tools/v3 v3.4.0
2020
)
2121

@@ -25,4 +25,5 @@ require (
2525
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
2626
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
2727
golang.org/x/sys v0.1.0 // indirect
28+
gopkg.in/yaml.v3 v3.0.1 // indirect
2829
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
99
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
1010
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
1111
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
12+
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
13+
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
1214
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1315
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
1416
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=

loader/extends.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, track
3434
}
3535
services, ok := a.(map[string]any)
3636
if !ok {
37-
return fmt.Errorf("services must be a mapping")
37+
return fmt.Errorf("services: must be a mapping")
3838
}
3939
for name := range services {
4040
merged, err := applyServiceExtends(ctx, name, services, opts, tracker, post...)
@@ -54,7 +54,7 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
5454
}
5555
service, ok := s.(map[string]any)
5656
if !ok {
57-
return nil, fmt.Errorf("services.%s must be a mapping", name)
57+
return nil, fmt.Errorf("services.%s: must be a mapping", name)
5858
}
5959
extends, ok := service["extends"]
6060
if !ok {

loader/loader.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ import (
4242
"github.com/compose-spec/compose-go/v2/types"
4343
"github.com/compose-spec/compose-go/v2/validation"
4444
"github.com/go-viper/mapstructure/v2"
45+
"github.com/goccy/go-yaml"
46+
"github.com/goccy/go-yaml/parser"
4547
"github.com/sirupsen/logrus"
46-
"gopkg.in/yaml.v3"
4748
)
4849

4950
// Options supported by Load
@@ -260,8 +261,6 @@ func WithProfiles(profiles []string) func(*Options) {
260261
// PostProcessor is used to tweak compose model based on metadata extracted during yaml Unmarshal phase
261262
// that hardly can be implemented using go-yaml and mapstructure
262263
type PostProcessor interface {
263-
yaml.Unmarshaler
264-
265264
// Apply changes to compose model based on recorder metadata
266265
Apply(interface{}) error
267266
}
@@ -430,6 +429,9 @@ func loadYamlFile(ctx context.Context,
430429
if err != nil {
431430
return err
432431
}
432+
if converted == nil {
433+
return errors.New("empty compose file")
434+
}
433435
cfg, ok := converted.(map[string]interface{})
434436
if !ok {
435437
return errors.New("top-level object must be a mapping")
@@ -501,17 +503,25 @@ func loadYamlFile(ctx context.Context,
501503
if file.Config == nil {
502504
r := bytes.NewReader(file.Content)
503505
decoder := yaml.NewDecoder(r)
504-
for {
505-
var raw interface{}
506-
reset := &ResetProcessor{target: &raw}
507-
err := decoder.Decode(reset)
508-
if err != nil && errors.Is(err, io.EOF) {
509-
break
506+
all, err := io.ReadAll(r)
507+
if err != nil {
508+
return nil, nil, err
509+
}
510+
y, err := parser.ParseBytes(all, parser.ParseComments)
511+
if err != nil {
512+
return nil, nil, err
513+
}
514+
for _, doc := range y.Docs {
515+
if doc.Body == nil {
516+
continue // empty file
510517
}
518+
processor = NewResetProcessor(doc)
519+
520+
var raw interface{}
521+
err := decoder.DecodeFromNodeContext(ctx, doc.Body, &raw)
511522
if err != nil {
512523
return nil, nil, err
513524
}
514-
processor = reset
515525
if err := processRawYaml(raw, processor); err != nil {
516526
return nil, nil, err
517527
}
@@ -873,14 +883,8 @@ func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interfac
873883
return value, nil
874884
}
875885

876-
func formatInvalidKeyError(keyPrefix string, key interface{}) error {
877-
var location string
878-
if keyPrefix == "" {
879-
location = "at top level"
880-
} else {
881-
location = fmt.Sprintf("in %s", keyPrefix)
882-
}
883-
return fmt.Errorf("non-string key %s: %#v", location, key)
886+
func formatInvalidKeyError(location string, key interface{}) error {
887+
return fmt.Errorf("%s: non-string key %#v", location, key)
884888
}
885889

886890
// Windows path, c:\\my\\path\\shiny, need to be changed to be compatible with

loader/loader_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ func TestNonStringKeys(t *testing.T) {
468468
foo:
469469
image: busybox
470470
`)
471-
assert.ErrorContains(t, err, "non-string key at top level: 123")
471+
assert.ErrorContains(t, err, `non-string key at top level: "123"`)
472472

473473
_, err = loadYAML(`
474474
services:
@@ -477,7 +477,7 @@ services:
477477
123:
478478
image: busybox
479479
`)
480-
assert.ErrorContains(t, err, "non-string key in services: 123")
480+
assert.ErrorContains(t, err, `non-string key in services: "123"`)
481481

482482
_, err = loadYAML(`
483483
services:
@@ -489,7 +489,7 @@ networks:
489489
config:
490490
- 123: oh dear
491491
`)
492-
assert.ErrorContains(t, err, "non-string key in networks.default.ipam.config[0]: 123")
492+
assert.ErrorContains(t, err, "networks.default.ipam.config.0: Additional property 123 is not allowed")
493493

494494
_, err = loadYAML(`
495495
services:
@@ -516,44 +516,44 @@ services:
516516
- foo:
517517
image: busybox
518518
`)
519-
assert.ErrorContains(t, err, "services must be a mapping")
519+
assert.ErrorContains(t, err, "services: must be a mapping")
520520

521521
_, err = loadYAML(`
522522
name: non-mapping-object
523523
services:
524524
foo: busybox
525525
`)
526-
assert.ErrorContains(t, err, "services.foo must be a mapping")
526+
assert.ErrorContains(t, err, "services.foo: must be a mapping")
527527

528528
_, err = loadYAML(`
529529
name: non-mapping-object
530530
networks:
531531
- default:
532532
driver: bridge
533533
`)
534-
assert.ErrorContains(t, err, "networks must be a mapping")
534+
assert.ErrorContains(t, err, "networks: must be a mapping")
535535

536536
_, err = loadYAML(`
537537
name: non-mapping-object
538538
networks:
539539
default: bridge
540540
`)
541-
assert.ErrorContains(t, err, "networks.default must be a mapping")
541+
assert.ErrorContains(t, err, "networks.default: must be a mapping")
542542

543543
_, err = loadYAML(`
544544
name: non-mapping-object
545545
volumes:
546546
- data:
547547
driver: local
548548
`)
549-
assert.ErrorContains(t, err, "volumes must be a mapping")
549+
assert.ErrorContains(t, err, "volumes: must be a mapping")
550550

551551
_, err = loadYAML(`
552552
name: non-mapping-object
553553
volumes:
554554
data: local
555555
`)
556-
assert.ErrorContains(t, err, "volumes.data must be a mapping")
556+
assert.ErrorContains(t, err, "volumes.data: must be a mapping")
557557
}
558558

559559
func TestNonStringImage(t *testing.T) {
@@ -563,7 +563,7 @@ services:
563563
foo:
564564
image: ["busybox", "latest"]
565565
`)
566-
assert.ErrorContains(t, err, "services.foo.image must be a string")
566+
assert.ErrorContains(t, err, "services.foo.image: must be a string")
567567
}
568568

569569
func TestLoadWithEnvironment(t *testing.T) {
@@ -640,7 +640,7 @@ services:
640640
environment:
641641
FOO: ["1"]
642642
`)
643-
assert.ErrorContains(t, err, "services.dict-env.environment.FOO must be a string, number, boolean or null")
643+
assert.ErrorContains(t, err, "services.dict-env.environment.FOO: must be a string, number, boolean or null")
644644
}
645645

646646
func TestInvalidEnvironmentObject(t *testing.T) {
@@ -651,7 +651,7 @@ services:
651651
image: busybox
652652
environment: "FOO=1"
653653
`)
654-
assert.ErrorContains(t, err, "services.dict-env.environment must be a mapping")
654+
assert.ErrorContains(t, err, "services.dict-env.environment: must be a mapping")
655655
}
656656

657657
func TestLoadWithEnvironmentInterpolation(t *testing.T) {
@@ -990,7 +990,7 @@ func TestDecodeErrors(t *testing.T) {
990990

991991
configDetails := buildConfigDetails(dict, nil)
992992
_, err := LoadWithContext(context.TODO(), configDetails)
993-
assert.Error(t, err, "yaml: line 4: found a tab character that violates indentation")
993+
assert.ErrorContains(t, err, "found character '\t' that cannot start any token")
994994
}
995995

996996
func TestBuildProperties(t *testing.T) {
@@ -1196,7 +1196,7 @@ services:
11961196
foo:
11971197
bar: zot
11981198
`)
1199-
assert.ErrorContains(t, err, "services.tmpfs.volumes.0 Additional property foo is not allowed")
1199+
assert.ErrorContains(t, err, "services.tmpfs.volumes.0: Additional property foo is not allowed")
12001200
}
12011201

12021202
func TestLoadBindMountSourceMustNotBeEmpty(t *testing.T) {
@@ -1350,7 +1350,7 @@ services:
13501350
tmpfs:
13511351
size: -1
13521352
`)
1353-
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size Must be greater than or equal to 0")
1353+
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size: Must be greater than or equal to 0")
13541354
}
13551355

13561356
func TestLoadTmpfsVolumeSizeMustBeInteger(t *testing.T) {
@@ -1365,7 +1365,7 @@ services:
13651365
tmpfs:
13661366
size: 0.0001
13671367
`)
1368-
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size must be a integer")
1368+
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size: must be a integer")
13691369
}
13701370

13711371
func TestLoadAttachableNetwork(t *testing.T) {
@@ -2421,7 +2421,7 @@ services:
24212421
context: .
24222422
ssh:
24232423
`)
2424-
assert.ErrorContains(t, err, "services.test.build.ssh must be a mapping")
2424+
assert.ErrorContains(t, err, "services.test.build.ssh: must be a mapping")
24252425
}
24262426

24272427
func TestLoadLegacyBoolean(t *testing.T) {
@@ -2653,7 +2653,7 @@ func TestDeviceWriteBps(t *testing.T) {
26532653

26542654
func TestInvalidProjectNameType(t *testing.T) {
26552655
p, err := loadYAML(`name: 123`)
2656-
assert.Error(t, err, "validating filename0.yml: name must be a string")
2656+
assert.ErrorContains(t, err, "name: must be a string")
26572657
assert.Assert(t, is.Nil(p))
26582658
}
26592659

@@ -3132,7 +3132,7 @@ services:
31323132
`, nil), func(options *Options) {
31333133
options.ResolvePaths = false
31343134
})
3135-
assert.ErrorContains(t, err, "validating filename0.yml: services.frontend.develop.watch.0 action is required")
3135+
assert.ErrorContains(t, err, "services.frontend.develop.watch.0: action is required")
31363136
}
31373137

31383138
func TestBadServiceConfig(t *testing.T) {
@@ -3729,7 +3729,7 @@ services:
37293729
environment:
37303730
- DEBUG = true
37313731
`)
3732-
assert.Check(t, strings.Contains(err.Error(), "'services[test].environment': environment variable DEBUG is declared with a trailing space"))
3732+
assert.ErrorContains(t, err, "'services[test].environment': environment variable DEBUG is declared with a trailing space")
37333733
}
37343734

37353735
func TestFileModeNumber(t *testing.T) {

loader/loader_yaml_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ services:
8888
configs: !override
8989
- source: credentials
9090
target: /literally-anywhere-else
91-
9291
y:
9392
<<: *x
9493
@@ -109,6 +108,7 @@ configs:
109108
map[string]interface{}{"source": string("credentials"), "target": string("/credentials/file1")},
110109
},
111110
},
111+
"none": map[string]interface{}{},
112112
"x": map[string]interface{}{
113113
"configs": []interface{}{
114114
map[string]interface{}{"source": string("credentials"), "target": string("/literally-anywhere-else")},

loader/mapstructure.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,21 @@ func cast(from reflect.Value, to reflect.Value) (interface{}, error) {
6565
return toInt(from.String())
6666
case reflect.Int64:
6767
return toInt64(from.String())
68+
case reflect.Uint64:
69+
return toInt64(from.String())
6870
case reflect.Float32:
6971
return toFloat32(from.String())
7072
case reflect.Float64:
7173
return toFloat(from.String())
7274
}
73-
case reflect.Int:
75+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
7476
if to.Kind() == reflect.String {
7577
return strconv.FormatInt(from.Int(), 10), nil
7678
}
79+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
80+
if to.Kind() == reflect.String {
81+
return strconv.FormatUint(from.Uint(), 10), nil
82+
}
7783
}
7884
return from.Interface(), nil
7985
}

loader/merge_reset_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func Test_LoadWithReset(t *testing.T) {
4545
services:
4646
foo:
4747
image: foo
48-
build: !reset
48+
build: !reset {}
4949
environment:
50-
FOO: !reset
50+
FOO: !reset {}
5151
`),
5252
},
5353
},
@@ -79,5 +79,5 @@ func Test_DuplicateReset(t *testing.T) {
7979
}, func(options *Options) {
8080
options.SkipNormalization = true
8181
})
82-
assert.Error(t, err, "line 6: mapping key \"command\" already defined at line 5")
82+
assert.ErrorContains(t, err, "mapping key \"command\" already defined")
8383
}

0 commit comments

Comments
 (0)