BurntSushi / toml

TOML parser for Golang with reflection.
MIT License
4.54k stars 528 forks source link

How to report a line number on error when decoding custom struct? #398

Closed GreatDanton closed 11 months ago

GreatDanton commented 1 year ago

When decoding built in types, I found out that toml.Decode() method returns a ParseError which contains lots of context about the error. When unmarshalling custom structs, however, I don't know how am I supposed to obtain such context (I would like to know at least the name of the field or the line number of the user text).

func decodeConfig() (*xxx, error) {
        config := &MyConfig{}
    md, err := toml.Decode(tomlData, config)
    if err != nil {
        // pretty print errors if possible
        var pErr toml.ParseError
        if errors.As(err, &pErr) {
            return nil, fmt.Errorf("toml: config parse error: %v", pErr.ErrorWithUsage())
        }

        // err could not be cast to a toml.ParseError, which means our own unmarshaller returned an error
        // TODO: how do I get a line number or the field here? md.context is a private member.
        return nil, fmt.Errorf("toml: config parse error:\n  %v", err)
    }

         ... more code

More context about my problem: In my case I have a custom type which tries to parse user value and return an error on invalid enum:

func (n *NodeReferenceType) UnmarshalText(text []byte) error {
    t := strings.TrimSpace(string(text))
    switch t {
    case "number_and_text":
        *n = NodeReference_NumberAndText
    case "number":
        *n = NodeReference_Number
    case "text":
        *n = NodeReference_Text
    default:
               // TODO: how can I get a line number or the name of a currently decoded key here?
               // I am trying to decode multiple fields of this same type (NodeReferenceType), so I 
               // need some way to distinguish between them.
        return fmt.Errorf("invalid reference type: available options [%s, %s, %s], but got: %q", NodeReference_NumberAndText, NodeReference_Number, NodeReference_Text, t)
    }
    return nil
}
arp242 commented 1 year ago

It would be helpful if you can provide a full example (either as a program or test) that I can copy/paste to demonstrate/reproduce the problem. In your example there's lots of missing references and such.

GreatDanton commented 1 year ago

I hope this is better

package main

import (
    "errors"
    "fmt"
    "github.com/BurntSushi/toml"
    "log"
    "strings"
)

type CustomType uint8

func (n *CustomType) UnmarshalText(text []byte) error {
    t := strings.TrimSpace(string(text))
    switch t {
    case "number_and_text":
        *n = 0
    case "number":
        *n = 1
    case "text":
        *n = 2
    default:
        // TODO: how can I get a line number or the name of a currently decoded field here?
        // I am trying to decode multiple fields of this same type (CustomType), so I
        // need some way to distinguish between them.
        return fmt.Errorf("invalid reference type: available options [%d, %d, %d], but got: %q", 0, 1, 2, t)
    }
    return nil
}

type MyConfig struct {
    Field1 CustomType `toml:"field1"`
    Field2 CustomType `toml:"field2"`
    Field3 CustomType `toml:"field3"`
}

func main() {
    CONFIG := `
    field1 = "number_and_text"
    field2 = "number"
    field3 = "invalid enum"
    `

    config := &MyConfig{}
    md, err := toml.Decode(CONFIG, config)
    if err != nil {
        var pErr toml.ParseError
        if errors.As(err, &pErr) {
            log.Fatalf("toml: config parse error: %v", pErr.ErrorWithUsage())
        }

        // err could not be cast to a toml.ParseError, which means our own unmarshaller
        // returned an error. Here I would need some way to report which field has invalid
                // value (field1, field2 or field3?)
        if false {
            // md contains context with our field that reported a decode error, but 
                        // it's a private member
            fmt.Println(md)
        }
        log.Fatalf("toml: config parse error: %v", err)
    }
}
arp242 commented 1 year ago

Cheers; that's helpful. I'll have a look tomorrow.

arp242 commented 11 months ago

Sorry, I forgot about your issue.

I fixed it; if you use go get github.com/BurntSushi/toml@master you should automatically get a ParseError, and your code should work.