...

Source file src/github.com/BurntSushi/toml/error.go

Documentation: github.com/BurntSushi/toml

     1  package toml
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  // ParseError is returned when there is an error parsing the TOML syntax such as
     9  // invalid syntax, duplicate keys, etc.
    10  //
    11  // In addition to the error message itself, you can also print detailed location
    12  // information with context by using [ErrorWithPosition]:
    13  //
    14  //	toml: error: Key 'fruit' was already created and cannot be used as an array.
    15  //
    16  //	At line 4, column 2-7:
    17  //
    18  //	      2 | fruit = []
    19  //	      3 |
    20  //	      4 | [[fruit]] # Not allowed
    21  //	            ^^^^^
    22  //
    23  // [ErrorWithUsage] can be used to print the above with some more detailed usage
    24  // guidance:
    25  //
    26  //	toml: error: newlines not allowed within inline tables
    27  //
    28  //	At line 1, column 18:
    29  //
    30  //	      1 | x = [{ key = 42 #
    31  //	                           ^
    32  //
    33  //	Error help:
    34  //
    35  //	  Inline tables must always be on a single line:
    36  //
    37  //	      table = {key = 42, second = 43}
    38  //
    39  //	  It is invalid to split them over multiple lines like so:
    40  //
    41  //	      # INVALID
    42  //	      table = {
    43  //	          key    = 42,
    44  //	          second = 43
    45  //	      }
    46  //
    47  //	  Use regular for this:
    48  //
    49  //	      [table]
    50  //	      key    = 42
    51  //	      second = 43
    52  type ParseError struct {
    53  	Message  string   // Short technical message.
    54  	Usage    string   // Longer message with usage guidance; may be blank.
    55  	Position Position // Position of the error
    56  	LastKey  string   // Last parsed key, may be blank.
    57  
    58  	// Line the error occurred.
    59  	//
    60  	// Deprecated: use [Position].
    61  	Line int
    62  
    63  	err   error
    64  	input string
    65  }
    66  
    67  // Position of an error.
    68  type Position struct {
    69  	Line  int // Line number, starting at 1.
    70  	Start int // Start of error, as byte offset starting at 0.
    71  	Len   int // Lenght in bytes.
    72  }
    73  
    74  func (pe ParseError) Error() string {
    75  	msg := pe.Message
    76  	if msg == "" { // Error from errorf()
    77  		msg = pe.err.Error()
    78  	}
    79  
    80  	if pe.LastKey == "" {
    81  		return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
    82  	}
    83  	return fmt.Sprintf("toml: line %d (last key %q): %s",
    84  		pe.Position.Line, pe.LastKey, msg)
    85  }
    86  
    87  // ErrorWithPosition returns the error with detailed location context.
    88  //
    89  // See the documentation on [ParseError].
    90  func (pe ParseError) ErrorWithPosition() string {
    91  	if pe.input == "" { // Should never happen, but just in case.
    92  		return pe.Error()
    93  	}
    94  
    95  	var (
    96  		lines = strings.Split(pe.input, "\n")
    97  		col   = pe.column(lines)
    98  		b     = new(strings.Builder)
    99  	)
   100  
   101  	msg := pe.Message
   102  	if msg == "" {
   103  		msg = pe.err.Error()
   104  	}
   105  
   106  	// TODO: don't show control characters as literals? This may not show up
   107  	// well everywhere.
   108  
   109  	if pe.Position.Len == 1 {
   110  		fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
   111  			msg, pe.Position.Line, col+1)
   112  	} else {
   113  		fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
   114  			msg, pe.Position.Line, col, col+pe.Position.Len)
   115  	}
   116  	if pe.Position.Line > 2 {
   117  		fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
   118  	}
   119  	if pe.Position.Line > 1 {
   120  		fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
   121  	}
   122  	fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
   123  	fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
   124  	return b.String()
   125  }
   126  
   127  // ErrorWithUsage returns the error with detailed location context and usage
   128  // guidance.
   129  //
   130  // See the documentation on [ParseError].
   131  func (pe ParseError) ErrorWithUsage() string {
   132  	m := pe.ErrorWithPosition()
   133  	if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
   134  		lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
   135  		for i := range lines {
   136  			if lines[i] != "" {
   137  				lines[i] = "    " + lines[i]
   138  			}
   139  		}
   140  		return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
   141  	}
   142  	return m
   143  }
   144  
   145  func (pe ParseError) column(lines []string) int {
   146  	var pos, col int
   147  	for i := range lines {
   148  		ll := len(lines[i]) + 1 // +1 for the removed newline
   149  		if pos+ll >= pe.Position.Start {
   150  			col = pe.Position.Start - pos
   151  			if col < 0 { // Should never happen, but just in case.
   152  				col = 0
   153  			}
   154  			break
   155  		}
   156  		pos += ll
   157  	}
   158  
   159  	return col
   160  }
   161  
   162  type (
   163  	errLexControl       struct{ r rune }
   164  	errLexEscape        struct{ r rune }
   165  	errLexUTF8          struct{ b byte }
   166  	errLexInvalidNum    struct{ v string }
   167  	errLexInvalidDate   struct{ v string }
   168  	errLexInlineTableNL struct{}
   169  	errLexStringNL      struct{}
   170  	errParseRange       struct {
   171  		i    interface{} // int or float
   172  		size string      // "int64", "uint16", etc.
   173  	}
   174  	errParseDuration struct{ d string }
   175  )
   176  
   177  func (e errLexControl) Error() string {
   178  	return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
   179  }
   180  func (e errLexControl) Usage() string { return "" }
   181  
   182  func (e errLexEscape) Error() string        { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
   183  func (e errLexEscape) Usage() string        { return usageEscape }
   184  func (e errLexUTF8) Error() string          { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
   185  func (e errLexUTF8) Usage() string          { return "" }
   186  func (e errLexInvalidNum) Error() string    { return fmt.Sprintf("invalid number: %q", e.v) }
   187  func (e errLexInvalidNum) Usage() string    { return "" }
   188  func (e errLexInvalidDate) Error() string   { return fmt.Sprintf("invalid date: %q", e.v) }
   189  func (e errLexInvalidDate) Usage() string   { return "" }
   190  func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
   191  func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
   192  func (e errLexStringNL) Error() string      { return "strings cannot contain newlines" }
   193  func (e errLexStringNL) Usage() string      { return usageStringNewline }
   194  func (e errParseRange) Error() string       { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
   195  func (e errParseRange) Usage() string       { return usageIntOverflow }
   196  func (e errParseDuration) Error() string    { return fmt.Sprintf("invalid duration: %q", e.d) }
   197  func (e errParseDuration) Usage() string    { return usageDuration }
   198  
   199  const usageEscape = `
   200  A '\' inside a "-delimited string is interpreted as an escape character.
   201  
   202  The following escape sequences are supported:
   203  \b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
   204  
   205  To prevent a '\' from being recognized as an escape character, use either:
   206  
   207  - a ' or '''-delimited string; escape characters aren't processed in them; or
   208  - write two backslashes to get a single backslash: '\\'.
   209  
   210  If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
   211  instead of '\' will usually also work: "C:/Users/martin".
   212  `
   213  
   214  const usageInlineNewline = `
   215  Inline tables must always be on a single line:
   216  
   217      table = {key = 42, second = 43}
   218  
   219  It is invalid to split them over multiple lines like so:
   220  
   221      # INVALID
   222      table = {
   223          key    = 42,
   224          second = 43
   225      }
   226  
   227  Use regular for this:
   228  
   229      [table]
   230      key    = 42
   231      second = 43
   232  `
   233  
   234  const usageStringNewline = `
   235  Strings must always be on a single line, and cannot span more than one line:
   236  
   237      # INVALID
   238      string = "Hello,
   239      world!"
   240  
   241  Instead use """ or ''' to split strings over multiple lines:
   242  
   243      string = """Hello,
   244      world!"""
   245  `
   246  
   247  const usageIntOverflow = `
   248  This number is too large; this may be an error in the TOML, but it can also be a
   249  bug in the program that uses too small of an integer.
   250  
   251  The maximum and minimum values are:
   252  
   253      size   │ lowest         │ highest
   254      ───────┼────────────────┼──────────
   255      int8   │ -128           │ 127
   256      int16  │ -32,768        │ 32,767
   257      int32  │ -2,147,483,648 │ 2,147,483,647
   258      int64  │ -9.2 × 10¹⁷    │ 9.2 × 10¹⁷
   259      uint8  │ 0              │ 255
   260      uint16 │ 0              │ 65535
   261      uint32 │ 0              │ 4294967295
   262      uint64 │ 0              │ 1.8 × 10¹⁸
   263  
   264  int refers to int32 on 32-bit systems and int64 on 64-bit systems.
   265  `
   266  
   267  const usageDuration = `
   268  A duration must be as "number<unit>", without any spaces. Valid units are:
   269  
   270      ns         nanoseconds (billionth of a second)
   271      us, µs     microseconds (millionth of a second)
   272      ms         milliseconds (thousands of a second)
   273      s          seconds
   274      m          minutes
   275      h          hours
   276  
   277  You can combine multiple units; for example "5m10s" for 5 minutes and 10
   278  seconds.
   279  `
   280  

View as plain text