errors.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package toml
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. "github.com/pelletier/go-toml/v2/internal/danger"
  7. )
  8. // DecodeError represents an error encountered during the parsing or decoding
  9. // of a TOML document.
  10. //
  11. // In addition to the error message, it contains the position in the document
  12. // where it happened, as well as a human-readable representation that shows
  13. // where the error occurred in the document.
  14. type DecodeError struct {
  15. message string
  16. line int
  17. column int
  18. key Key
  19. human string
  20. }
  21. // StrictMissingError occurs in a TOML document that does not have a
  22. // corresponding field in the target value. It contains all the missing fields
  23. // in Errors.
  24. //
  25. // Emitted by Decoder when DisallowUnknownFields() was called.
  26. type StrictMissingError struct {
  27. // One error per field that could not be found.
  28. Errors []DecodeError
  29. }
  30. // Error returns the canonical string for this error.
  31. func (s *StrictMissingError) Error() string {
  32. return "strict mode: fields in the document are missing in the target struct"
  33. }
  34. // String returns a human readable description of all errors.
  35. func (s *StrictMissingError) String() string {
  36. var buf strings.Builder
  37. for i, e := range s.Errors {
  38. if i > 0 {
  39. buf.WriteString("\n---\n")
  40. }
  41. buf.WriteString(e.String())
  42. }
  43. return buf.String()
  44. }
  45. type Key []string
  46. // internal version of DecodeError that is used as the base to create a
  47. // DecodeError with full context.
  48. type decodeError struct {
  49. highlight []byte
  50. message string
  51. key Key // optional
  52. }
  53. func (de *decodeError) Error() string {
  54. return de.message
  55. }
  56. func newDecodeError(highlight []byte, format string, args ...interface{}) error {
  57. return &decodeError{
  58. highlight: highlight,
  59. message: fmt.Errorf(format, args...).Error(),
  60. }
  61. }
  62. // Error returns the error message contained in the DecodeError.
  63. func (e *DecodeError) Error() string {
  64. return "toml: " + e.message
  65. }
  66. // String returns the human-readable contextualized error. This string is multi-line.
  67. func (e *DecodeError) String() string {
  68. return e.human
  69. }
  70. // Position returns the (line, column) pair indicating where the error
  71. // occurred in the document. Positions are 1-indexed.
  72. func (e *DecodeError) Position() (row int, column int) {
  73. return e.line, e.column
  74. }
  75. // Key that was being processed when the error occurred. The key is present only
  76. // if this DecodeError is part of a StrictMissingError.
  77. func (e *DecodeError) Key() Key {
  78. return e.key
  79. }
  80. // decodeErrorFromHighlight creates a DecodeError referencing a highlighted
  81. // range of bytes from document.
  82. //
  83. // highlight needs to be a sub-slice of document, or this function panics.
  84. //
  85. // The function copies all bytes used in DecodeError, so that document and
  86. // highlight can be freely deallocated.
  87. //nolint:funlen
  88. func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
  89. offset := danger.SubsliceOffset(document, de.highlight)
  90. errMessage := de.Error()
  91. errLine, errColumn := positionAtEnd(document[:offset])
  92. before, after := linesOfContext(document, de.highlight, offset, 3)
  93. var buf strings.Builder
  94. maxLine := errLine + len(after) - 1
  95. lineColumnWidth := len(strconv.Itoa(maxLine))
  96. // Write the lines of context strictly before the error.
  97. for i := len(before) - 1; i > 0; i-- {
  98. line := errLine - i
  99. buf.WriteString(formatLineNumber(line, lineColumnWidth))
  100. buf.WriteString("|")
  101. if len(before[i]) > 0 {
  102. buf.WriteString(" ")
  103. buf.Write(before[i])
  104. }
  105. buf.WriteRune('\n')
  106. }
  107. // Write the document line that contains the error.
  108. buf.WriteString(formatLineNumber(errLine, lineColumnWidth))
  109. buf.WriteString("| ")
  110. if len(before) > 0 {
  111. buf.Write(before[0])
  112. }
  113. buf.Write(de.highlight)
  114. if len(after) > 0 {
  115. buf.Write(after[0])
  116. }
  117. buf.WriteRune('\n')
  118. // Write the line with the error message itself (so it does not have a line
  119. // number).
  120. buf.WriteString(strings.Repeat(" ", lineColumnWidth))
  121. buf.WriteString("| ")
  122. if len(before) > 0 {
  123. buf.WriteString(strings.Repeat(" ", len(before[0])))
  124. }
  125. buf.WriteString(strings.Repeat("~", len(de.highlight)))
  126. if len(errMessage) > 0 {
  127. buf.WriteString(" ")
  128. buf.WriteString(errMessage)
  129. }
  130. // Write the lines of context strictly after the error.
  131. for i := 1; i < len(after); i++ {
  132. buf.WriteRune('\n')
  133. line := errLine + i
  134. buf.WriteString(formatLineNumber(line, lineColumnWidth))
  135. buf.WriteString("|")
  136. if len(after[i]) > 0 {
  137. buf.WriteString(" ")
  138. buf.Write(after[i])
  139. }
  140. }
  141. return &DecodeError{
  142. message: errMessage,
  143. line: errLine,
  144. column: errColumn,
  145. key: de.key,
  146. human: buf.String(),
  147. }
  148. }
  149. func formatLineNumber(line int, width int) string {
  150. format := "%" + strconv.Itoa(width) + "d"
  151. return fmt.Sprintf(format, line)
  152. }
  153. func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) {
  154. return beforeLines(document, offset, linesAround), afterLines(document, highlight, offset, linesAround)
  155. }
  156. func beforeLines(document []byte, offset int, linesAround int) [][]byte {
  157. var beforeLines [][]byte
  158. // Walk the document backward from the highlight to find previous lines
  159. // of context.
  160. rest := document[:offset]
  161. backward:
  162. for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; {
  163. switch {
  164. case rest[o] == '\n':
  165. // handle individual lines
  166. beforeLines = append(beforeLines, rest[o+1:])
  167. rest = rest[:o]
  168. o = len(rest) - 1
  169. case o == 0:
  170. // add the first line only if it's non-empty
  171. beforeLines = append(beforeLines, rest)
  172. break backward
  173. default:
  174. o--
  175. }
  176. }
  177. return beforeLines
  178. }
  179. func afterLines(document []byte, highlight []byte, offset int, linesAround int) [][]byte {
  180. var afterLines [][]byte
  181. // Walk the document forward from the highlight to find the following
  182. // lines of context.
  183. rest := document[offset+len(highlight):]
  184. forward:
  185. for o := 0; o < len(rest) && len(afterLines) <= linesAround; {
  186. switch {
  187. case rest[o] == '\n':
  188. // handle individual lines
  189. afterLines = append(afterLines, rest[:o])
  190. rest = rest[o+1:]
  191. o = 0
  192. case o == len(rest)-1:
  193. // add last line only if it's non-empty
  194. afterLines = append(afterLines, rest)
  195. break forward
  196. default:
  197. o++
  198. }
  199. }
  200. return afterLines
  201. }
  202. func positionAtEnd(b []byte) (row int, column int) {
  203. row = 1
  204. column = 1
  205. for _, c := range b {
  206. if c == '\n' {
  207. row++
  208. column = 1
  209. } else {
  210. column++
  211. }
  212. }
  213. return
  214. }