taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

error.go (10278B)


      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 	Col   int // Error column, starting at 1.
     71 	Start int // Start of error, as byte offset starting at 0.
     72 	Len   int // Length of the error in bytes.
     73 }
     74 
     75 func (p Position) withCol(tomlFile string) Position {
     76 	var (
     77 		pos   int
     78 		lines = strings.Split(tomlFile, "\n")
     79 	)
     80 	for i := range lines {
     81 		ll := len(lines[i]) + 1 // +1 for the removed newline
     82 		if pos+ll >= p.Start {
     83 			p.Col = p.Start - pos + 1
     84 			if p.Col < 1 { // Should never happen, but just in case.
     85 				p.Col = 1
     86 			}
     87 			break
     88 		}
     89 		pos += ll
     90 	}
     91 	return p
     92 }
     93 
     94 func (pe ParseError) Error() string {
     95 	if pe.LastKey == "" {
     96 		return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, pe.Message)
     97 	}
     98 	return fmt.Sprintf("toml: line %d (last key %q): %s",
     99 		pe.Position.Line, pe.LastKey, pe.Message)
    100 }
    101 
    102 // ErrorWithPosition returns the error with detailed location context.
    103 //
    104 // See the documentation on [ParseError].
    105 func (pe ParseError) ErrorWithPosition() string {
    106 	if pe.input == "" { // Should never happen, but just in case.
    107 		return pe.Error()
    108 	}
    109 
    110 	// TODO: don't show control characters as literals? This may not show up
    111 	// well everywhere.
    112 
    113 	var (
    114 		lines = strings.Split(pe.input, "\n")
    115 		b     = new(strings.Builder)
    116 	)
    117 	if pe.Position.Len == 1 {
    118 		fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
    119 			pe.Message, pe.Position.Line, pe.Position.Col)
    120 	} else {
    121 		fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
    122 			pe.Message, pe.Position.Line, pe.Position.Col, pe.Position.Col+pe.Position.Len-1)
    123 	}
    124 	if pe.Position.Line > 2 {
    125 		fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3]))
    126 	}
    127 	if pe.Position.Line > 1 {
    128 		fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2]))
    129 	}
    130 
    131 	/// Expand tabs, so that the ^^^s are at the correct position, but leave
    132 	/// "column 10-13" intact. Adjusting this to the visual column would be
    133 	/// better, but we don't know the tabsize of the user in their editor, which
    134 	/// can be 8, 4, 2, or something else. We can't know. So leaving it as the
    135 	/// character index is probably the "most correct".
    136 	expanded := expandTab(lines[pe.Position.Line-1])
    137 	diff := len(expanded) - len(lines[pe.Position.Line-1])
    138 
    139 	fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded)
    140 	fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", pe.Position.Col-1+diff), strings.Repeat("^", pe.Position.Len))
    141 	return b.String()
    142 }
    143 
    144 // ErrorWithUsage returns the error with detailed location context and usage
    145 // guidance.
    146 //
    147 // See the documentation on [ParseError].
    148 func (pe ParseError) ErrorWithUsage() string {
    149 	m := pe.ErrorWithPosition()
    150 	if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
    151 		lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
    152 		for i := range lines {
    153 			if lines[i] != "" {
    154 				lines[i] = "    " + lines[i]
    155 			}
    156 		}
    157 		return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
    158 	}
    159 	return m
    160 }
    161 
    162 func expandTab(s string) string {
    163 	var (
    164 		b    strings.Builder
    165 		l    int
    166 		fill = func(n int) string {
    167 			b := make([]byte, n)
    168 			for i := range b {
    169 				b[i] = ' '
    170 			}
    171 			return string(b)
    172 		}
    173 	)
    174 	b.Grow(len(s))
    175 	for _, r := range s {
    176 		switch r {
    177 		case '\t':
    178 			tw := 8 - l%8
    179 			b.WriteString(fill(tw))
    180 			l += tw
    181 		default:
    182 			b.WriteRune(r)
    183 			l += 1
    184 		}
    185 	}
    186 	return b.String()
    187 }
    188 
    189 type (
    190 	errLexControl       struct{ r rune }
    191 	errLexEscape        struct{ r rune }
    192 	errLexUTF8          struct{ b byte }
    193 	errParseDate        struct{ v string }
    194 	errLexInlineTableNL struct{}
    195 	errLexStringNL      struct{}
    196 	errParseRange       struct {
    197 		i    any    // int or float
    198 		size string // "int64", "uint16", etc.
    199 	}
    200 	errUnsafeFloat struct {
    201 		i    interface{} // float32 or float64
    202 		size string      // "float32" or "float64"
    203 	}
    204 	errParseDuration struct{ d string }
    205 )
    206 
    207 func (e errLexControl) Error() string {
    208 	return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
    209 }
    210 func (e errLexControl) Usage() string { return "" }
    211 
    212 func (e errLexEscape) Error() string        { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
    213 func (e errLexEscape) Usage() string        { return usageEscape }
    214 func (e errLexUTF8) Error() string          { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
    215 func (e errLexUTF8) Usage() string          { return "" }
    216 func (e errParseDate) Error() string        { return fmt.Sprintf("invalid datetime: %q", e.v) }
    217 func (e errParseDate) Usage() string        { return usageDate }
    218 func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
    219 func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
    220 func (e errLexStringNL) Error() string      { return "strings cannot contain newlines" }
    221 func (e errLexStringNL) Usage() string      { return usageStringNewline }
    222 func (e errParseRange) Error() string       { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
    223 func (e errParseRange) Usage() string       { return usageIntOverflow }
    224 func (e errUnsafeFloat) Error() string {
    225 	return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size)
    226 }
    227 func (e errUnsafeFloat) Usage() string   { return usageUnsafeFloat }
    228 func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
    229 func (e errParseDuration) Usage() string { return usageDuration }
    230 
    231 const usageEscape = `
    232 A '\' inside a "-delimited string is interpreted as an escape character.
    233 
    234 The following escape sequences are supported:
    235 \b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
    236 
    237 To prevent a '\' from being recognized as an escape character, use either:
    238 
    239 - a ' or '''-delimited string; escape characters aren't processed in them; or
    240 - write two backslashes to get a single backslash: '\\'.
    241 
    242 If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
    243 instead of '\' will usually also work: "C:/Users/martin".
    244 `
    245 
    246 const usageInlineNewline = `
    247 Inline tables must always be on a single line:
    248 
    249     table = {key = 42, second = 43}
    250 
    251 It is invalid to split them over multiple lines like so:
    252 
    253     # INVALID
    254     table = {
    255         key    = 42,
    256         second = 43
    257     }
    258 
    259 Use regular for this:
    260 
    261     [table]
    262     key    = 42
    263     second = 43
    264 `
    265 
    266 const usageStringNewline = `
    267 Strings must always be on a single line, and cannot span more than one line:
    268 
    269     # INVALID
    270     string = "Hello,
    271     world!"
    272 
    273 Instead use """ or ''' to split strings over multiple lines:
    274 
    275     string = """Hello,
    276     world!"""
    277 `
    278 
    279 const usageIntOverflow = `
    280 This number is too large; this may be an error in the TOML, but it can also be a
    281 bug in the program that uses too small of an integer.
    282 
    283 The maximum and minimum values are:
    284 
    285     size   │ lowest         │ highest
    286     ───────┼────────────────┼──────────────
    287     int8   │ -128           │ 127
    288     int16  │ -32,768        │ 32,767
    289     int32  │ -2,147,483,648 │ 2,147,483,647
    290     int64  │ -9.2 × 10¹⁷    │ 9.2 × 10¹⁷
    291     uint8  │ 0              │ 255
    292     uint16 │ 0              │ 65,535
    293     uint32 │ 0              │ 4,294,967,295
    294     uint64 │ 0              │ 1.8 × 10¹⁸
    295 
    296 int refers to int32 on 32-bit systems and int64 on 64-bit systems.
    297 `
    298 
    299 const usageUnsafeFloat = `
    300 This number is outside of the "safe" range for floating point numbers; whole
    301 (non-fractional) numbers outside the below range can not always be represented
    302 accurately in a float, leading to some loss of accuracy.
    303 
    304 Explicitly mark a number as a fractional unit by adding ".0", which will incur
    305 some loss of accuracy; for example:
    306 
    307 	f = 2_000_000_000.0
    308 
    309 Accuracy ranges:
    310 
    311 	float32 =            16,777,215
    312 	float64 = 9,007,199,254,740,991
    313 `
    314 
    315 const usageDuration = `
    316 A duration must be as "number<unit>", without any spaces. Valid units are:
    317 
    318     ns         nanoseconds (billionth of a second)
    319     us, µs     microseconds (millionth of a second)
    320     ms         milliseconds (thousands of a second)
    321     s          seconds
    322     m          minutes
    323     h          hours
    324 
    325 You can combine multiple units; for example "5m10s" for 5 minutes and 10
    326 seconds.
    327 `
    328 
    329 const usageDate = `
    330 A TOML datetime must be in one of the following formats:
    331 
    332     2006-01-02T15:04:05Z07:00   Date and time, with timezone.
    333     2006-01-02T15:04:05         Date and time, but without timezone.
    334     2006-01-02                  Date without a time or timezone.
    335     15:04:05                    Just a time, without any timezone.
    336 
    337 Seconds may optionally have a fraction, up to nanosecond precision:
    338 
    339     15:04:05.123
    340     15:04:05.856018510
    341 `
    342 
    343 // TOML 1.1:
    344 // The seconds part in times is optional, and may be omitted:
    345 //     2006-01-02T15:04Z07:00
    346 //     2006-01-02T15:04
    347 //     15:04