Skip to content

Commit

Permalink
Fix encoding of anchor and alias (#605)
Browse files Browse the repository at this point in the history
* fix encoding of anchor and alias

* fix alias

* fix lint error
  • Loading branch information
goccy authored Dec 23, 2024
1 parent beec790 commit 9cbf5d4
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 47 deletions.
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}

### 3.1. Explicitly declared `Anchor` name and `Alias` name

If you want to use `anchor` or `alias`, you can define it as a struct tag.
If you want to use `anchor`, you can define it as a struct tag.
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor.

```go
type T struct {
Expand Down Expand Up @@ -216,10 +218,7 @@ d: *x

If you do not explicitly declare the anchor name, the default behavior is to
use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor.

If you do not explicitly declare the alias name AND the value is a pointer
to another element, we look up the anchor name by finding out which anchor
field the value is assigned to by looking up its pointer address.
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.

```go
type T struct {
Expand All @@ -229,8 +228,8 @@ type T struct {
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
C *T `yaml:"c"`
D *T `yaml:"d"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
Expand Down
45 changes: 15 additions & 30 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,42 +787,27 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column
}
var key ast.MapKeyNode = e.encodeString(structField.RenderName, column)
switch {
case structField.AnchorName != "":
anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column)
if err != nil {
return nil, err
}
value = anchorNode
case structField.IsAutoAlias:
if fieldValue.Kind() != reflect.Ptr {
return nil, fmt.Errorf(
"%s in struct is not pointer type. but required automatically alias detection",
structField.FieldName,
)
}
anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()]
if anchorName == "" {
return nil, errors.New(
"cannot find anchor name from pointer address for automatically alias detection",
)
case value.Type() == ast.AliasType:
if aliasName := structField.AliasName; aliasName != "" {
alias, ok := value.(*ast.AliasNode)
if !ok {
return nil, errors.ErrUnexpectedNodeType(value.Type(), ast.AliasType, value.GetToken())
}
got := alias.Value.String()
if aliasName != got {
return nil, fmt.Errorf("expected alias name is %q but got %q", aliasName, got)
}
}
aliasName := anchorName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
value = alias
if structField.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
}
case structField.AliasName != "":
aliasName := structField.AliasName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
value = alias
if structField.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
case structField.AnchorName != "":
anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column)
if err != nil {
return nil, err
}
value = anchorNode
case structField.IsInline:
isAutoAnchor := structField.IsAutoAnchor
if !hasInlineAnchorField {
Expand Down
60 changes: 51 additions & 9 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -804,8 +804,8 @@ func TestEncodeWithAutoAlias(t *testing.T) {
var v struct {
A *T `yaml:"a,anchor=a"`
B *T `yaml:"b,anchor=b"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
C *T `yaml:"c"`
D *T `yaml:"d"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
Expand Down Expand Up @@ -838,8 +838,8 @@ func TestEncodeWithImplicitAnchorAndAlias(t *testing.T) {
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
C *T `yaml:"c"`
D *T `yaml:"d"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
Expand All @@ -864,7 +864,7 @@ d: *b

func TestEncodeWithMerge(t *testing.T) {
type Person struct {
*Person `yaml:",omitempty,inline,alias"`
*Person `yaml:",omitempty,inline"`
Name string `yaml:",omitempty"`
Age int `yaml:",omitempty"`
}
Expand Down Expand Up @@ -1151,8 +1151,8 @@ func TestEncoder_MarshalAnchor(t *testing.T) {
Host *Host `yaml:",anchor"`
}
type Queue struct {
Name string `yaml:","`
*Host `yaml:",alias"`
Name string `yaml:","`
*Host
}
var doc struct {
Hosts []*HostDecl `yaml:"hosts"`
Expand Down Expand Up @@ -1338,8 +1338,8 @@ func ExampleMarshal_implicitAnchorAlias() {
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
C *T `yaml:"c"`
D *T `yaml:"d"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
Expand Down Expand Up @@ -1675,3 +1675,45 @@ func TestIssue174(t *testing.T) {
t.Fatalf("failed to encode: %q", got)
}
}

func TestIssue259(t *testing.T) {
type AnchorValue struct {
Foo uint64
Bar string
}

type Value struct {
Baz string `yaml:"baz"`
Value *AnchorValue `yaml:"value,anchor"`
}

type Schema struct {
Values []*Value
}

schema := Schema{}
anchorValue := AnchorValue{Foo: 3, Bar: "bar"}
schema.Values = []*Value{
{Baz: "xxx", Value: &anchorValue},
{Baz: "yyy", Value: &anchorValue},
{Baz: "zzz", Value: &anchorValue},
}
b, err := yaml.Marshal(schema)
if err != nil {
t.Fatal(err)
}
expected := `
values:
- baz: xxx
value: &value
foo: 3
bar: bar
- baz: yyy
value: *value
- baz: zzz
value: *value
`
if strings.TrimPrefix(expected, "\n") != string(b) {
t.Fatalf("failed to encode: got = %s", string(b))
}
}
2 changes: 1 addition & 1 deletion testdata/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ b: *a
`
var v struct {
A *int `yaml:"a,anchor"`
B *int `yaml:"b,alias"`
B *int `yaml:"b"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
Expand Down

0 comments on commit 9cbf5d4

Please sign in to comment.