Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populating strings with values that could be parsed as booleans mutates the result #74

Open
lrewega opened this issue Apr 10, 2018 · 5 comments
Labels

Comments

@lrewega
Copy link

lrewega commented Apr 10, 2018

package main

import (
        "fmt"

        "go.uber.org/config"
)

func main() {
        p, err := config.NewYAMLProviderFromBytes([]byte(`foo: yes`))
        if err != nil {
                panic(err)
        }

        var foo string

        if err := p.Get("foo").Populate(&foo); err != nil {
                panic(err)
        }

        fmt.Println(foo)
}

This prints true, not yes as I'd expect.

@glibsm
Copy link
Contributor

glibsm commented Apr 10, 2018

Good find. foo: "yes" does the right thing, but it should work with foo: yes correctly as well.

The underlying yaml library does the right thing, which likely means a bug in config code somewhere.

package main

import (
	"fmt"
	"log"

	"gopkg.in/yaml.v2"
)

func main() {
	var stringFoo string
	err := yaml.Unmarshal([]byte(`yes`), &stringFoo)
	if err != nil {
		log.Panic(err)
	}
	fmt.Println("string foo", stringFoo)

	var boolFoo bool
	err = yaml.Unmarshal([]byte(`yes`), &boolFoo)
	if err != nil {
		log.Panic(err)
	}
	fmt.Println("bool foo", boolFoo)
}

@glibsm
Copy link
Contributor

glibsm commented Apr 10, 2018

It looks like at the core is the way yaml library handles interaface{} unmarshaling. Here is the smallest repro scenario for this bug:

package main

import (
	"fmt"
	"log"

	"gopkg.in/yaml.v2"
)

func main() {
	var i interface{}
	err := yaml.Unmarshal([]byte(`yes`), &i)
	if err != nil {
		log.Panic(err)
	}
	fmt.Println("interface{} foo", i)
}
// Output: interface{} foo true

The line in question: https://github.com/uber-go/config/blob/master/yaml.go#L51

@xuyang2
Copy link

xuyang2 commented Jun 27, 2018

YAML 1.1 says:

Canonical:

y|n

Regexp:

y|Y|yes|Yes|YES|n|N|no|No|NO |true|True|TRUE|false|False|FALSE |on|On|ON|off|Off|OFF

Related lines in yaml library:

{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
yaml_BOOL_TAG      = "tag:yaml.org,2002:bool"      // The tag !!bool with the values: true and false.

So I think this can be work around by adding an explicit !!str tag:

package main

import (
	"fmt"
	"log"

	"gopkg.in/yaml.v2"
)

func main() {
	var i interface{}
	err := yaml.Unmarshal([]byte(`!!str yes`), &i)
	if err != nil {
		log.Panic(err)
	}
	fmt.Println("interface{} foo", i)
}
// Output: interface{} foo yes

@akshayjshah
Copy link
Contributor

akshayjshah commented Jul 2, 2018

As @xuyang2 points out, the YAML specification outlines a number of different ways to encode Boolean values. The way these strings are treated depends on the structure they're unmarshalled into - unmarshalling into a string keeps the YAML as-is, while unmarshalling into interface{} treats these strings as Booleans. Quoting these special strings or using !!str forces standards-compliant YAML parsers (including gopkg.in/yaml.v2) to always treat them as strings, side-stepping this issue entirely.

These special representations of bools cause particular trouble for this package because we need to merge multiple YAML files into a single file. Post-v1.1, we do this decoding into interface{}, merging, and re-encoding. (We handle it a little differently in earlier versions, but the bug is the same.) This has the unfortunate side effect of converting these special strings into "true" or "false" even when unmarshalling into a string field. This is unfortunate, but it's difficult to fix without introducing more significant breakages in multi-source configurations.

In principle, we could fix this by writing a YAML parser from scratch and merging ASTs without unmarshalling. I doubt that we'll ever spend the time required to actually do this.

@akshayjshah
Copy link
Contributor

Documented this more clearly in #94. I'm going to leave this open in case someone comes up with a nice fix (or feels like writing a fully-compliant YAML parser that lets us merge without unmarshalling).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

4 participants