error.go 8.1 KB

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